Work In Progress!
Create a multi-homed router, based off FreeBSD 6.x
- Support load-balancing NAT outgoing connections across all four routers
- Support proper routing of incoming connections
- No ISP support available (No bundling/multilink, no BGP, etc..)
- PacketFilter preferred
Problems:
- PacketFilter can NAT outgoing connections properly, but incoming connections always use default gateway.
- Statefulness means egress packets don't touch the gateway selection rules in PF
- PF is 'closer' to the device than IPFW, so IPFW only sees untranslated egress packets
Need some way to reinject translated packets, so that they can hit the gateway selection rules.
- All of the above fixed with dummygw (see below)
FTP definitely refuses to work, except for passive FTP on a single-IP binatted private machine.
- Must be bound to a single IP
- Occasional, hard to trace hangs in certain flows (for instance, microsoft.com's homepage)
- Totally doesn't handle single-link failure. Failover script must be crafted.
- DNS works to everywhere except ISP's DNS servers. Why?
- Too much traffic causes, almost immediately, 'No buffer space available'
MPD - Multilink PPP Daemon
- Easy to set up under FreeBSD
- Supports multilink in case ISP gets heads out of asses
- Uses netgraph nodes. All encapsulation done in kernelspace, so it's faster (no context switches)
Must use custom up-script to ensure:
- All six static IPs are added to the node
- None of the added IPs peers are the same (routing error)
- We can specify any arbitrary peer gateway, as it is merely an identifier to pick the appropriate link and doesn't affect the traffic routing.
mpd.conf:
default: set login admin load sbcdsl_common load sbcdsl1 load sbcdsl2 sbcdsl_common: set iface disable on-demand set iface idle 0 set bundle enable multilink set link no acfcomp protocomp set link disable pap chap set link accept chap set link mtu 1492 set link keep-alive 10 60 set link ipcp yes vjcomp set ipcp ranges 0.0.0.0/0 0.0.0.0/0 set iface up-script /usr/local/etc/mpd/mpd.linkup sbcdsl1: new -i ng0 sbcdsl1 sbc1 set iface addrs 10.0.0.1 10.0.0.2 set bundle authname "blah1@sbcglobal.net" set iface up-script "/usr/local/etc/mpd/mpd.linkup" open iface sbcdsl2: new -i ng1 sbcdsl2 sbc2 set iface addrs 10.0.0.3 10.0.0.4 set bundle authname "blah2@sbcglobal.net" set iface up-script "/usr/local/etc/mpd/mpd.linkup" open iface
mpd.links:
sbc1: set link type pppoe set pppoe iface sis0 set pppoe service "ISP" set pppoe disable incoming set pppoe enable originate sbc2: set link type pppoe set pppoe iface sis1 set pppoe service "ISP" set pppoe disable incoming set pppoe enable originate
mpd.secret:
admin adminpass blah1@sbcglobal.net pass4blah1 blah2@sbcglobal.net pass4blah2
mpd.linkup:
#!/bin/sh # ng0 inet xxx.xxx.xxx.130 192.0.2.100 blah1@sbcglobal.net echo "${*}" >> /tmp/mpd.linkup eval $(echo ${1} | sed -e 's/^ng\(.*\)$/export metric="\1"/'); eval $(echo ${3} | sed -e 's/^\([^.]*\.[^.]*\.[^.]*\.\)\(.*\)$/export pub_pref="\1" pub_suf="\2"/') eval $(echo ${4} | sed -e 's/^\([^.]*\.[^.]*\.[^.]*\.\)\(.*\)$/export her_pref="\1" her_suf="\2"/') pub_suf=$(expr ${puf_suf} + 1) idx=0 alias='' while [ "${idx}" -lt 8 ] do pub_idx=$(expr ${pub_suf} - ${idx}) pub_addr="${pub_pref}${pub_idx}" her_idx=$(expr ${her_suf} + ${idx} + \( ${metric} \* 10 \) + 1) her_addr="${her_pref}${her_idx}" echo "/sbin/ifconfig ${1} ${alias} ${pub_addr} ${her_addr} netmask 0xffffffff" >> /tmp/mpd.linkup /sbin/ifconfig ${1} ${alias} ${pub_addr} ${her_addr} netmask 0xffffffff idx=$(expr ${idx} + 1) [ -z "${alias}" ] && alias="alias" done
PacketFilter
int_if="fxp0" int_net="10.0.0.0/24" ext1_if="ng0" ext1_net="xxx.xxx.xxx.137/32" ext1_gw="192.0.2.101" ext2_if="ng1" ext2_net="yyy.yyy.yyy.127/32" ext2_gw="192.0.2.111" dnsserv="{ 68.94.156.1/32, 68.94.157.1/32 }" # Don't simply block packets. This makes debugging rulesets -much- easier. set block-policy return #set state-policy if-bound scrub in all # AltQ queues altq on $ext1_if bandwidth 750Kb cbq queue { ack, ssh, dflt, bulk, down } altq on $ext2_if bandwidth 750Kb cbq queue { ack, ssh, dflt, bulk, down } queue ack bandwidth 32Kb priority 7 cbq(rio, borrow) queue ssh bandwidth 128Kb priority 5 cbq(rio, borrow) queue dflt bandwidth 8Kb priority 4 cbq(rio, borrow, default) queue bulk bandwidth 8Kb priority 2 cbq(rio, borrow) queue down bandwidth 8Kb priority 0 cbq(rio, borrow) # nat/binat: First match wins # GINKO1 binat on $ext1_if from 10.0.0.2 to any -> xxx.xxx.xxx.135 binat on $ext2_if from 10.0.0.2 to any -> yyy.yyy.yyy.125 # Cobolt binat on $ext1_if from 10.0.0.3 to any -> xxx.xxx.xxx.136 binat on $ext2_if from 10.0.0.3 to any -> yyy.yyy.yyy.126 # Haverty binat on $ext1_if from 10.0.0.18 to any -> xxx.xxx.xxx.132 # Riboflavin binat on $ext1_if from 10.0.0.22 to any -> xxx.xxx.xxx.133 # Potassium binat on $ext2_if from 10.0.0.23 to any -> yyy.yyy.yyy.131 # NAT rules nat on $ext1_if from $int_net to any -> $ext1_net nat on $ext2_if from $int_net to any -> $ext2_net nat on tun0 from tun0 to any -> $ext1_net # Forward packets to inner machines # GINKO1 rdr on $ext1_if from any to xxx.xxx.xxx.135 -> 10.0.0.2 rdr on $ext2_if from any to yyy.yyy.yyy.125 -> 10.0.0.2 # Cobolt rdr on $ext1_if from any to xxx.xxx.xxx.136 -> 10.0.0.3 rdr on $ext2_if from any to yyy.yyy.yyy.126 -> 10.0.0.3 # Haverty rdr on $ext1_if from any to xxx.xxx.xxx.132 -> 10.0.0.18 # Riboflavin rdr on $ext1_if from any to xxx.xxx.xxx.133 -> 10.0.0.22 # Potassium rdr on $ext2_if from any to yyy.yyy.yyy.131 -> 10.0.0.23 # Tungsten rdr on $ext1_if proto { tcp, udp } from any to $ext1_net port 40585 -> 10.0.0.8 port 40585 rdr on $ext2_if proto { tcp, udp } from any to $ext2_net port 40585 -> 10.0.0.8 port 42585 # Redirect FTP connections to ftp proxy running on localhost #rdr on $int_if proto tcp from any to !$int_if port 21 -> 127.0.0.1 port 8021 # pass/block: Last match wins block in on $ext1_if all block in on $ext2_if all # Block SMB/CIFS traffic block log quick on { $ext1_if, $ext2_if } proto udp from any to any port { 137, 138, 139, 445 } pass in log quick on $ext1_if from $dnsserv to any keep state queue (ack) pass in log quick on $ext2_if from $dnsserv to any keep state queue (ack) # Prioritize VoIP address pass in on $int_if proto { tcp, udp } from 10.0.0.210 to any keep state tag VOIP # Deprioritize Tungsten pass in on $int_if proto { tcp, udp } from 10.0.0.8 to any keep state tag BULK # Local SSH pass in on { $ext1_if, $ext2_if } proto tcp from any to { xxx.xxx.xxx.137/32, yyy.yyy.yyy.127/32 } port 22 keep state queue (ssh, ack) # Load-balance LAN traffic pass in log on $int_if route-to { (ng0 192.0.2.103), (ng1 192.0.2.115) } round-robin from $int_net to !$int_net flags S/SA keep state # Load-balance GINKO1 pass in log on $int_if route-to { (ng0 192.0.2.105), (ng1 192.0.2.117) } round-robin from 10.0.0.2 to !$int_net flags S/SA keep state # Load-balance Cobolt pass in log on $int_if route-to { (ng0 192.0.2.104), (ng1 192.0.2.116) } round-robin from 10.0.0.3 to !$int_net flags S/SA keep state # Haverty pass in log on $int_if route-to (ng0 192.0.2.108) from 10.0.0.18 to !$int_net flags S/SA keep state # Riboflavin pass in log on $int_if route-to (ng0 192.0.2.107) from 10.0.0.22 to !$int_net flags S/SA keep state # Potassium pass in log on $int_if route-to (ng1 192.0.2.111) from 10.0.0.23 to !$int_net flags S/SA keep state # Make sure next-hop traffic on NATed gateways goes out the right interface. Not needed for PtP links #pass in on $int_if route-to { ($ext1_if $ext1_gw) } round-robin from $int_net to $ext1_net keep state #pass in on $int_if route-to { ($ext2_if $ext2_gw) } round-robin from $int_net to $ext2_net keep state # Make sure traffic goes out the interface corresponding to its' source address pass in log on tun0 route-to ($ext1_if $ext1_gw) from $ext1_if to any pass in log on tun0 route-to ($ext2_if $ext2_gw) from $ext2_if to any pass out log on $ext1_if route-to ($ext2_if $ext2_gw) from $ext2_if to any pass out log on $ext2_if route-to ($ext1_if $ext1_gw) from $ext1_if to any # Block inappropriate traffic. The ISP does this already, but there's no sense wasting bandwidth #block out log quick on $ext1_if from !$ext1_if to any #block out log quick on $ext2_if from !$ext2_if to any # Pass previously NATed connections # GINKO1 pass in log on { $ext1_if, $ext2_if } from any to 10.0.0.2 keep state queue (dflt, ack) # Cobolt pass in log on { $ext1_if, $ext2_if } from any to 10.0.0.3 keep state queue (dflt, ack) # Haverty pass in log on $ext1_if from any to 10.0.0.18 keep state queue (dflt, ack) # Riboflavin pass in log on $ext1_if from any to 10.0.0.22 keep state queue (dflt, ack) # Potassium pass in log on $ext2_if from any to 10.0.0.23 keep state queue (dflt, ack) # Tungsten pass in log on { $ext1_if, $ext2_if } proto { tcp, udp } from any to 10.0.0.8 port 40585 keep state queue (bulk, ack) # FTP Proxy connections #pass log on $ext1_if proto tcp from any to any user = proxy keep state queue (bulk, ack) #pass in log on $int_if route-to (lo0 127.0.0.1) proto tcp from any to 127.0.0.1 port 8021 keep state # Prioritize VoIP pass out on $ext1_if tagged VOIP keep state queue (ssh, ack) pass out on $ext2_if tagged VOIP keep state queue (ssh, ack) # Deprioritize Tungsten pass out on $ext1_if tagged BULK keep state queue (bulk, ack) pass out on $ext2_if tagged BULK keep state queue (bulk, ack) # Bind AIM to a single IP pass in on $int_if route-to (ng0 192.0.2.103) proto tcp from any to any port 5190 keep state # Pass all LAN traffic unconditionally, so we don't accidentally lock ourselves out. pass in on fxp0 from any to fxp0 pass out on fxp0 from fxp0 to any
Injector
A method to reinject packets headed for the default route, so that they can be routed to the proper gateway
- tun(4) device acts as the default gateway for the machine
- Any packets it receives must have a source address of one of the public interfaces.
- Read them, perform a sanity check to make sure their source nor destination address matches that interface, then blindly reinject them
- Hopefully, this will pass them through the firewall rules again, statelessly, so that the gateway redirection rules can affect them and send them to the proper interface.
- Starts on tun0. You must ifconfig tun0 up xxx.xxx.xxx.139/32 10.1.1.2 and route add -net default 10.1.1.2 to direct all 'default' packets to this device, so they can be reinjected and hit the gateway redirect rules in pf.
- tun0 should have a valid source address, as it will be used as the source address for all publicly routed packets coming from the machine itself (DNS lookups, etc..)
tunbounce.c:
#include <errno.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <net/if.h> int main(int argc, char **argv) { static int sockfd = -1; static int iffd = -1; static char pkt[1500]; static size_t pkt_len = 0; static size_t pkt_len2 = 0; static struct ifreq iff; static struct ifreq my_iff; static int status = -1; /* Acquire tunnel device */ if ((sockfd = open("/dev/tun0", O_RDWR|O_DIRECT)) < 0) { perror("open(/dev/tun0)"); return 32; } /* Set device up. This is easier than all that mess with sysctls and ioctls */ if (vfork() == 0) { /* Child */ if (execl("/sbin/ifconfig", "ifconfig", "tun0", "up", (char *)NULL) < 0) { perror("execl(/sbin/ifconfig tun0 up)"); _exit(32); } } wait(&status); if (WIFEXITED(status) && WEXITSTATUS(status) == 32) { printf("Exiting."); return 32; } while (1) { if ((pkt_len = read(sockfd, &pkt, 1500)) == -1) { perror("read failed"); continue; } if ((pkt_len2 = write(sockfd, pkt, pkt_len)) == -1) { perror("write failed"); continue; }else if (pkt_len != pkt_len2) { printf("Partial write: %u of %u\n", pkt_len2, pkt_len); continue; } } return 0; }
tunbounce.c v0.2: (Doesn't work!)
#include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/ioctl.h> #include <net/if.h> int main(int argc, char **argv) { static int sockfd = -1; static int iffd = -1; static char pkt[1500]; static size_t pkt_len = 0; static size_t pkt_len2 = 0; static struct ifreq iff; static struct ifreq my_iff; if ((sockfd = open("/dev/tun0", O_RDWR|O_DIRECT)) < 0) { perror("open(/dev/tun0)"); return 32; } if ((iffd = open("/dev/net/tun0", O_RDWR|O_DIRECT)) < 0) { perror("open(/dev/net/tun)"); return 32; } if (ioctl(iffd, SIOCGIFFLAGS, &iff) < 0) { perror("ioctl (SIOCGIFFLAGS)"); return 32; } my_iff.ifr_flags = iff.ifr_flags | IFF_UP; if (ioctl(iffd, SIOCSIFFLAGS, my_iff) < 0) { perror("ioctl (SIOCSIFFLAGS)"); return 32; } printf("%u\n", sockfd); while (1) { if ((pkt_len = read(sockfd, &pkt, 1500)) == -1) { perror("read failed"); continue; } if ((pkt_len2 = write(sockfd, pkt, pkt_len)) == -1) { perror("write failed"); continue; }else if (pkt_len != pkt_len2) { printf("Partial write: %u of %u\n", pkt_len2, pkt_len); continue; } } return 0; }
Script to run the above and make the appropriate changes (cuz doing that in C is way too annoying x.x ) in rcng format dummygw:
#!/bin/sh # # PROVIDE: dummygw # REQUIRE: NETWORKING routing mpd # KEYWORD: nojail . /etc/rc.subr name="dummygw" start_cmd="dummygw_start" stop_cmd="dummygw_stop" dummygw_bin=${tunbounce_bin:-/usr/home/cyberleo/tunbounce} dummygw_pid=${tunbounce_pid:-/var/run/dummygw.pid} dummygw_enable=${tunbounce_enable:-NO} dummygw_addr=${dummygw_addr:-xxx.xxx.xxx.139} dummygw_peer=${dummygw_peer:-10.44.44.44} dummygw_checkrunning() { cnt=0 if [ -f "${dummygw_pid}" ] then pid=$(/bin/cat "${dummygw_pid}") cnt=$(/bin/ps -axocommand "${pid:-0}" | /usr/bin/grep -c "$(/usr/bin/basename "${dummygw_bin}")") fi if [ "${cnt}" -eq 0 ] then return 1 fi return 0 } dummygw_start() { echo "Establishing bouncing default gateway..." if dummygw_checkrunning then echo "Already running." exit 64 fi $dummygw_bin & echo ${!} > $dummygw_pid sleep 1 if dummygw_checkrunning then /sbin/ifconfig tun0 "${dummygw_addr}/32" "${dummygw_peer}" /sbin/route delete -net default 2>/dev/null /sbin/route add -net default "${dummygw_peer}" /sbin/pfctl -Fstate else echo "Startup failed." fi } dummygw_stop() { echo "Destroying bouncing default gateway..." /sbin/route delete -net default 2>/dev/null /sbin/route add -net default ${defaultrouter} /sbin/ifconfig tun0 down 0.0.0.0 2>/dev/null if [ -f "${dummygw_pid}" -a -n "$(cat "${dummygw_pid}")" ] then kill -TERM $(cat "${dummygw_pid}") rm -f "${dummygw_pid}" fi /sbin/pfctl -Fstate } load_rc_config $name run_rc_command "$1"