DSL-Modem im Bridging-Modus betreiben

> Von wogri

Wir hören es fast jeden Tag in den Medien: Großflächige DDoS-Angriffe auf strategisch interessante Ziele im Internet. Bei DDoS-Angriffen haben Angreifer in der Regel private Geräte unter ihrer Kontrolle, ohne dass die Besitzer davon wissen.

Ein mögliches Angriffsziel ist hier der alte DSL-Router, dessen Hersteller schon Jahre kein Softwarupdate mehr ausliefert, und der vor Exploits nur so strotzt. Ein Angriff auf dieses Gerät bleibt unbemerkt, da es weiterhin seinen Dienst verrichtet und der Besitzer kein Fehlverhalten feststellen kann. Während einer DDoS-Attacke sendet das Gerät eine Sequenz von IP-Paketen, die dem privaten User wahrscheinlich nicht auffallen.

Dieser Artikel ist der erste Teil einer Serie aus vorerst zwei Artikeln[1]. Der vorliegende größere Teil beschreibt die Änderung der Router-Einstellungen und die Einrichtung eines Linux-Rechners mit Firewall. Der zweite Teil behandelt die Konfiguration von IPv6 mit DHCP und Firewall.

Wie kann ich mein DSL-Modem schützen?

Abgesehen von regelmäßigen Software-Updates gibt es noch eine mögliche Alternative: Das DSL-Modem vom Internet aus nicht ansprechbar zu machen. Das bedeutet, dass man dem Modem keine öffentliche IP-Adresse zuweist. Ohne öffentliche IP-Adresse ist jedoch keine Teilnahme am Internet möglich. Der Trick ist, das Modem in einen Bridging-Modus zu versetzen und die öffentliche IP-Adresse an einen Linux Software-Router zu vergeben.

Ein weiterer Vorteil dieses Modus ist, dass man frei aus den Netzwerkfähigkeiten von Linux schöpfen kann. VLANs, IPv6 Tunnel, Dynamisches DNS, IPSec, Split Horizon DNS – alles wird möglich. Es muss natürlich erwähnt werden, dass man damit eine Linux-Installation als Angriffsziel im Internet exponiert. Im Unterschied zu proprietären DSL-Boxen hat man hier jedoch selbst die Möglichkeit, das Gerät auf dem aktuellen Softwarestand zu halten, wie auch das Gerät mit Firewall-Regeln komplett von außen unzugänglich zu machen.

Bridging-Modus im DSL-Modem

Nicht jedes DSL-Modem unterstützt den Bridging-Modus. Um herauszufinden, ob ein DSL-Modem dazu fähig ist, muss man in der Konfigurationsoberfläche (oder im Manual) nachsehen. Bei meinem Zyxel-Modem war es glücklicherweise sehr einfach. Man kann ganz unkompliziert zwischen Routing und Bridging Mode umschalten.

Router-Konfiguration

WOLFGANG HENNERBICHLER

Router-Konfiguration

Wie man die jeweiligen DSL-Modems in den Bridging-Modus versetzt, ist im Handbuch der jeweiligen Hersteller nachzulesen. Nicht jedes Modem wird diesen Modus unterstützen.

Achtung: Sobald sich das Modem im Bridging-Modus befindet, ist es vorbei mit der Internet-Verbindung. Man möchte daher diesen Schritt daher gut vorbereiten und auf jeden Fall die Zugangsdaten zum DSL-Provider zur Hand haben, falls man doch wieder auf Routing umkonfigurieren möchte.

Was bedeutet Bridging-Modus?

Falls man bisher mit gebridgten Netzwerken nicht so viel zu tun hatte, ist der Bridging-Modus etwas schwer zu begreifen. Was hier passiert ist, dass das Modem etwaige PPPoE-Pakete direkt an die physische DSL-Schnittstelle weiterleitet. Das kann man mit einem WLAN Access Point vergleichen, der LAN-Pakete ins WLAN weiterleitet. Das Modem braucht nicht mal mehr eine IP-Adresse, um als DSL-Bridge zu fungieren. Ich empfehle dennoch, unbedingt eine lokale IP-Adresse auf dem LAN-Interface zu konfigurieren, damit das DSL-Modem nach wie vor über eine Administrations-Schnittstelle konfiguriert werden kann. Diese lokale IP-Adresse wird von der Linux Firewall geschützt und ist von außen nicht zugänglich, außer man befiehlt der Linux-Firewall explizit, Pakete an dieses Interface weiterzuleiten.

An welche IP-Adresse müssen PPPoE-Pakete geschickt werden?

Die genaue Erklärung findet man auf Wikipedia[2], die kurze Antwort hier: Man muss es nicht konfigurieren. PPPoE hat zwei Phasen. Zuerst die Discovery Phase (auch PADI genannt, steht für PPPoE Active Discovery Initiation) in der ein spezieller Ethernet Broadcast (an FF:FF:FF:FF:FF:FF) geschickt wird. Als Antwort erhält der Absender die MAC-Adresse des DSL-Modems. Ab diesem Zeitpunkt weiß der PPPoE-Client, an welche Ziel-MAC er die PPPoE-Pakete schicken soll.

Das Modem im Bridging-Modus nimmt die PPPoE-Pakete in Empfang und leitet sie an das DSL-Interface weiter. Aus diesem Grund braucht das DSL-Modem keine IP-Adresse im LAN, sobald es als Bridge funktioniert. Wie oben erwähnt empfehle ich dennoch, eine Management-IP auf das LAN-Interface des Modems zu konfigurieren.

Linux wird zum DSL-Router

Unterstützt das Modem den Bridging-Modus, braucht man noch einen Computer für die DSL-Einwahl. Ein Rasperry Pi mit Linux ist wahrscheinlich ausreichend dafür. Da der Pi seine Ethernet-Schnittstelle mit dem restlichen USB-Bus teilt, bevorzuge ich in meinem Beispiel eine virtuelle Maschine mit Debian Jessie, um die Netzwerk-Latenz für berufliche Videokonferenzen gering zu halten.

In diesem Tutorial probiere ich nicht nur, das Ding zum Laufen zu bringen, ich probiere auch, moderne Software dafür zu verwenden. Das war endlich ein Grund, mich zumindest etwas näher mit systemd zu beschäftigen und nftables zu verstehen.

systemd

Wie vorher erwähnt, verwende ich als PPPoE-Dialer und Firewall eine dezidierte virtuelle Maschine mit Debian. Um meine Skripte und Konfigurationsdateien etwas leserlicher zu gestalten, habe ich mir vorgenommen, die physischen Schnittstellen gleich vorneweg richtig zu benennen. Systemd hilft hier sehr, und es ist einfacher als jemals zuvor (ich denke hier an udev), physische Schnittstellen richtig zu benennen. Leider hat Debian Jessie noch nicht alle Features, die wir für unser Setup benötigen. Daher holen wir uns Systemd aus den Backports:

echo deb ftp.debian.org/debian jessie-backports main >> /etc/apt/sources.list
apt-get update
apt-get -t jessie-backports install "systemd"

Zuerst ist es notwendig, systemd-networkd zu aktivieren, um von den vielen Vorteilen der Netzwerkkonfiguration mit systemd zu profitieren: systemct enable systemd-networkd.service aktiviert den networkd. Um ein Interface namens lan als solches zu benennen, geht man nach /etc/systemd/network und erzeugt eine Datei namens 01-lan.link. Der Dateiname muss auf .link enden, und das 01 gibt die Reihenfolge in der Bootsequenz bekannt. Die Datei schaut bei mir folgendermaßen aus:

[Match]
MACAddress=52:54:00:c1:f8:1a
[Link]
Name=lan

Die IP-Adressen des Systems habe ich nicht altmodisch in /etc/network/interfaces konfiguriert, sondern auch mit systemd in der Datei /etc/systemd/network/lan.network:

[Match]
Name=lan
 
[Network]
Address=192.168.1.1/24
IPForward=yes

Dann selbiges auch noch mit dem Loopback-Interface in der Datei lo.network:

[Match]
Name=lo
 
[Network]
Address=127.0.0.1/8

PPPoE

PPPoE setzt ein paar Pakete voraus, die eventuell im System noch nicht vorinstalliert sind:

apt-get install pppd pppoeconf

Mit pppoeconf kann man die angesprochene Discovery anstoßen. Bevor wir die Discovery anstoßen, sollten wir aber die PPP-Parameter noch setzen. Zuerst tragen wir die Zugangsdaten zu unserem Provider in /etc/ppp/chap-secrets ein:

wogri@myprovider.com * my-secret-password *

Danach konfigurieren wir den pppd in /etc/ppp/peers/provider. Die Datei namens provider stellt den Default-Provider für pppd dar. Da die meisten User nur einen DSL-Provider haben, ist es praktisch diese Datei zu verwenden, da sonsten immer der Provider in den anschließenden Befehlen separat angegeben werden muss. Hier also die Konfiguration:

# Use Roaring Penguin's PPPoE implementation.
plugin rp-pppoe.so lan
 
# Login settigns.
user "wogri@myprovider.com"
noauth
hide-password
 
# Connection settings.
persist
maxfail 0
holdoff 5
 
# LCP settings.
lcp-echo-interval 10
lcp-echo-failure 3
 
# PPPoE compliant settings.
noaccomp
default-asyncmap
mtu 1492
 
# IP settings.
noipdefault
defaultroute

Man beachte hier die MTU von 1492. Bei Ethernet ist der Default 1500. Das wird im weiteren Verlauf der Konfiguration noch wichtig (Stichwort MSS Clamping).

Mit pon sollte man nun eine Verbindung zum ISP aufbauen können. ip address show ppp0 sollte die verhandelte IPv4-Konfiguration zeigen.

PPP-Verbindung beim Booten aufbauen

Natürlich müssen wir sicherstellen, dass bei einem Neustart die PPP-Verbindung wieder zuverlässig aufgebaut wird. Dafür müssen wir systemd mitteilen, dass wir eine neuen Service haben:

cat <<EOF >/etc/systemd/system/pppoe.service
[Unit]
Description=PPPoE connection
After=network-online.target
 
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/pon
ExecStop=/usr/bin/poff -a
 
[Install]
WantedBy=default.target
EOF
systemctl daemon-reload
systemctl enable pppoe.service
systemctl start pppoe.service

Routing

Das IPForward=yes in der systemd-networkd-Konfiguration erledigt das Setzen der bekannten ip_forwarding-Parameter, somit sollte ein manuelles Bearbeiten von sysctl.conf nicht notwendig sein.

Die Maschine sollte von nun an mit dem Internet verbunden sein. Was hier aber noch fehlt, ist das Masquerading. Da das interne Netzwerk mit privaten IP-Adressen betrieben wird, ist es notwendig, die private Quell-IP-Adresse der nach außen gerichteten Pakete zu verändern. Unter Linux wird diese Technik Masquerading genannt. Da dieses Tutorial neue Technologien verwendet, werde ich zuerst auf die Masquerading-Konfiguration von nftables eingehen, danach auch kurz iptables aufzeigen.

Firewall

Ziel dieses Tutorials ist es, die Angriffsfläche aufs Heimnetzwerk zu verkleinern. Daher konfigurieren wir eine Firewall, die per Default von außen keine Verbindungen erlaubt. Ich empfehle StrongSwan + IKEv2, sollte jemand das Bedürfnis haben, von außen auf sein Heimnetzwerk zugreifen zu wollen.

nftables

NFTables[3] ist die geplante Ablöse von IPTables. NFTables befindet sich bereits im Linux-Kernel seit Version 3.13. Ein schönes Tutorial über nftables[4] hat Martin Loschwitz im Linux-Magazin geschrieben. Mein Fazit zu nftables ist, dass es bereits ganz gut verwendbar ist, jedoch noch ein paar Features fehlen, bzw. teilweise die Syntax für mich nicht ganz verständlich ist. Fehlende Features für meine Anwendungsfälle sind:

  • mangelnde IPSec-Integration
  • teilweise inkonsistente Syntax der nft-Befehlszeile.
  • Spezial-Features wie MSS Clamping (mehr dazu später) sind noch nicht verfügbar.

Ich gehe davon aus, dass diese Probleme mit der Zeit gelöst werden.

Nun zur Installation: Das Userspace-Tool ist in Debian Jessie nur aus den Backports zu beziehen:

apt-get -t jessie-backports install "nftables"

Nftables kann man wie iptables mit einzelnen Befehlen betreiben oder aber über eine Datei, die atomar in den Kernel geladen (oder geändert) wird. Ich bevorzuge, eine Datei zu pflegen, die atomar in den Kernel geladen wird, ähnlich zum pf-System unter BSD. Eine weitere Eigenheit von nftables NAT ist, dass iptables NAT nicht geladen sein darf. Daher ist es erforderlich, das iptabes NAT-Modul zu entladen:

rmmod iptables_nat

Hier sieht man ein einfaches Beispiel, das nur ein Masquerading mit nftables macht und sinnvolle Chains für die weitere Verwendung angelegt hat:

#!/usr/sbin/nft -f
 
flush ruleset
 
table inet filter {
  chain input {
    type filter hook input priority 0;
    iifname lo accept
    iifname lan accept
    iifname ppp0 jump input_ppp0
    drop
  }
  chain input_ppp0 { # rules applicable to public interface
    ct state {established,related} counter accept
    ct state invalid counter drop
    log
    drop
  }
  chain ouput {
    type filter hook output priority 0;
    accept
  }
  chain forward {
    type filter hook forward priority 0;
    iifname ppp0 counter jump from_internet
    accept
  }
  chain from_internet {
    ct state {established,related} counter accept
    ct state invalid counter drop
    drop
  }
}
 
table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
  }
  chain postrouting {
    type nat hook postrouting priority 0;
    oifname ppp0 counter masquerade
  }
}

Mit diesem Regelwerk sollte die einfachste Variante von nft, nämlich ein simples NAT, erledigt sein. Wer ein Beispiel mit mehr Komplexität in seinen Regeln braucht, hier meine Konfiguration, inkl. IPv6:

#!/usr/sbin/nft -f
 
define server_net = 1.2.3.0/28
define my_phone = 192.168.1.100
 
# ipv6
define dovecot_ip6 = 2001:dead:beef:2::143
define server_net_ip6 = 2001:dead:beef::/64
 
flush ruleset
 
table inet filter {
  chain input {
    type filter hook input priority 0;
    iifname lo accept
    iifname lan accept
    iifname servlan accept
    iifname ipsec0 accept
    iifname ppp0 jump input_ppp0
    drop
  }
  chain input_ppp0 { # rules applicable to public interface
    ct state {established,related} counter accept
    ct state invalid counter drop
    ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate 10/second counter accept
    ip6 nexthdr icmpv6 icmpv6 type {
      destination-unreachable, packet-too-big, time-exceeded, 
      parameter-problem, nd-router-advert, nd-neighbor-solicit, 
      nd-neighbor-advert } counter accept
    ip protocol icmp icmp type echo-request limit rate 10/second counter accept
    ip6 daddr fe80::/64 udp dport dhcpv6-client counter accept
    ip6 saddr $server_net_ip6 tcp dport {22} counter accept
    # letsencrypt
    ip saddr 0.0.0.0/0 tcp dport {80,443} counter accept
    ip6 saddr ::/0 tcp dport {80,443} counter accept
    # ipsec
    ip protocol esp accept
    ip saddr 0.0.0.0/0 udp dport {500,4500} counter accept
    log
    drop
  }
  chain ouput {
    type filter hook output priority 0;
    accept
  }
  chain forward {
    type filter hook forward priority 0;
    iifname ppp0 counter jump from_internet
  }
  chain from_internet {
    ct state {established,related} counter accept
    ct state invalid counter drop
    ip6 daddr $dovecot_ip6 jump to_dovecot
    log
    drop
  }
  chain to_dovecot {
    ip6 saddr $server_net_ip6 tcp dport {22} counter accept
  }
}
 
table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
    iifname ppp0 counter jump dnat_from_internet 
  }
  chain dnat_from_internet {
    udp dport { sip, 16384-16400 } counter dnat $my_phone
  }
  chain postrouting {
    type nat hook postrouting priority 0;
    oifname ppp0 counter masquerade
  }
}

iptables

Wer sich nftables nicht antun möchte, kann das simple NAT mit IPv4 mit iptables folgendermaßen einrichten:

#!/bin/bash                                                                         
                                                                                    
iptables --flush                                                                    
iptables -t nat --flush                                                             
iptables -X                                                                         
                                                                                    
set -x                                                                              
iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE                                
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu 
iptables -A FORWARD -i ppp0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i ppp0 -m state --state RELATED,ESTABLISHED -j ACCEPT            
iptables -A INPUT -i ppp0 -j LOG                                                    
iptables -A INPUT -i ppp0 -j DROP                                                   
set +x                                                                              

Firewall-Skript automatisch laden

Egal ob man iptables oder nftables verwendet, man muss sicherstellen, dass die Regeln geladen werden, wenn das PPP-Interface hochkommt. Das erledigt man am einfachsten, indem man eine ausführbare Datei nach /etc/ppp/ip-up.d legt. Bei mir sieht der Inhalt folgendermaßen aus:

#!/bin/bash
nft -f /etc/nftables.conf
iptables --flush
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Ich habe an dieser Stelle nicht überprüft, wann dieses Skript ausgeführt wird – theoretisch könnte es einen kurzen Zeitpunkt geben, wo das PPP-Interface bereits von außen erreichbar, aber die Firewall noch nicht geladen worden ist (oder aufgrund eines Syntax-Fehlers in der Datei die Firewall gar nicht aktiviert wird). Da auf meiner Firewall außer SSH keine Internet-Dienste laufen, mache ich mir darüber keine weiteren Sorgen.

MSS Clamping

Eingangs habe ich erwähnt, dass das PPPoE-Protokoll zum Einsatz kommt. Aus diesem Grund muss man bei der gesetzten MTU besonders aufpassen. PPP setzt die MTU mit dem ISP auf 1492. Ethernet hat eine MTU von 1500. Da vor das PPP-Paket mit 1492 Bytes noch ein 8 Byte Header kommt, kommuniziert der pppd mit dem ISP mit 1500 Bytes.

Wird ein TCP Paket jedoch im Ethernet (das annimmt, 1500 Byte als MTU im gesamten Pfad bis ins Internet zu haben) mit einer Paketgröße von 1500 Bytes versendet, muss unser PPP-Router das Paket fragmentieren.

Hier kommt eine Technik namens Maximum Segment Size Clamping zum Einsatz. Kurzum, mit dem Befehl

iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

wird der Header in TCP-Paketen derart verändert, dass kein TCP-Paket größer als 1492 von innen oder außen gesendet wird. Das kann für einige Provider erforderlich sein. Manche ISPs erlauben jedoch MTUs, die größer als 1500 Bytes sind, dann wird das MSS Clamping nicht benötigt. Für IPv6 ist kein MSS Clamping notwendig, da IPv6 die MTU im gesamten Routing-Pfad ermittelt.

Über den Autor

Wolfgang Hennebichler (Webseite[5]) ist Site Reliability Engineer bei Google.

Linkverweise:


[1] www.pro-linux.de/artikel/1/73/dsl-modem-im-bridging-modus-betreiben.html 
[2] de.wikipedia.org/wiki/PPP_over_Ethernet 
[3] www.pro-linux.de/news/1/21853/netfilter-projekt-veröffentlicht-nftables-04.html 
[4] www.linux-magazin.de/Ausgaben/2014/01/NFtables 
[5] www.wogri.at/

http://www.pro-linux.de/images/NB3/base/print/nb3_logo.png

Mediadaten RSS/Feeds Datenschutz Impressum

© 2017 Pro-Linux

 

Schreibe einen Kommentar