Hur en OpenVPN-klient bakom NAT blir internetets utgång
Utgången ringer själv ut, så dess router behöver ingen inkommande port. Följ vad hubben routar, vad utgången vidarebefordrar och varifrån returvägen kommer.
Tre Linux-värdar, en design: en offentligt nåbar OpenVPN-hubb, en konsumentbärbar och en utgångsmaskin bakom en vanlig hemrouter som inte vidarebefordrar något inåt. Utgången ringer själv till hubben. Sedan går konsumentens internettrafik in i tunneln på den bärbara och ut genom utgångens egen uplänk. Allt överraskande med detta följer av en detalj: utgången startade anslutningen, så vägen inåt finns redan och ingen portvidarebefordran behövs för att skapa den.
Det följande följer paketets väg i stället för konfigurationsordningen, eftersom konfigurationen bara är meningsfull när man vet vilken komponent som äger vilket beslut. Varje lager äger exakt ett beslut, och felen samlas där två lager antas vara ett.
Två transportsessioner, en inre väg
Varje värd håller sin egen yttre anslutning till hubben, och ingen av dem känner till den andra. Konsumentens trafik till internet är ett separat inre flöde som råkar bäras inne i konsumentens tunnel och sedan sänds ut igen från utgångens tunnel. Det är detta inre flöde hubben måste routa och det utgången måste vidarebefordra.
outer: consumer --TLS--> hub
outer: exit --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink
Eftersom båda de yttre anslutningarna går utåt från klientsidan ser utgångens router aldrig ett inkommande SYN och behöver aldrig en destinations-NAT-regel. Den behöver bara hålla den etablerade transporten vid liv, vilket den redan gör för varje långvarig anslutning. Ingenting på det publika internetet behöver först nå utgången, därför är en dynamisk adress på den uplänken inget problem. Routern måste fortfarande tillåta den utgående sessionen, men det är samma tillstånd som en webbläsare redan använder.
Vad iroute faktiskt installerar inne i servern
Hubben är den enda enheten som avgör vilken peer som äger vilken destination, och den fattar beslutet i sin egen användarrymds-routingtabell, inte i kernens. En client-config-dir-post för utgången med namnet exit-node kan kräva standardprefixet där:
# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0
Den raden säger till serverprocessen "inre paket med vilken destination som helst tillhör den här klienten". Den lägger inte till en standardrutt i hubbens operativsystem och gör inte hubben till en internetgateway. OpenVPN 2.6-referens håller klientspecifik intern routing och kärnrouting som separata mekanismer, och den separationen är skälet till att designen fungerar utan att röra hubbens standardrutt.
Konsumenten behöver den spegelvända instruktionen, levererad som push, så att dess egen trafik går in i tunneln i stället för ut genom den lokala gatewayen. Med subnätstopologi är poolen en ansluten rutt på utgångens tun-enhet, så returriktningen behöver ingen extra push; det utgången inte får ta emot är omdirigeringen nedan, som skulle skicka dess egen uplänktrafik tillbaka in i tunneln den försöker använda. Skicka den bara till konsumenten och läs de tillämpade rutterna på klienten i stället för att lita på att CCD-filen valdes.
# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"
Utgångens kärna gör vidarebefordran
Inne i utgången anländer ett paket från konsumenten på tun-gränssnittet, adresserat till någon internetvärd. Kärnan slår upp sin routingtabell, hittar den verkliga uplänken som bästa träff och tillämpar sedan två oberoende policyer: får paketet vidarebefordras alls, och får det lämna det gränssnittet med den källadressen. Vidarebefordran är en sysctl, och den är avstängd som standard på många distributioner.
Så utgången behöver varaktigt påslagen IPv4-vidarebefordran, en regel som släpper in konsumentens tunnelkällor mot uplänken, och returvägen tillbaka genom tun. Att slå på vidarebefordran för hela värden ändrar också beteendet för allt annat som routas genom den, därför bör regeln namnge källorna och gränssnitten i stället för att acceptera allt.
Returvägen köps av källöversättning
En internetvärd har ingen rutt till en 10.90.0.x-adress, så svar skulle slängas innan de nådde utgången. Att skriva om källan när paketet lämnar uplänken löser det, och anslutningsspårning vänder tillbaka omskrivningen på returvägen.
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9 # run on the exit: must point at the tun
Regeln ovan är en utgångspunkt för ett uplänkgränssnitt, inte en brandmurspolicy. Begränsa källan till de tilldelningar du faktiskt auktoriserat, och håll utgångens egen ingångspolicy separat, så att maskering av tunneltrafik inte tyst exponerar lokala tjänster. På en värd med dynamisk uplänkadress sparar maskeringen dig också från att spåra adressen manuellt.
Var hubben kan upprätthålla och var den inte kan
Med client-to-client och den traditionella användarrymds-datavägen överlämnar hubben inre paket mellan peers inne i serverprocessen. De passerar aldrig hubbens kärna-FORWARD-kedja, så en kärnregel där kan inte begränsa peer-till-peer-transit, hur noggrant den än är skriven. Upprätthållandet måste leva i serverns egen policy: vem som får ansluta, vilken CCD-fil de får och om utgångens standardägarskap alls ges.
Därför är en standard-iroute ett brett förtroendebeslut. Utgången måste legitimt acceptera returtrafik från godtyckliga internetadresser, och den måste vara en maskin du kontrollerar, patchr och övervakar. Att ge samma ägarskap till en peer du inte skulle ge en router är felet den här designen bjuder in till.
Medlemskap är den verkliga kontrollen. Varje klient som kan konfigurera sina egna rutter kan försöka använda utgången, så att begränsa vem som får ansluta, eller vilka peers utgången ska betjäna, är en del av designen och inte en eftertanke.
Misslyckas säkert när utgången försvinner
Ta utgången offline och det interna standardägarskapet försvinner med dess session. Det som inte får hända är att hubben tyst blir ersättningsutgången: ingen standardrutt från VPN-poolen till hubbens uplänk, och ingen bred maskeringsregel på hubben som gärna skulle vidarebefordra samma trafik. Testa det felet med avsikt, och testa sedan återställningen med ett färskt flöde i stället för en sida som redan var öppen.
# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
Om konsumentens trafik fortfarande når internet efter att utgången är borta bär något annat den, och gränsen du avsåg finns inte. Behandla DNS och IPv6 också som separata beslut: en IPv4-rutt plus maskering säger ingenting om AAAA-trafik, och en resolver som hubben skjuter på svarar från där förfrågan faktiskt lämnar.