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.
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.138/32" ext1_gw="192.0.2.101" ext2_if="ng1" ext2_net="yyy.yyy.yyy.130/32" ext2_gw="192.0.2.102" set block-policy return scrub in all set block-policy return scrub in all 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 on $ext1_if from $int_net to any -> ($ext1_if) nat on $ext2_if from $int_net to any -> ($ext2_if) # Packets from this box will be generated with the source address of the default gateway. NAT them to their proper home. nat on tun0 from tun0 to any -> ($ext1_if) # Some test redirects rdr on $ext1_if proto tcp from any to xxx.xxx.xxx.133 port 22 -> 10.0.0.18 port 22 rdr on $ext2_if proto tcp from any to yyy.yyy.yyy.125 port 22 -> 10.0.0.18 port 22 #pass in log on $ext1_if keep state #pass in log on $ext2_if keep state # Load-balance LAN traffic pass in log on $int_if route-to { ($ext1_if $ext1_gw), ($ext2_if $ext2_gw) } round-robin from $int_net to !$int_if flags S/SA keep state # Don't load-balance traffic that's one-hop away from the public interfaces. Not useful for DSL/PPPoE. #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 where it's needed 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 anyways. #block out quick on $ext1_if from !$ext1_if to any #block out quick on $ext2_if from !$ext2_if to any # Pass/queue rules for the previous rdr rules pass in on $ext1_if proto tcp from any to 10.0.0.18 port 22 keep state queue (ssh, ack) pass in on $ext2_if proto tcp from any to 10.0.0.18 port 22 keep state queue (ssh, ack) # Anti-lockouts, cuz I'm doing this remotely. >@.@< 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 ) 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_checkrunning() { cnt=0 if [ -f "${dummygw_pid}" ] then pid=$(/bin/cat "${dummygw_pid}") cnt=$(/bin/ps -axocommand ${pid} | /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 xxx.xxx.xxx.139/32 10.44.44.44 /sbin/route delete -net default 2>/dev/null /sbin/route add -net default 10.44.44.44 /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}) fi /sbin/pfctl -Fstate } load_rc_config $name run_rc_command "$1"