2 uplinks with bandwidth management, load splitting and fail-over. Working

From: G Bryant (bsd_at_roamingsolutions.net)
Date: 10/06/05

  • Next message: Jeremie Le Hen: "Re: IPFW+DUMMYNET UPLOAD PROBLEM"
    Date: Thu, 06 Oct 2005 10:18:45 +0200
    To: FreeBSD <freebsd-ipfw@freebsd.org>, FreeBSD <freebsd-net@freebsd.org>
    
    

    Greetings,
    2 uplinks with bandwidth management, load splitting and fail-over. Working
    For those interested in an alternate method of doing the same thing -
    here are some basics.
    You could probably modify the script rules and sets to do crude load
    balancing between the lines too - but more of that later.

    The Setup:
    FreeBSD 5.4 - Stable
    ipfw, natd, natd2, ng_one2many, squid, jftpgw.
    2 dsl links to the internet.
    3 network cards ( 1 per dsl modem, 1 for lan)

    Learning curve:
    After much reading and testing, I found that the way to use both dsl
    lines was to use ipfw prob, ipfw fwd, and divert to seperate natd 's.
    Basically:
    a natd with alias_address associated with each of the boxes external ip's.

    ipfw rules as follows:
    #------------------------
    route add default $ext_gw1
    <--snip-->
    ipfw add allow ip from any to any via $int_if

    ipfw add divert natd1 ip from any to $ext_ip1 in
    ipfw add divert natd2 ip from any to $ext_ip2 in

    # Simple version
    ipfw add skipto 8000 ip from $lan to any out
    ipfw add allow ip from any to $lan in

    ipfw add 8000 prob 0.5 skipto 8500 ip from any to any out
    ipfw add 8100 divert natd1 ip from any to any out
    ipfw add 8200 allow ip from $ext_ip1 to any out
    ipfw add 8500 divert natd2 ip from any to any out
    ipfw add 8600 fwd $ext_gw2 ip from $ext_ip2 to any out

    ipfw add deny ip from any to any out
    #------------------------

    This didn't work for 2 reasons.
    Packets exiting with a fwd command to ext_gw2 didn't get to exit the
    correct interface as routed had already set them up to exit via the
    default route.
    Second reason was that tcp sessions really like all sequenced packets,
    and ack replies to come from the same source it started talking with
    initially.
    The keep-state unfortunately only creates a dynamic rule using ip and
    port for source and destination, and the interface. This does not help
    as I would like the rule to remember which packets to skipto 8500 (same
    as before) - but the keep-state (dynamic rule for that session) seems to
    think it's a free-for-all and only sees it as an allow rule, to send
    the packets directly out. This doesn't help as the packets have not yet
    been natted, and so exit with a private source ip (up, up and away -
    never to be seen again).

    Current (dirty) working Solution:
    rc.conf: (relevant entries)

    hostname="fw.xx.yy.zz"
    # Configure the internal network
    ifconfig_vr0="inet 192.168.1.1 netmask 255.255.255.0"
    # Configure the external networks (connected to the internet)
    ifconfig_rl0="inet 192.168.8.70 netmask 255.255.255.0"
    ifconfig_rl0_alias0="inet 192.168.0.99 netmask 255.255.255.0"
    defaultrouter="192.168.8.1"

    # - Enabling the FreeBSD Firewall
    gateway_enable="YES"
    firewall_enable="YES"
    firewall_script="/etc/ipfw.rules"
    firewall_logging="YES"

    # Enabling natd for the 2 external interfaces
    natd_enable="YES"
    natd_flags="-f /etc/natd1.conf"
    # Remember to specify the natd2 port in /etc/services
    # To start this, the easiest way is to cp /etc/rc.d/natd
    /usr/local/etc/rc.d/natd2.sh and then edit it.
    natd2_enable="YES"
    natd2_flags="-f /etc/natd2.conf"

    #Enable the proxy server
    squid_enable="YES"

    # Sync server time from internet
    ntpd_enable="YES"
    ntpd_flags="-c /etc/ntp.conf"

    # Bandwidth monitoring with html graphs
    bandwidthd_enable="YES"

    # jftpgw ftp proxy for anonymous ftp proxy-cache
    jftpgw_enable="YES"

    # Load the script to hook the two external nic's together
    # Add the actual script file to /usr/local/etc/rc.d/netmon1.sh
    netmon1_enable="YES"
     
    I found the fwd command in the above ipfw rules wasn't working for me -
    even tried different network cards, but the packets still didn't exit
    the correct interface correctly. So I decided to try force them out
    whether they liked it or not. (Hence the dirty part - as you will see)
    Used ng_one2many to hookboth external nic's onto the first one, and
    configured it for "transmit all." i.e. all packets leaving interface0
    get forced out of interface1 as well.
    Just ran a script at startup to get this setup:
    (Yes I know it can be done a lot better - for now it works!!!)

    #----------------------------
    #!/bin/sh
    # Load the kernel modules
    kldload ng_ether
    kldload ng_one2many

    ifconfig rl0 down
    ifconfig rl1 down
    # Plumb nodes together
    ngctl mkpeer rl0: one2many upper one
    ngctl connect rl0: rl0:upper lower many0
    ngctl connect rl1: rl0:upper lower many1

    # Allow rl1 to xmit / recv rl0 frames
    ifconfig rl1 promisc
    ngctl msg rl1: setautosrc 0
    # Configure to transmit to all interfaces
    ngctl msg rl0:upper setconfig "{xmitAlg=2 failAlg=1 enabledLinks =[ 1 1 ] }"

    echo "Now up the interfaces again"
    ifconfig rl0 up
    ifconfig rl1 up
    ifconfig rl0 inet 192.168.8.70 netmask 255.255.255.0
    ifconfig rl0 inet 192.168.0.99 netmask 255.255.255.0 alias
    Make sure the default route is correct.
    route delete default
    route add default 192.168.0.1
    echo "Done"
    #----------------------------

    Weird thing was that using default route 192.168.8.1 (via the interface
    directly linked to $ext_if1) didn't work.
    It was only when I tried with the default route set to $ext_gw2 that
    everything started working.
    Still figuring that one, but reckon it was prayers that give God the
    honours here.

    ipfw rules:

    #!/bin/sh
    ################ Start of IPFW rules file ###############################
    # Flush out the list before we begin.
    ipfw -q -f flush

    # Set rules command prefix
    cmd="ipfw -q add"
    bwm="ipfw -q pipe"
    skip="skipto 8000"
    ext_if1="rl0" # public interface name of NIC
    ext_if2="rl0"
    lan="192.168.1.0/24"
    int_if="vr0" # private interface name of NIC
    ext_ip1="192.168.8.70"
    ext_ip2="192.168.0.99"
    ext_gw1="192.168.8.1"
    ext_gw2="192.168.0.1"

    # Setup the different Sets to be used for different connection options
    ipfw -q set disable 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30 31
    # Initially enable set 1 and 2 and 12 assuming we have 2 WAN links up
    and working
    ipfw -q set enable 1 2 12

    # Specify which ip addresses get what bandwidth
    # Can also tell this dhcp server to give certain addresses to selected mac
    # addresses in file /usr/local/etc/dhcpd.conf
    u512k="" # Users given 512kb/s link
    u256k="192.168.1.0/24{2-254}" # Users given 256kb/s link
    u128k="" # Users given 128kb/s link
    u64k="" # Users given 64kb/s link
    # squid and jftpgw have to be configured seperately to provide the same
    # bandwidth management as what is configured here. See their config/man
    pages.

    # Check and drop packets that are appearing to come from
    # the destination LAN i.e. a spoofed source ip address
    $cmd deny ip from any to any not antispoof in

    # No restrictions on Loopback Interface
    # Protect spoofing to localhost
    $cmd allow ip from any to any via lo0
    $cmd deny ip from any to 127.0.0.0/8
    $cmd deny ip from 127.0.0.0/8 to any

    # check if packet is inbound and nat address if it is
    $cmd 1000 divert natd1 ip from any to $ext_ip1 in
    $cmd 1000 divert natd2 ip from any to $ext_ip2 in

    # Divert incoming http and ftp traffic to the proxy (squid and jftpgw)
    $cmd fwd 192.168.1.1,3128 tcp from $lan to any 80 in via $int_if
    $cmd fwd 192.168.1.1,2370 tcp from $lan to any 21 via $int_if

    # Allow the rest of the LAN traffic in and out
    $cmd allow ip from any to any via $int_if

    ################ Bandwidth Management ############################
    # Setup up pipes for each of the user groups

    # Users with 512Kb / 256Kb access (in / out)
    $cmd pipe 10 ip from any to $u512k in via $ext_if1
    $cmd pipe 11 ip from $u512k to any out via $ext_if1
    $bwm 10 config mask dst-ip 0x000000ff bw 512Kbit/s queue 4KBytes
    $bwm 11 config mask src-ip 0x000000ff bw 256Kbit/s queue 3KBytes

    # Users with 256Kb / 128Kb access
    $cmd pipe 20 ip from any to $u256k in via $ext_if1
    $cmd pipe 21 ip from $u256k to any out via $ext_if1
    $bwm 20 config mask dst-ip 0x000000ff bw 256Kbit/s queue 4KBytes
    $bwm 21 config mask src-ip 0x000000ff bw 128Kbit/s queue 3KBytes

    # Users with 128Kb / 64Kb access
    $cmd pipe 30 ip from any to $u128k in via $ext_if1
    $cmd pipe 31 ip from $u128k to any out via $ext_if1
    $bwm 30 config mask dst-ip 0x000000ff bw 128Kbit/s queue 4KBytes
    $bwm 31 config mask src-ip 0x000000ff bw 64Kbit/s queue 3KBytes

    # Users with 64Kb / 56Kb access
    $cmd pipe 40 ip from any to $u64k in via $ext_if1
    $cmd pipe 41 ip from $u64k to any out via $ext_if1
    $bwm 40 config mask dst-ip 0x000000ff bw 64Kbit/s queue 3KBytes
    $bwm 41 config mask src-ip 0x000000ff bw 56Kbit/s queue 2KBytes

    #################################################################
    # Interface facing Public Internet (Outbound Section)

    # Allow out access to my ISP's Domain name server.
    # Get the IP addresses from /etc/resolv.conf file
    #$cmd $skip UDP from any to { 196.7.0.138 or 196.28.86.2 or 196.28.86.3
    or 196.25.1.1 } 53 out
    $cmd $skip UDP from any to any 53 out

    # Allow this box out access to my ISP's DHCP server (or adsl router)
    $cmd $skip udp from me to any 67 out

    # Allow skype connections out
    # Allow ntp time server out
    $cmd $skip UDP from any to any 80,443,123,1024-65535 out
    $cmd $skip UDP from any 80,443,1024-65535 to any out
    $cmd $skip tcp from any 1024-65535 to any 1024-65535 out

    # Allow out non-secure standard www function - via proxy
    $cmd $skip tcp from me to any 80

    # Allow out secure www function https over TLS SSL
    # Allow out send & get email function (GMail uses ports 587, 995)
    # Allow out MSN messenger
    # Allow out Time, nntp news (i.e. news groups),
    # SSH (secure FTP, Telnet, and SCP), whois
    $cmd $skip tcp from any to any 443,25,110,587,995,1863,37,119,22,43 out

    # Allow out regular http and ftp access (for if proxy and fwd cmd's
    above are off)
    $cmd $skip tcp from $lan 1024-65535 to any 20,21,80 out

    # Allow out ping
    $cmd $skip icmp from $lan to any out icmptypes 8
    $cmd allow icmp from me to 192.168.0.0/16 out icmptypes 8
    $cmd allow icmp from $ext_ip1,$ext_ip2 to any out icmptypes 8

    # Allow www and ftp proxy out
    $cmd $skip tcp from me to any 20,21,80 out uid squid

    # Allow out FreeBSD (make install & CVSUP) functions
    # Give user root "GOD" privileges.
    $cmd allow ip from me to any out uid root

    # Deny the rest out
    $cmd deny log ip from any to any out

    #################################################################
    # Interface facing Public Internet (Inbound Section)
    # Interrogate packets originating from the public Internet
    # destine for this gateway server or the private network.

    # Deny all inbound traffic from non-routable reserved address spaces
    #$cmd 300 deny all from 192.168.0.0/16 to any in via $ext_if1 #RFC
    1918 private IP
    $cmd deny all from
    172.16.0.0/12,10.0.0.0/8,0.0.0.0/8,169.254.0.0/16,192.0.2.0/24,204.152.64.0/23,224.0.0.0/3
    to any in
    #RFC 1918 private IP #DHCP auto-config #reserved for docs #Sun cluster
    #Class D & E multicast

    # Deny ident
    # Deny all Netbios service. 137=name, 138=datagram, 139=session
    # Netbios is MS/Windows sharing services.
    # Block MS/Windows hosts2 name server requests 81
    $cmd deny all from any to any 113,137,138,139,81 in

    # Allow traffic in from ISP's DHCP server. This rule must contain
    # the IP address of your ISP's DHCP server as it's the only
    # authorized source to send this packet type.
    $cmd allow udp from 192.168.8.1,192.168.0.1 to any 68,5678 in

    # Allow dns lookups back in
    $cmd allow udp from any 53,67 to $lan in
    $cmd allow udp from any 53,67 to me in

    # Allow skype connections in
    $cmd allow udp from any 80,123,443,1024-655353 to $lan in
    $cmd allow udp from any to $lan 80,443,1024-655353 in
    $cmd deny log udp from any to any in # Deny the rest
    $cmd allow tcp from any 1024-65535 to $lan 1024-65535 in

    # Allow in SecureFTP and SSH from public Internet
    $cmd allow tcp from { 192.168.0.0/24 or $lan or 192.168.8.0/24 } to me
    22 in #setup limit src-addr 3
    $cmd allow tcp from any to me 22 in setup limit src-addr 1

    # Allow in standard www function because I have Apache server - or is
    there an internal webserver?
    # Allow Webmin connections from close-by
    $cmd allow tcp from { 192.168.8.0/24 or 192.168.0.0/24 } to me 80,10000 in
    $cmd allow tcp from any to $lan 80,10000 in

    # Allow outgoing web traffic (via proxy) back in
    $cmd allow tcp from any 20,21,80 to me 1024-65535 in

    # Deny the rest to me
    $cmd deny log tcp from any to me in

    # Allow out regular ftp, http access if proxy is off
    $cmd allow tcp from any 20,21,80 to $lan 1024-65535 in

    # Allow in secure www function https over TLS SSL
    # Allow in send & get email function (GMail uses ports 587, 995)
    # Allow in MSN messenger
    # Allow in Time, nntp news (i.e. news groups),
    # SSH (secure FTP, Telnet, and SCP), whois
    $cmd allow tcp from any 443,25,110,587,995,1863,37,119,22,43 to any in

    #Allow in ICMP (ping) from public networks close by only.
    $cmd allow icmp from 192.168.0.0/16 to me in icmptypes 0,3,11
    $cmd allow icmp from any to $lan in icmptypes 0,3,11
    # Used for testing network connections
    $cmd allow icmp from 196.7.0.138,196.25.1.1,196.4.160.7 to me in
    icmptypes 0,3,11

    #Deny the rest
    $cmd deny icmp from any to any in

    # Reject & Log all unauthorized incoming connections from the public
    Internet (/var/log/security)
    $cmd deny log all from any to any in

    # This is skipto location for outbound stateful rules
    $cmd 8000 skipto 9000 tcp from any to any out setup
    $cmd 8010 skipto 9000 udp from any to any out
    $cmd 8020 skipto 9000 icmp from any to any out
    $cmd 8100 tee natd1 ip from any to any out
    $cmd 8150 check-state
    $cmd 8200 divert natd2 ip from any to any out
    $cmd 8250 check-state
    $cmd 8400 deny ip from any to any out
    $cmd 9000 set 12 prob 0.5 skipto 9500 ip from any to any out
    $cmd 9100 set 1 divert natd1 ip from any to any out
    $cmd 9200 set 1 allow ip from any to any out keep-state
    $cmd 9500 set 2 divert natd2 ip from any to any out
    $cmd 9600 set 2 allow ip from any to any out keep-state

    # deny and log all packets that fell through to see what they are
    $cmd 9999 deny log all from any to any
    ################ End of IPFW rules file ###############################

    What this effectively does is send packets destined for either of the
    $ext_gw's out over both
    the external lines. This is a terrible way of spamming these external
    lines with traffic that doesn't
    belong there, but then again it's only a single link between your ext_if
    and the ext_gw with
    nothing else on that link. It's also not holding much traffic as it's
    the limited internet link
    which (here in .za) is only 512k per line. (although they have just
    recently brought out a 1M link, available in certain areas)
    Anyway, the 100M link should handle the extra noise on the line.
    If someone has a nicer solution for me - I'm all ears.

    Last thing to do was to setup fail-over of some sort.
    Again, I thought myself some sh scripting in a day, so this isn't the
    prettiest, but it works.
    I added an entry to crontab to have this script run every 2 minutes.
    Basically it adds a specific (pingable) host to the route, and then
    pings it via the defined path (1st dsl line).
    Then changes the route and pings it via the 2nd path (2nd dsl line).

    #-----------------------------------------
    #!/bin/sh
    target="196.7.0.138"
    ext_gw1="192.168.8.1"
    ext_gw2="192.168.0.1"

    # Setup route to ping through
    route -q add -host $target $ext_gw1
    # Test link one through ext_gw1 to see if any packets get returned
    ping1=$( ping -q -c 3 -f -s 8 -o -t 2 196.7.0.138 | grep "packet loss" |
    cut -c24-24 )
    # Test link two through ext_gw2 to see if any packets get returned
    route -q delete $target
    route -q add -host $target $ext_gw2
    ping2=$( ping -q -c 3 -f -s 8 -o -t 2 196.7.0.138 | grep "packet loss" |
    cut -c24-24 )
    # Remove route
    route -q delete $target

    # Configure the ipfw sets as per network route availability
    if [ "$ping1" != "0" ]; then
        if [ "$ping2" = "1" ]; then
        ipfw set enable 1 2 12
        else
        ipfw set enable 1
        ipfw set disable 2 12
        fi
    else
        if [ "$ping2" != "0" ]; then
            ipfw set disable 1 12
        ipfw set enable 2
        else
        # echo "enabling everything to wait for network recovery"
        ipfw set enable 1 2 12
        fi
    fi
    #--------------------------------------

    This could be expanded to some sort of crude bandwidth management system
    if you add some rules to the ipfw
    that specify probability of 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8 with each
    rule associated with a different ipfw set.
    Then you parse the ping replies (you would now need an average for the
    pings, not just a single ping) for the average time on each route and do
    some math to enable the correct ipfw set with the correct ipfw prob ratio.
    It's dirty, but it should (in theory) work. I haven't gotten this far -
    currently happy with what I have so far and time to earn some money
    again. Maybe later.

    Feel free to post any comments / queries. Feedback welcome.
    I hope this helps some people save some of the hours and hours I spent
    on trial and error.

    Regards
    Graham

    _______________________________________________
    freebsd-net@freebsd.org mailing list
    http://lists.freebsd.org/mailman/listinfo/freebsd-net
    To unsubscribe, send any mail to "freebsd-net-unsubscribe@freebsd.org"


  • Next message: Jeremie Le Hen: "Re: IPFW+DUMMYNET UPLOAD PROBLEM"

    Relevant Pages

    • 6.0BETA1: ipfw Abort trap (dumped core) - "modified (chunk-) pointer"
      ... I have some firewall rules loaded, and on bootup, ipfw fails ... Abort trap ... $cmd 00100 allow all from any to any via lo0 ... $cmd 00200 deny all from any to 127.0.0.0/8 ...
      (freebsd-current)
    • problem in smtp server
      ... FTP and the e-mail for the domain with qmail. ... All seems to work fine if i disable the ipfw... ... $cmd 00010 allow all from any to any via lo0 ... $cmd 00299 deny log all from any to any out via $pif ...
      (comp.unix.bsd.freebsd.misc)
    • Re: IPFW rules for FreeBSD?
      ... # IPFW Firewall Commands ... # Allow Loopback and Deny Loopback Spoofing ... $cmd allow tcp from any to any 22 setup keep-state #allow SSH/SFTP ... >> My FreeBSD server got hacked and I would like to finally configure a IPFW ...
      (comp.security.firewalls)
    • Re: RELENG_5 kernel b0rken with IPFIREWALL and without PFIL_HOOKS
      ... > to kldload ipfw, which will fail if ipfw is compiled into the kernel, ... > and since the precmd failed, the _cmd will not be run. ...
      (freebsd-current)
    • Re: ipfw rules
      ... I'm going to use both the ruleset you recommended ... $cmd 011 allow all from any to any via lo0 ... $cmd 012 deny all from any to 127.0.0.0/8 ... #check state of incoming packets ...
      (freebsd-questions)