Add basic files
authorDimitris Aragiorgis <dimara@grnet.gr>
Wed, 4 Apr 2012 15:13:28 +0000 (18:13 +0300)
committerDimitris Aragiorgis <dimara@grnet.gr>
Sun, 8 Apr 2012 14:56:06 +0000 (17:56 +0300)
Include nfdhcpd and tools handling vlans

Include basic scripts for configuring networks in nodes

Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

21 files changed:
README
add-network [new file with mode: 0755]
add-nodegroup [new file with mode: 0644]
conf/default/nfdhcpd [new file with mode: 0644]
conf/init.d/nfdhcpd [new file with mode: 0644]
connect-network [new file with mode: 0644]
disconnect-network [new file with mode: 0644]
interfaces [new file with mode: 0644]
kvm-vif-bridge [new file with mode: 0755]
modify-network [new file with mode: 0644]
nfdhcpd/nfdhcpd [new file with mode: 0755]
nfdhcpd/nfdhcpd.conf [new file with mode: 0644]
nfdhcpd/nfdhcpd.ferm [new file with mode: 0644]
pylintrc [new file with mode: 0644]
remove-network [new file with mode: 0644]
rt_tables [new file with mode: 0644]
tools/mac2eui64 [new file with mode: 0755]
vlans/if-down.d/vmrouter [new file with mode: 0755]
vlans/if-up.d/vmrouter [new file with mode: 0755]
vlans/prv-net-helper [new file with mode: 0755]
vlans/vlan [new file with mode: 0755]

diff --git a/README b/README
index 88156c1..7c2708b 100644 (file)
--- a/README
+++ b/README
@@ -32,15 +32,16 @@ GRNET's specific routed mode:
 Single bridge setup. Private IPs. Masquerade: 
 
 For security and not being able to change ip-mac-tap key: 
-# ebtables -t filter -D INPUT -i tap0 -j TAP0 
-# ebtables -t filter -D FORWARD -i tap0 -j TAP0
-# ebtables -t filter -X TAP0
-# ebtables -t filter -N TAP0 
-# ebtables -t filter -A TAP0 --ip-source \! 192.168.100.2 -p ipv4 -j DROP
-# ebtables -t filter -A TAP0 -s \! aa:00:00:8c:d3:a4 -j DROP 
-# ebtables -t filter -A INPUT -i tap0 -j TAP0 (for masquerading)
-# ebtables -t filter -A FORWARD -i tap0 -j TAP0 (for private lans)
-
+# ebtables -N FROMTAP0 
+# ebtables -A FROMTAP0 --ip-source \! 192.168.100.2 -p ipv4 -j DROP
+# ebtables -A FROMTAP0 -s \! aa:00:00:8c:d3:a4 -j DROP 
+# ebtables -A INPUT -i tap0 -j FROMTAP0 (for masquerading)
+# ebtables -A FORWARD -i tap0 -j FROMTAP0 (for private lans)
+# ebtables -N TOTAP0
+# ebtables -A FORWARD -o tap0 -j TOTAP0
+# ebtables -A OUTPUT -o tap0 -j TOTAP0
+# ebtables -A TOTAP0 -s 6e:10:e1:a0:c3:0f -j ACCEPT (from gateway)
+# ebtables -A TOTAP0 -s \! aa:0:0:8c:d3:a4/ff:ff:ff:ff:0:0 -j DROP 
 
 
 Private LANs: 
diff --git a/add-network b/add-network
new file mode 100755 (executable)
index 0000000..4374d4c
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+SUBNET=$1
+GATEWAY=$2
+TYPE=$3
+NAME=$4
+RT_TABLES=/etc/iproute2/rt_tables
+
+
+
+if [ $# -ne 4 ]; then
+  echo "$0 <subnet> <gateway> <private/public> <name>"
+  exit 1
+fi
+
+
+
+cat > $DIR/networks/$NAME <<EOF
+SUBNET=$SUBNET
+GATEWAY=$GATEWAY
+TYPE=$TYPE
+EOF
+
+
+IDX=$(ls $DIR/networks | wc -l)
+
+# remove old entry
+sed -i '/^'"$IDX"'\ / d' $RT_TABLES
+
+echo "$IDX rt_$NAME" >> $RT_TABLES
+
+
diff --git a/add-nodegroup b/add-nodegroup
new file mode 100644 (file)
index 0000000..a4210de
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+NODES=$1
+ROUTER=$2
+IFACE=$3
+VLAN=$4
+VLANS=$5
+NAME=$6
+
+
+if [ $# -ne 6 ]; then
+  echo "$0 <list_nodes> <router> <iface> <public_vlan> <list_of_private_vlans> <name>"
+  echo "$0 'dev88 89' 'dev88' 'eth0' '101' '2990 2999' 'default'"
+  exit 1
+fi
+
+
+
+cat > $DIR/nodegroups/$NAME <<EOF
+ROUTER=$ROUTER
+INTERFACE=$IFACE
+PUBLIC_VLAN=$VLAN
+PRIVATE_VLANS=$VLANS
+EOF
+
+
diff --git a/conf/default/nfdhcpd b/conf/default/nfdhcpd
new file mode 100644 (file)
index 0000000..49bf08f
--- /dev/null
@@ -0,0 +1,12 @@
+# Defaults for nfdhcpd initscript
+# sourced by /etc/init.d/nfdhcpd
+# installed at /etc/default/nfdhcpd by the maintainer scripts
+
+#
+# This is a POSIX shell fragment
+#
+
+RUN="yes"
+
+# Additional options that are passed to the Daemon.
+DAEMON_OPTS=""
diff --git a/conf/init.d/nfdhcpd b/conf/init.d/nfdhcpd
new file mode 100644 (file)
index 0000000..6c77a64
--- /dev/null
@@ -0,0 +1,229 @@
+#!/bin/sh
+#
+# This is free software; you may redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2,
+# or (at your option) any later version.
+#
+# This is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License with
+# the Debian operating system, in /usr/share/common-licenses/GPL;  if
+# not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+### BEGIN INIT INFO
+# Provides:          nfdhcpd
+# Required-Start:    $network $local_fs $remote_fs
+# Required-Stop:     $remote_fs
+# Should-Start:
+# Should-Stop:
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: NFQueue DHCP/RA server
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+DAEMON=/usr/sbin/nfdhcpd
+NAME=nfdhcpd
+DESC="NFQUEUE-based DHCP/RA server" 
+LOGDIR=/var/log/nfdhcpd
+
+PIDFILE=/var/run/$NAME.pid
+
+test -x $DAEMON || exit 0
+
+. /lib/lsb/init-functions
+
+# Default options, these can be overriden by the information
+# at /etc/default/$NAME
+DAEMON_OPTS=""          # Additional options given to the server
+
+DIETIME=2              # Time to wait for the server to die, in seconds
+                        # If this value is set too low you might not
+                        # let some servers to die gracefully and
+                        # 'restart' will not work
+
+STARTTIME=1             # Time to wait for the server to start, in seconds
+                        # If this value is set each time the server is
+                        # started (on start or restart) the script will
+                        # stall to try to determine if it is running
+                        # If it is not set and the server takes time
+                        # to setup a pid file the log message might 
+                        # be a false positive (says it did not start
+                        # when it actually did)
+                        
+LOGFILE=$LOGDIR/$NAME.log  # Server logfile
+#DAEMONUSER=nfdhcp   # Users to run the daemons as. If this value
+                        # is set start-stop-daemon will chuid the server
+
+# Include defaults if available
+if [ -f /etc/default/$NAME ] ; then
+       . /etc/default/$NAME
+fi
+
+# Use this if you want the user to explicitly set 'RUN' in
+# /etc/default/
+if [ "x$RUN" != "xyes" ] ; then
+    log_failure_msg "$NAME disabled, please adjust the configuration to your needs "
+    log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it."
+    exit 1
+fi
+
+# Check that the user exists (if we set a user)
+# Does the user exist?
+set -e
+
+running_pid() {
+# Check if a given process pid's cmdline matches a given name
+    pid=$1
+    name=$2
+    [ -z "$pid" ] && return 1
+    [ ! -d /proc/$pid ] &&  return 1
+    cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
+    # Is this the expected server
+    [ "$cmd" != "$name" ] &&  return 1
+    return 0
+}
+
+running() {
+# Check if the process is running looking at /proc
+# (works for all users)
+
+    # No pidfile, probably no daemon present
+    [ ! -f "$PIDFILE" ] && return 1
+    pid=`cat $PIDFILE`
+    running_pid $pid python || return 1
+    return 0
+}
+
+start_server() {
+       start_daemon -p $PIDFILE $DAEMON $DAEMON_OPTS
+       errcode=$?
+       return $errcode
+}
+
+stop_server() {
+       killproc -p $PIDFILE $DAEMON
+       rrcode=$?
+       return $errcode
+}
+
+reload_server() {
+    [ ! -f "$PIDFILE" ] && return 1
+    pid=pidofproc $PIDFILE # This is the daemon's pid
+    # Send a SIGHUP
+    kill -1 $pid
+    return $?
+}
+
+force_stop() {
+# Force the process to die killing it manually
+       [ ! -e "$PIDFILE" ] && return
+       if running ; then
+               kill -15 $pid
+       # Is it really dead?
+               sleep "$DIETIME"s
+               if running ; then
+                       kill -9 $pid
+                       sleep "$DIETIME"s
+                       if running ; then
+                               echo "Cannot kill $NAME (pid=$pid)!"
+                               exit 1
+                       fi
+               fi
+       fi
+       rm -f $PIDFILE
+}
+
+
+case "$1" in
+  start)
+       log_daemon_msg "Starting $DESC " "$NAME"
+        # Check if it's running first
+        if running ;  then
+            log_progress_msg "apparently already running"
+            log_end_msg 0
+            exit 0
+        fi
+        if start_server ; then
+            # NOTE: Some servers might die some time after they start,
+            # this code will detect this issue if STARTTIME is set
+            # to a reasonable value
+            [ -n "$STARTTIME" ] && sleep $STARTTIME # Wait some time 
+            if  running ;  then
+                # It's ok, the server started and is running
+                log_end_msg 0
+            else
+                # It is not running after we did start
+                log_end_msg 1
+            fi
+        else
+            # Either we could not start it
+            log_end_msg 1
+        fi
+       ;;
+  stop)
+        log_daemon_msg "Stopping $DESC" "$NAME"
+        if running ; then
+            # Only stop the server if we see it running
+                       errcode=0
+            stop_server || errcode=$?
+            log_end_msg $errcode
+        else
+            # If it's not running don't do anything
+            log_progress_msg "apparently not running"
+            log_end_msg 0
+            exit 0
+        fi
+        ;;
+  force-stop)
+        # First try to stop gracefully the program
+        $0 stop
+        if running; then
+            # If it's still running try to kill it more forcefully
+            log_daemon_msg "Stopping (force) $DESC" "$NAME"
+                       errcode=0
+            force_stop || errcode=$?
+            log_end_msg $errcode
+        fi
+       ;;
+  restart|force-reload)
+        log_daemon_msg "Restarting $DESC" "$NAME"
+               errcode=0
+        stop_server || errcode=$?
+        # Wait some sensible amount, some server need this
+        [ -n "$DIETIME" ] && sleep $DIETIME
+        start_server || errcode=$?
+        [ -n "$STARTTIME" ] && sleep $STARTTIME
+        running || errcode=$?
+        log_end_msg $errcode
+       ;;
+  status)
+
+        log_daemon_msg "Checking status of $DESC" "$NAME"
+        if running ;  then
+            log_progress_msg "running"
+            log_end_msg 0
+        else
+            log_progress_msg "apparently not running"
+            log_end_msg 1
+            exit 1
+        fi
+        ;;
+  reload)
+        log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon"
+        log_warning_msg "cannot re-read the config file (use restart)."
+       ;;
+  *)
+       N=/etc/init.d/$NAME
+       echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+       exit 1
+       ;;
+esac
+
+exit 0
diff --git a/connect-network b/connect-network
new file mode 100644 (file)
index 0000000..7e9fc5f
--- /dev/null
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+NETWORK=$1
+NODEGROUP=$2
+MODE=$3
+LINK=$4
+
+source /etc/default/snf-network
+
+if [ $# -ne 4 ]; then
+  echo "$0 <network> <nodegroup> <mode> <link>"
+  exit 1
+fi
+
+NETWORK_FILE=$DIR/networks/$NETWORK
+NODEGROUP_FILE=$DIR/nodegoups/$NODEGROUP
+INTERFACES=$DIR/interfaces/$NETWORK-$NODEGROUP
+
+source $NETWORK_FILE
+source $NODEGROUP_FILE
+
+if [ $MODE == "routed" ]; then 
+  VLAN=$LINK
+  if [ $TYPE == "public" ]; then
+    APR_IP=$(ipcalc $SUBNET | grep HostMax | awk '{print $2}')
+    cat > $INTERFACES<<EOF
+# $VLAN $MODE
+auto $VLAN
+iface $VLAN inet manual
+#    ip-routing-table rt_$NETWORK
+#    ip-routes $SUBNET
+#    ip-gateway $GATEWAY
+#    ip-forwarding 1
+#    ip-proxy-arp 1
+#    arp-ip $ARP_IP
+EOF 
+    ifup -i $INTERFACES $VLAN
+    ip link set $VLAN up
+
+    ip rule add iif $VLAN table rt_$NAME
+
+    ip route add $SUBNET dev $VLAN table main 
+
+    ip route add $SUBNET dev $VLAN table rt_$NAME
+    ip route add default via $GATEWAY dev $VLAN table rt_$NAME
+    
+    echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
+
+    arptables -A OUTPUT -o $VLAN --opcode request -j mangle --mangle-ip-s  $ARP_IP 
+  fi
+fi
+
+
+
+if [ $MODE == "bridged" ]; then
+  BRIDGE=$LINK
+  echo 1 > /proc/sys/net/ipv4/ip_forward
+  if [ $TYPE == "public" ]; then
+    VLAN=$INTERFACE.$PUBLIC_VLAN_ID
+  elif [ $TYPE == "private" ]; then
+    VLAN_ID=${PRIVATE_VLAN_IDS%% *}
+    VLAN_IDS=${PRIVATE_VLAN_IDS#* }
+    sed -i 's/PRIVATE_VLAN_IDS/ s/=.*/='"VLAN_IDS"'/' $NODEGROUP_FILE
+    #set -- $PRIVATE_VLAN_IDS
+    #VLAN=$1
+    #shift
+    #VLANS=$@
+    VLAN=$INTERFACE.$VLAN_ID
+  fi
+  cat > $INTERFACES <<EOF
+# $VLAN $MODE $BRIDGE
+auto $VLAN
+iface $VLAN inet manual
+
+auto $BRIDGE
+iface $BRIDGE inet manual
+  bridge_ports $VLAN
+  bridge_stp off
+  bridge_fd 2
+EOF
+  ifup -i $INTERFACES $BRIDGE
+  ip link set $VLAN up
+  ip route add $SUBNET dev $BRIDGE table main
+
+  ip route add $SUBNET dev $BRIDGE table rt_$NETWORK
+  if [ ! -z $GATEWAY ]; then
+    ip route add default via  dev $BRIDGE table rt_$NETWORK
+    if [ $TYPE == "private" ]; then 
+      if [ ! -z $ROUTER ]; then 
+        if [ $(hostname) == $ROUTER ]; then
+          NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}')
+          ip addr add $GATEWAY/$NETMASK dev $BRIDGE
+          iptables -t nat -A POSTROUTING -s $SUBNET \! -d $SUBNET -j MASQUERADE
+        fi  
+      fi
+    fi
+  fi
+fi
diff --git a/disconnect-network b/disconnect-network
new file mode 100644 (file)
index 0000000..f28c912
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+NETWORK=$1
+NODEGROUP=$2
+
+source /etc/default/snf-network
+
+if [ $# -ne 2 ]; then
+  echo "$0 <network> <nodegroup>"
+  exit 1
+fi
+
+NETWORK_FILE=$DIR/networks/$NETWORK
+NODEGROUP_FILE=$DIR/nodegoups/$NODEGROUP
+INTERFACES=$DIR/interfaces/$NETWORK-$NODEGROUP
+
+read x VLAN BRIDGE < $INTERFACES
+
+VLAN_ID=${VLAN#*:}
+
+source $NETWORK_FILE
+source $NODEGROUP_FILE
+
+if [ $MODE == "routed" ]; then 
+  if [ $TYPE == "public" ]; then
+    APR_IP=$(ipcalc $SUBNET | grep HostMax | awk '{print $2}')
+    ip rule del iif $VLAN table rt_$NAME
+
+    ip route del $SUBNET dev $VLAN table main 
+
+    ip route del $SUBNET dev $VLAN table rt_$NAME
+    ip route del default via $GATEWAY dev $VLAN table rt_$NAME
+
+    arptables -D OUTPUT -o $VLAN --opcode request -j mangle --mangle-ip-s  $ARP_IP 
+    ifdown -i $INTERFACES $VLAN
+    rm $INTERFACES
+  fi
+fi
+
+
+
+if [ $MODE == "bridged" ]; then
+  if [ $TYPE == "private" ]; then
+    VLAN_IDS="$VLAN_ID $PRIVATE_VLAN_IDS"
+    sed -i 's/PRIVATE_VLAN_IDS/ s/=.*/='"VLAN_IDS"'/' $NODEGROUP_FILE
+  fi
+
+  ip route del $SUBNET dev $BRIDGE table main
+
+  ip route del $SUBNET dev $BRIDGE table rt_$NETWORK
+  if [ ! -z $GATEWAY ]; then
+    ip route del default via $GATEWAY dev $BRIDGE table rt_$NETWORK
+    if [ $TYPE == "private" ]; then 
+      if [ ! -z $ROUTER ]; then 
+        if [ $(hostname) == $ROUTER ]; then
+          NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}')
+          ip addr del $GATEWAY/$NETMASK dev $LINK 
+          iptables -t nat -D POSTROUTING -s $SUBNET \! -d $SUBNET -j MASQUERADE
+        fi  
+      fi
+    fi
+  fi
+  ifdown -i $INTERFACES $BRIDGE
+  rm $INTERFACES
+fi
diff --git a/interfaces b/interfaces
new file mode 100644 (file)
index 0000000..5faf191
--- /dev/null
@@ -0,0 +1,41 @@
+# IP-less inteface, used to route public IPv4
+# for Synnefo VMs
+auto eth0.101
+iface eth0.101 inet manual
+    ip-routing-table rt_public
+    ip-routes 62.217.123.128/27
+    ip-gateway 62.217.123.129
+    ip-forwarding 1
+    ip-proxy-arp 1
+    arp-ip 62.217.123.158
+
+#auto eth0.100
+iface eth0.100 inet manual
+  up ip link set eth0.100 up
+
+#auto br100
+iface br100 inet static
+  # needed for being the rooter for the VMs
+  address 192.168.100.1
+  netmask 255.255.255.240
+  bridge_ports eth0.100
+  # needed by nfdhcpd to make DHCP responses
+  up ip route add 192.168.100.0/28 dev br100 table rt_net100
+  up ip route add default via 192.168.100.1 dev br100 table rt_net100
+  # needed for the VMs to connect to the world
+  up iptables -t nat -A POSTROUTING -s 192.168.100.0/28 \! -d 192.168.100.0/28 -j MASQUERADE
+  down iptables -t nat -D POSTROUTING -s 192.168.100.0/28 \! -d 192.168.100.0/28 -j MASQUERADE
+  bridge_stp off
+  bridge_fd 2
+
+#auto br100:1
+iface br100:1 inet static
+  # needed for being the rooter for the VMs
+  address 192.168.101.1
+  netmask 255.255.255.240
+  up ip route add 192.168.101.0/28 dev br100 table rt_net101
+  up ip route add default via 192.168.101.1 dev br100 table rt_net101
+  # needed for the VMs to connect to the world
+  up iptables -t nat -A POSTROUTING -s 192.168.101.0/28 \! -d 192.168.101.0/28 -j MASQUERADE
+  down iptables -t nat -D POSTROUTING -s 192.168.101.0/28 \! -d 192.168.101.0/28 -j MASQUERADE
+
diff --git a/kvm-vif-bridge b/kvm-vif-bridge
new file mode 100755 (executable)
index 0000000..a4d8e98
--- /dev/null
@@ -0,0 +1,149 @@
+#!/bin/bash
+
+# This is an example of a Ganeti kvm ifup script that configures network
+# interfaces based on the initial deployment of the Okeanos project
+
+TAP_CONSTANT_MAC=cc:47:52:4e:45:54 # GRNET in hex :-)
+MAC2EUI64=/usr/bin/mac2eui64
+NFDHCPD_STATE_DIR=/var/lib/nfdhcpd
+
+function routed_setup_ipv4 {
+       # get the link's default gateway
+       gw=$(ip route list table $TABLE | sed -n 's/default via \([^ ]\+\).*/\1/p' | head -1)
+
+       # mangle ARPs to come from the gw's IP
+       arptables -D OUTPUT -o $INTERFACE --opcode request -j mangle >/dev/null 2>&1
+       arptables -A OUTPUT -o $INTERFACE --opcode request -j mangle --mangle-ip-s "$gw"
+
+       # route interface to the proper routing table
+       while ip rule del dev $INTERFACE; do :; done
+       ip rule add dev $INTERFACE table $TABLE
+
+       # static route mapping IP -> INTERFACE
+       ip route replace $IP proto static dev $INTERFACE table $TABLE
+
+       # Enable proxy ARP
+       echo 1 > /proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp
+}
+
+function routed_setup_ipv6 {
+       # Add a routing entry for the eui-64
+       prefix=$(ip -6 route list table $TABLE | awk '/\/64/ {print $1; exit}')
+       uplink=$(ip -6 route list table $TABLE | sed -n 's/default via .* dev \([^ ]\+\).*/\1/p' | head -1)
+       eui64=$($MAC2EUI64 $MAC $prefix)
+
+       while ip -6 rule del dev $INTERFACE; do :; done
+       ip -6 rule add dev $INTERFACE table $TABLE
+       ip -6 ro replace $eui64/128 dev $INTERFACE table $TABLE
+       ip -6 neigh add proxy $eui64 dev $uplink
+
+       # disable proxy NDP since we're handling this on userspace
+       # this should be the default, but better safe than sorry
+       echo 0 > /proc/sys/net/ipv6/conf/$INTERFACE/proxy_ndp
+}
+
+# pick a firewall profile per NIC, based on tags (and apply it)
+function routed_setup_firewall {
+       ifprefix="synnefo:network:$INTERFACE_INDEX:"
+       for tag in $TAGS; do
+               case ${tag#$ifprefix} in
+               protected)
+                       chain=protected
+               ;;
+               unprotected)
+                       chain=unprotected
+               ;;
+               limited)
+                       chain=limited
+               ;;
+               esac
+       done
+
+       # Flush any old rules. We have to consider all chains, since
+       # we are not sure the instance was on the same chain, or had the same
+       # tap interface.
+       for oldchain in protected unprotected limited; do
+               iptables  -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
+               ip6tables -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null
+       done
+
+       if [ "x$chain" != "x" ]; then
+               iptables  -A FORWARD -o $INTERFACE -j $chain
+               ip6tables -A FORWARD -o $INTERFACE -j $chain
+       fi
+}
+
+function routed_setup_nfdhcpd {
+       umask 022
+       cat >$NFDHCPD_STATE_DIR/$INTERFACE <<EOF
+IFACE=$1
+IP=$IP
+MAC=$MAC
+LINK=$TABLE
+HOSTNAME=$INSTANCE
+TAGS="$TAGS"
+EOF
+}
+
+function make_ebtables {
+  TAP=$INTERFACE
+  FROM=FROM${TAP^^}
+  TO=TO${TAP^^}
+  
+  ebtables -D INPUT -i $TAP -j $FROM
+  ebtables -D FORWARD -i $TAP -j $FROM
+  ebtables -D FORWARD -o $TAP -j $TO
+  ebtables -D OUTPUT -o $TAP -j $TO
+  
+  ebtables -X $FROM
+  ebtables -X $TO
+
+  ebtables -N $FROM
+  ebtables -A $FROM --ip-source \! $IP -p ipv4 -j DROP
+  ebtables -A $FROM -s \! $MAC -j DROP 
+  ebtables -A INPUT -i $TAP -j $FROM 
+  ebtables -A FORWARD -i $TAP -j $FROM 
+  ebtables -N $TO
+  ebtables -A FORWARD -o $TAP -j $TO
+  ebtables -A OUTPUT -o $TAP -j $TO
+  if [ $TYPE == "private" ]; then 
+    ebtables -A $TO -s \! $MAC/$MAC_MASK -j DROP 
+    if [ ! -z $GATEWAY ]; then 
+      ebtables -A $TO -s $ROUTER_MAC -j ACCEPT 
+    fi
+  fi
+}
+
+#FIXME: import router mac from the config files
+#       must know node group!! how???
+ROUTER_MAC=6e:10:e1:a0:c3:0f
+MAC_MASK=ff:ff:ff:0:0:0
+
+TABLE=rt_$NETWORK
+
+source /var/lib/snf-network/networks/$NETWORK
+
+
+if [ "$MODE" = "routed" ]; then
+       # special proxy-ARP/NDP routing mode
+
+       # use a constant predefined MAC address for the tap
+       ip link set $INTERFACE addr $TAP_CONSTANT_MAC
+       # bring the tap up
+       ifconfig $INTERFACE 0.0.0.0 up
+
+       # Drop unicast BOOTP/DHCP packets
+       iptables -D FORWARD -i $INTERFACE -p udp --dport 67 -j DROP 2>/dev/null
+       iptables -A FORWARD -i $INTERFACE -p udp --dport 67 -j DROP
+
+       routed_setup_ipv4
+       routed_setup_ipv6
+       routed_setup_firewall
+       routed_setup_nfdhcpd $INTERFACE
+elif [ "$MODE" = "bridged" ]; then
+  while ip rule del dev $INTERFACE; do :; done
+       ifconfig $INTERFACE 0.0.0.0 up
+       brctl addif $BRIDGE $INTERFACE
+       routed_setup_nfdhcpd $BRIDGE
+  make_ebtables
+fi   
diff --git a/modify-network b/modify-network
new file mode 100644 (file)
index 0000000..0058d60
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+NEW_GATEWAY=$1
+NEW_TYPE=$2
+NETWORK=$3
+RT_TABLES=/etc/iproute2/rt_tables
+
+if [ $# -ne 3 ]; then
+  echo "$0 <gateway> <private/public> <name>"
+  exit 1
+fi
+
+source /etc/default/snf-network
+
+NETWORK_FILE=$DIR/networks/$NETWORK
+
+source $NETWORK_FILE
+
+OLD_GATEWAY=$GATEWAY
+OLD_TYPE=$TYPE
+
+INTERFACES=$(ls $DIR/interfaces/$NETWORK-*)
+
+
+for IFACES in $INTERFACES ; do
+  
+  NODEGROUP=$(echo $IFACES | sed 's/.*interfaces.*-//')
+  source $DIR/nodegroups/$NODEGROUP
+
+  read x VLAN MODE BRIDGE < $INTERFACES
+
+  if [ $MODE == "routed" ]; then 
+    if [ $TYPE == "public" ]; then
+      ip route replace default via $GATEWAY dev $VLAN table rt_$NETWORK
+    fi
+  fi
+
+  if [ $MODE == "bridged" ]; then
+    if [ ! -z $GATEWAY ]; then
+      ip route replace default via $GATEWAY dev $BRIDGE table rt_$NETWORK
+      if [ $TYPE == "private" ]; then 
+        if [ ! -z $ROUTER ]; then 
+          if [ $(hostname) == $ROUTER ]; then
+            NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}')
+            ip addr del $GATEWAY/$NETMASK dev $BRIDGE  
+            ip addr add $NEW_GATEWAY/$NETMASK dev $BRIDGE  
+          fi  
+        fi
+      fi
+    fi
+  fi
+
+  if [ ! -z $NEW_GATEWAY ]; then 
+    sed -i '/^GATEWAY/ s/=.*/='"$NEW_GATEWAY"'/' $NETWORK_FILE 
+  fi
+
+  if [ ! -z $NEW_TYPE ]; then
+    sed -i '/^TYPE/ s/=.*/='"$NEW_TYPE"'/' $NETWORK_FILE
+  fi 
+
+done
diff --git a/nfdhcpd/nfdhcpd b/nfdhcpd/nfdhcpd
new file mode 100755 (executable)
index 0000000..0b94d4a
--- /dev/null
@@ -0,0 +1,934 @@
+#!/usr/bin/env python
+#
+
+# nfdcpd: A promiscuous, NFQUEUE-based DHCP server for virtual machine hosting
+# Copyright (c) 2010 GRNET SA
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import os
+import re
+import sys
+import glob
+import time
+import logging
+import logging.handlers
+import threading
+import traceback
+import subprocess
+
+import daemon
+import daemon.pidlockfile
+import nfqueue
+import pyinotify
+
+import IPy
+import socket
+from select import select
+from socket import AF_INET, AF_INET6
+
+from scapy.data import ETH_P_ALL
+from scapy.packet import BasePacket
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \
+                               ICMPv6NDOptDstLLAddr, \
+                               ICMPv6NDOptPrefixInfo, \
+                               ICMPv6NDOptRDNSS
+from scapy.layers.dhcp import BOOTP, DHCP
+
+DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
+DEFAULT_PATH = "/var/run/ganeti-dhcpd"
+DEFAULT_USER = "nobody"
+DEFAULT_LEASE_LIFETIME = 604800 # 1 week
+DEFAULT_LEASE_RENEWAL = 600  # 10 min
+DEFAULT_RA_PERIOD = 300 # seconds
+DHCP_DUMMY_SERVER_IP = "1.2.3.4"
+
+LOG_FILENAME = "nfdhcpd.log"
+
+SYSFS_NET = "/sys/class/net"
+
+LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
+
+# Configuration file specification (see configobj documentation)
+CONFIG_SPEC = """
+[general]
+pidfile = string()
+datapath = string()
+logdir = string()
+user = string()
+
+[dhcp]
+enable_dhcp = boolean(default=True)
+lease_lifetime = integer(min=0, max=4294967295)
+lease_renewal = integer(min=0, max=4294967295)
+server_ip = ip_addr()
+dhcp_queue = integer(min=0, max=65535)
+nameservers = ip_addr_list(family=4)
+
+[ipv6]
+enable_ipv6 = boolean(default=True)
+ra_period = integer(min=1, max=4294967295)
+rs_queue = integer(min=0, max=65535)
+ns_queue = integer(min=0, max=65535)
+nameservers = ip_addr_list(family=6)
+"""
+
+
+DHCPDISCOVER = 1
+DHCPOFFER = 2
+DHCPREQUEST = 3
+DHCPDECLINE = 4
+DHCPACK = 5
+DHCPNAK = 6
+DHCPRELEASE = 7
+DHCPINFORM = 8
+
+DHCP_TYPES = {
+    DHCPDISCOVER: "DHCPDISCOVER",
+    DHCPOFFER: "DHCPOFFER",
+    DHCPREQUEST: "DHCPREQUEST",
+    DHCPDECLINE: "DHCPDECLINE",
+    DHCPACK: "DHCPACK",
+    DHCPNAK: "DHCPNAK",
+    DHCPRELEASE: "DHCPRELEASE",
+    DHCPINFORM: "DHCPINFORM",
+}
+
+DHCP_REQRESP = {
+    DHCPDISCOVER: DHCPOFFER,
+    DHCPREQUEST: DHCPACK,
+    DHCPINFORM: DHCPACK,
+    }
+
+
+def parse_routing_table(table="main", family=4):
+    """ Parse the given routing table to get connected route, gateway and
+    default device.
+
+    """
+    ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls",
+                             "table", table], stdout=subprocess.PIPE)
+    routes = ipro.stdout.readlines()
+
+    def_gw = None
+    def_dev = None
+    def_net = None
+
+    for route in routes:
+        match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route)
+        if match:
+            def_gw, def_dev = match.groups()
+            break
+
+    for route in routes:
+        # Find the least-specific connected route
+        m = re.match("^([^\\s]+) dev %s" % def_dev, route)
+        if not m:
+            continue
+
+        if family == 6 and m.group(1).startswith("fe80:"):
+            # Skip link-local declarations in "main" table
+            continue
+
+        def_net = m.group(1)
+
+        try:
+            def_net = IPy.IP(def_net)
+        except ValueError, e:
+            logging.warn("Unable to parse default route entry %s: %s",
+                         def_net, str(e))
+
+    return Subnet(net=def_net, gw=def_gw, dev=def_dev)
+
+
+def parse_binding_file(path):
+    """ Read a client configuration from a tap file
+
+    """
+    try:
+        iffile = open(path, 'r')
+    except EnvironmentError, e:
+        logging.warn("Unable to open binding file %s: %s", path, str(e))
+        return None
+
+    ifname = os.path.basename(path)
+    mac = None
+    ips = None
+    link = None
+    hostname = None
+
+    for line in iffile:
+        if line.startswith("IP="):
+            ip = line.strip().split("=")[1]
+            ips = ip.split()
+        elif line.startswith("MAC="):
+            mac = line.strip().split("=")[1]
+        elif line.startswith("LINK="):
+            link = line.strip().split("=")[1]
+        elif line.startswith("HOSTNAME="):
+            hostname = line.strip().split("=")[1]
+        elif line.startswith("IFACE="):
+            iface = line.strip().split("=")[1]
+
+    return Client(ifname=ifname, mac=mac, ips=ips, link=link, hostname=hostname, iface=iface)
+
+
+class ClientFileHandler(pyinotify.ProcessEvent):
+    def __init__(self, server):
+        pyinotify.ProcessEvent.__init__(self)
+        self.server = server
+
+    def process_IN_DELETE(self, event): # pylint: disable=C0103
+        """ Delete file handler
+
+        Currently this removes an interface from the watch list
+
+        """
+        self.server.remove_iface(event.name)
+
+    def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
+        """ Add file handler
+
+        Currently this adds an interface to the watch list
+
+        """
+        self.server.add_iface(os.path.join(event.path, event.name))
+
+
+class Client(object):
+    def __init__(self, ifname=None, mac=None, ips=None, link=None, hostname=None, iface=None):
+        self.mac = mac
+        self.ips = ips
+        self.hostname = hostname
+        self.link = link
+        self.iface = iface
+        self.ifname = ifname
+
+    @property
+    def ip(self):
+        return self.ips[0]
+
+    def is_valid(self):
+        return self.mac is not None and self.ips is not None\
+               and self.hostname is not None
+
+
+class Subnet(object):
+    def __init__(self, net=None, gw=None, dev=None):
+        if isinstance(net, str):
+            self.net = IPy.IP(net)
+        else:
+            self.net = net
+        self.gw = gw
+        self.dev = dev
+
+    @property
+    def netmask(self):
+        """ Return the netmask in textual representation
+
+        """
+        return str(self.net.netmask())
+
+    @property
+    def broadcast(self):
+        """ Return the broadcast address in textual representation
+
+        """
+        return str(self.net.broadcast())
+
+    @property
+    def prefix(self):
+        """ Return the network as an IPy.IP
+
+        """
+        return self.net.net()
+
+    @property
+    def prefixlen(self):
+        """ Return the prefix length as an integer
+
+        """
+        return self.net.prefixlen()
+
+    @staticmethod
+    def _make_eui64(net, mac):
+        """ Compute an EUI-64 address from an EUI-48 (MAC) address
+
+        """
+        comp = mac.split(":")
+        prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
+        eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
+        eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
+        for l in range(0, len(eui64), 2):
+            prefix += ["".join(eui64[l:l+2])]
+        return IPy.IP(":".join(prefix))
+
+    def make_eui64(self, mac):
+        """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
+        subnet.
+
+        """
+        return self._make_eui64(self.net, mac)
+
+    def make_ll64(self, mac):
+        """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
+
+        """
+        return self._make_eui64("fe80::", mac)
+
+
+class VMNetProxy(object): # pylint: disable=R0902
+    def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
+                 rs_queue_num=None, ns_queue_num=None,
+                 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
+                 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
+                 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
+                 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
+
+        self.data_path = data_path
+        self.lease_lifetime = dhcp_lease_lifetime
+        self.lease_renewal = dhcp_lease_renewal
+        self.dhcp_server_ip = dhcp_server_ip
+        self.ra_period = ra_period
+        if dhcp_nameservers is None:
+            self.dhcp_nameserver = []
+        else:
+            self.dhcp_nameservers = dhcp_nameservers
+
+        if ipv6_nameservers is None:
+            self.ipv6_nameservers = []
+        else:
+            self.ipv6_nameservers = ipv6_nameservers
+
+        self.ipv6_enabled = False
+
+        self.clients = {}
+        self.subnets = {}
+        self.ifaces = {}
+        self.v6nets = {}
+        self.nfq = {}
+        self.l2socket = socket.socket(socket.AF_PACKET,
+                                      socket.SOCK_RAW, ETH_P_ALL)
+        self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
+
+        # Inotify setup
+        self.wm = pyinotify.WatchManager()
+        mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
+        mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
+        inotify_handler = ClientFileHandler(self)
+        self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
+        self.wm.add_watch(self.data_path, mask, rec=True)
+
+        # NFQUEUE setup
+        if dhcp_queue_num is not None:
+            self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
+
+        if rs_queue_num is not None:
+            self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
+            self.ipv6_enabled = True
+
+        if ns_queue_num is not None:
+            self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
+            self.ipv6_enabled = True
+
+    def _cleanup(self):
+        """ Free all resources for a graceful exit
+
+        """
+        logging.info("Cleaning up")
+
+        logging.debug("Closing netfilter queues")
+        for q in self.nfq.values():
+            q.close()
+
+        logging.debug("Closing socket")
+        self.l2socket.close()
+
+        logging.debug("Stopping inotify watches")
+        self.notifier.stop()
+
+        logging.info("Cleanup finished")
+
+    def _setup_nfqueue(self, queue_num, family, callback):
+        logging.debug("Setting up NFQUEUE for queue %d, AF %s",
+                      queue_num, family)
+        q = nfqueue.queue()
+        q.set_callback(callback)
+        q.fast_open(queue_num, family)
+        q.set_queue_maxlen(5000)
+        # This is mandatory for the queue to operate
+        q.set_mode(nfqueue.NFQNL_COPY_PACKET)
+        self.nfq[q.get_fd()] = q
+
+    def sendp(self, data, iface):
+        """ Send a raw packet using a layer-2 socket
+
+        """
+        if isinstance(data, BasePacket):
+            data = str(data)
+
+        self.l2socket.bind((iface, ETH_P_ALL))
+        count = self.l2socket.send(data)
+        ldata = len(data)
+        if count != ldata:
+            logging.warn("Truncated send on %s (%d/%d bytes sent)",
+                         iface, count, ldata)
+
+    def build_config(self):
+        self.clients.clear()
+        self.subnets.clear()
+
+        for path in glob.glob(os.path.join(self.data_path, "*")):
+            self.add_iface(path)
+
+    def get_ifindex(self, iface):
+        """ Get the interface index from sysfs
+
+        """
+        path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
+        if not path.startswith(SYSFS_NET):
+            return None
+
+        ifindex = None
+
+        try:
+            f = open(path, 'r')
+        except EnvironmentError:
+            logging.debug("%s is probably down, removing", iface)
+            self.remove_iface(iface)
+
+            return ifindex
+
+        try:
+            ifindex = f.readline().strip()
+            try:
+                ifindex = int(ifindex)
+            except ValueError, e:
+                logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
+                             " output '%s'", iface, ifindex)
+        except EnvironmentError, e:
+            logging.warn("Error reading %s's ifindex from sysfs: %s",
+                         iface, str(e))
+            self.remove_iface(iface)
+        finally:
+            f.close()
+
+        return ifindex
+
+
+    def get_iface_hw_addr(self, iface):
+        """ Get the interface hardware address from sysfs
+
+        """
+        path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
+        if not path.startswith(SYSFS_NET):
+            return None
+
+        addr = None
+        try:
+            f = open(path, 'r')
+        except EnvironmentError:
+            logging.debug("%s is probably down, removing", iface)
+            self.remove_iface(iface)
+            return addr
+
+        try:
+            addr = f.readline().strip()
+        except EnvironmentError, e:
+            logging.warn("Failed to read hw address for %s from sysfs: %s",
+                         iface, str(e))
+        finally:
+            f.close()
+
+        return addr
+
+    def add_iface(self, path):
+        """ Add an interface to monitor
+
+        """
+        iface = os.path.basename(path)
+
+        logging.debug("Updating configuration for %s", iface)
+        binding = parse_binding_file(path)
+        if binding is None:
+            return
+        ifindex = self.get_ifindex(binding.iface)
+
+        if ifindex is None:
+            logging.warn("Stale configuration for %s found", iface)
+        else:
+            if binding.is_valid():
+                self.clients[binding.mac] = binding
+                self.subnets[binding.link] = parse_routing_table(binding.link)
+                logging.debug("Added client %s on %s", binding.hostname, iface)
+                self.ifaces[ifindex] = binding.iface
+                self.v6nets[iface] = parse_routing_table(binding.link, 6)
+
+    def remove_iface(self, ifname):
+        """ Cleanup clients on a removed interface
+
+        """
+        if ifname in self.v6nets:
+            del self.v6nets[ifname]
+
+        for mac in self.clients.keys():
+            if self.clients[mac].ifname == ifname:
+                iface = self.client[mac].iface
+                del self.clients[mac]
+
+        for ifindex in self.ifaces.keys():
+            if self.ifaces[ifindex] == ifname == iface:
+                del self.ifaces[ifindex]
+
+        logging.debug("Removed interface %s", ifname)
+
+    def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
+        """ Generate a reply to a BOOTP/DHCP request
+
+        """
+        logging.info("%s",payload)
+        indev = payload.get_indev()
+        try:
+            # Get the actual interface from the ifindex
+            iface = self.ifaces[indev]
+        except KeyError:
+            # We don't know anything about this interface, so accept the packet
+            # and return
+            logging.debug("Ignoring DHCP request on unknown iface %d", indev)
+            # We don't know what to do with this packet, so let the kernel
+            # handle it
+            payload.set_verdict(nfqueue.NF_ACCEPT)
+            return
+
+        # Decode the response - NFQUEUE relays IP packets
+        pkt = IP(payload.get_data())
+
+        # Signal the kernel that it shouldn't further process the packet
+        payload.set_verdict(nfqueue.NF_DROP)
+
+        # Get the client MAC address
+        resp = pkt.getlayer(BOOTP).copy()
+        hlen = resp.hlen
+        mac = resp.chaddr[:hlen].encode("hex")
+        mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
+
+       logging.info("%s %s %s ", resp, hlen, mac)
+        # Server responses are always BOOTREPLYs
+        resp.op = "BOOTREPLY"
+        del resp.payload
+
+        try:
+            binding = self.clients[mac]
+        except KeyError:
+            logging.warn("Invalid client %s on %s", mac, iface)
+            return
+
+        if iface != binding.iface:
+            logging.warn("Received spoofed DHCP request for %s from interface"
+                         " %s instead of %s", mac, iface, binding.iface)
+            return
+
+        resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
+               IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
+               UDP(sport=pkt.dport, dport=pkt.sport)/resp
+        subnet = self.subnets[binding.link]
+
+        if not DHCP in pkt:
+            logging.warn("Invalid request from %s on %s, no DHCP"
+                         " payload found", binding.mac, iface)
+            return
+
+        dhcp_options = []
+        requested_addr = binding.ip
+        for opt in pkt[DHCP].options:
+            if type(opt) is tuple and opt[0] == "message-type":
+                req_type = opt[1]
+            if type(opt) is tuple and opt[0] == "requested_addr":
+                requested_addr = opt[1]
+
+        logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
+                     binding.mac, iface)
+
+        if req_type == DHCPREQUEST and requested_addr != binding.ip:
+            resp_type = DHCPNAK
+            logging.info("Sending DHCPNAK to %s on %s: requested %s"
+                         " instead of %s", binding.mac, iface, requested_addr,
+                         binding.ip)
+
+        elif req_type in (DHCPDISCOVER, DHCPREQUEST):
+            resp_type = DHCP_REQRESP[req_type]
+            resp.yiaddr = self.clients[mac].ip
+            dhcp_options += [
+                 ("hostname", binding.hostname),
+                 ("domain", binding.hostname.split('.', 1)[-1]),
+                 ("router", subnet.gw),
+                 ("broadcast_address", str(subnet.broadcast)),
+                 ("subnet_mask", str(subnet.netmask)),
+                 ("renewal_time", self.lease_renewal),
+                 ("lease_time", self.lease_lifetime),
+            ]
+            dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
+
+        elif req_type == DHCPINFORM:
+            resp_type = DHCP_REQRESP[req_type]
+            dhcp_options += [
+                 ("hostname", binding.hostname),
+                 ("domain", binding.hostname.split('.', 1)[-1]),
+            ]
+            dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
+
+        elif req_type == DHCPRELEASE:
+            # Log and ignore
+            logging.info("DHCPRELEASE from %s on %s", binding.mac, iface)
+            return
+
+        # Finally, always add the server identifier and end options
+        dhcp_options += [
+            ("message-type", resp_type),
+            ("server_id", DHCP_DUMMY_SERVER_IP),
+            "end"
+        ]
+        resp /= DHCP(options=dhcp_options)
+
+        logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
+                     binding.ip, iface)
+        self.sendp(resp, iface)
+
+    def rs_response(self, i, payload): # pylint: disable=W0613
+        """ Generate a reply to a BOOTP/DHCP request
+
+        """
+        indev = payload.get_indev()
+        try:
+            # Get the actual interface from the ifindex
+            iface = self.ifaces[indev]
+        except KeyError:
+            logging.debug("Ignoring router solicitation on"
+                          " unknown interface %d", indev)
+            # We don't know what to do with this packet, so let the kernel
+            # handle it
+            payload.set_verdict(nfqueue.NF_ACCEPT)
+            return
+
+        ifmac = self.get_iface_hw_addr(iface)
+        subnet = self.v6nets[iface]
+        ifll = subnet.make_ll64(ifmac)
+
+        # Signal the kernel that it shouldn't further process the packet
+        payload.set_verdict(nfqueue.NF_DROP)
+
+        resp = Ether(src=self.get_iface_hw_addr(iface))/\
+               IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
+               ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
+                                     prefixlen=subnet.prefixlen)
+
+        if self.ipv6_nameservers:
+            resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
+                                     lifetime=self.ra_period * 3)
+
+        logging.info("RA on %s for %s", iface, subnet.net)
+        self.sendp(resp, iface)
+
+    def ns_response(self, i, payload): # pylint: disable=W0613
+        """ Generate a reply to an ICMPv6 neighbor solicitation
+
+        """
+        indev = payload.get_indev()
+        try:
+            # Get the actual interface from the ifindex
+            iface = self.ifaces[indev]
+        except KeyError:
+            logging.debug("Ignoring neighbour solicitation on"
+                          " unknown interface %d", indev)
+            # We don't know what to do with this packet, so let the kernel
+            # handle it
+            payload.set_verdict(nfqueue.NF_ACCEPT)
+            return
+
+        ifmac = self.get_iface_hw_addr(iface)
+        subnet = self.v6nets[iface]
+        ifll = subnet.make_ll64(ifmac)
+
+        ns = IPv6(payload.get_data())
+
+        if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
+            logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
+            payload.set_verdict(nfqueue.NF_ACCEPT)
+            return 1
+
+        payload.set_verdict(nfqueue.NF_DROP)
+
+        try:
+            client_lladdr = ns.lladdr
+        except AttributeError:
+            return 1
+
+        resp = Ether(src=ifmac, dst=client_lladdr)/\
+               IPv6(src=str(ifll), dst=ns.src)/\
+               ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
+               ICMPv6NDOptDstLLAddr(lladdr=ifmac)
+
+        logging.info("NA on %s for %s", iface, ns.tgt)
+        self.sendp(resp, iface)
+        return 1
+
+    def send_periodic_ra(self):
+        # Use a separate thread as this may take a _long_ time with
+        # many interfaces and we want to be responsive in the mean time
+        threading.Thread(target=self._send_periodic_ra).start()
+
+    def _send_periodic_ra(self):
+        logging.debug("Sending out periodic RAs")
+        start = time.time()
+        i = 0
+        for client in self.clients.values():
+            iface = client.iface
+            ifmac = self.get_iface_hw_addr(iface)
+            if not ifmac:
+                continue
+
+            subnet = self.v6nets[iface]
+            if subnet.net is None:
+                logging.debug("Skipping periodic RA on interface %s,"
+                              " as it is not IPv6-connected", iface)
+                continue
+
+            ifll = subnet.make_ll64(ifmac)
+            resp = Ether(src=ifmac)/\
+                   IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
+                   ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
+                                         prefixlen=subnet.prefixlen)
+            if self.ipv6_nameservers:
+                resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
+                                         lifetime=self.ra_period * 3)
+            try:
+                self.sendp(resp, iface)
+            except socket.error, e:
+                logging.warn("Periodic RA on %s failed: %s", iface, str(e))
+            except Exception, e:
+                logging.warn("Unkown error during periodic RA on %s: %s",
+                             iface, str(e))
+            i += 1
+        logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
+
+    def serve(self):
+        """ Safely perform the main loop, freeing all resources upon exit
+
+        """
+        try:
+            self._serve()
+        finally:
+            self._cleanup()
+
+    def _serve(self):
+        """ Loop forever, serving DHCP requests
+
+        """
+        self.build_config()
+
+        # Yes, we are accessing _fd directly, but it's the only way to have a
+        # single select() loop ;-)
+        iwfd = self.notifier._fd # pylint: disable=W0212
+
+        start = time.time()
+        if self.ipv6_enabled:
+            timeout = self.ra_period
+            self.send_periodic_ra()
+        else:
+            timeout = None
+
+        while True:
+            rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
+            if xlist:
+                logging.warn("Warning: Exception on %s",
+                             ", ".join([ str(fd) for fd in xlist]))
+
+            if rlist:
+                if iwfd in rlist:
+                # First check if there are any inotify (= configuration change)
+                # events
+                    self.notifier.read_events()
+                    self.notifier.process_events()
+                    rlist.remove(iwfd)
+
+                for fd in rlist:
+                    try:
+                        self.nfq[fd].process_pending()
+                    except RuntimeError, e:
+                        logging.warn("Error processing fd %d: %s", fd, str(e))
+                    except Exception, e:
+                        logging.warn("Unknown error processing fd %d: %s",
+                                     fd, str(e))
+
+            if self.ipv6_enabled:
+                # Calculate the new timeout
+                timeout = self.ra_period - (time.time() - start)
+
+                if timeout <= 0:
+                    start = time.time()
+                    self.send_periodic_ra()
+                    timeout = self.ra_period - (time.time() - start)
+
+
+if __name__ == "__main__":
+    import capng
+    import optparse
+    from cStringIO import StringIO
+    from pwd import getpwnam, getpwuid
+    from configobj import ConfigObj, ConfigObjError, flatten_errors
+
+    import validate
+
+    validator = validate.Validator()
+
+    def is_ip_list(value, family=4):
+        try:
+            family = int(family)
+        except ValueError:
+            raise validate.VdtParamError(family)
+        if isinstance(value, (str, unicode)):
+            value = [value]
+        if not isinstance(value, list):
+            raise validate.VdtTypeError(value)
+
+        for entry in value:
+            try:
+                ip = IPy.IP(entry)
+            except ValueError:
+                raise validate.VdtValueError(entry)
+
+            if ip.version() != family:
+                raise validate.VdtValueError(entry)
+        return value
+
+    validator.functions["ip_addr_list"] = is_ip_list
+    config_spec = StringIO(CONFIG_SPEC)
+
+
+    parser = optparse.OptionParser()
+    parser.add_option("-c", "--config", dest="config_file",
+                      help="The location of the data files", metavar="FILE",
+                      default=DEFAULT_CONFIG)
+    parser.add_option("-d", "--debug", action="store_true", dest="debug",
+                      help="Turn on debugging messages")
+    parser.add_option("-f", "--foreground", action="store_false",
+                      dest="daemonize", default=True,
+                      help="Do not daemonize, stay in the foreground")
+
+
+    opts, args = parser.parse_args()
+
+    try:
+        config = ConfigObj(opts.config_file, configspec=config_spec)
+    except ConfigObjError, err:
+        sys.stderr.write("Failed to parse config file %s: %s" %
+                         (opts.config_file, str(err)))
+        sys.exit(1)
+
+    results = config.validate(validator)
+    if results != True:
+        logging.fatal("Configuration file validation failed! See errors below:")
+        for (section_list, key, unused) in flatten_errors(config, results):
+            if key is not None:
+                logging.fatal(" '%s' in section '%s' failed validation",
+                              key, ", ".join(section_list))
+            else:
+                logging.fatal(" Section '%s' is missing",
+                              ", ".join(section_list))
+        sys.exit(1)
+
+    logger = logging.getLogger()
+    if opts.debug:
+        logger.setLevel(logging.DEBUG)
+    else:
+        logger.setLevel(logging.INFO)
+
+    if opts.daemonize:
+        logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
+        handler = logging.handlers.RotatingFileHandler(logfile,
+                                                       maxBytes=2097152)
+    else:
+        handler = logging.StreamHandler()
+
+    handler.setFormatter(logging.Formatter(LOG_FORMAT))
+    logger.addHandler(handler)
+
+    if opts.daemonize:
+        pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
+            config["general"]["pidfile"], 10)
+
+        d = daemon.DaemonContext(pidfile=pidfile,
+                                 stdout=handler.stream,
+                                 stderr=handler.stream,
+                                 files_preserve=[handler.stream])
+        d.umask = 0022
+        d.open()
+
+    logging.info("Starting up")
+
+    proxy_opts = {}
+    if config["dhcp"].as_bool("enable_dhcp"):
+        proxy_opts.update({
+            "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
+            "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
+            "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
+            "dhcp_server_ip": config["dhcp"]["server_ip"],
+            "dhcp_nameservers": config["dhcp"]["nameservers"],
+        })
+
+    if config["ipv6"].as_bool("enable_ipv6"):
+        proxy_opts.update({
+            "rs_queue_num": config["ipv6"].as_int("rs_queue"),
+            "ns_queue_num": config["ipv6"].as_int("ns_queue"),
+            "ra_period": config["ipv6"].as_int("ra_period"),
+            "ipv6_nameservers": config["ipv6"]["nameservers"],
+        })
+
+    # pylint: disable=W0142
+    proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
+
+    # Drop all capabilities except CAP_NET_RAW and change uid
+    try:
+        uid = getpwuid(config["general"].as_int("user"))
+    except ValueError:
+        uid = getpwnam(config["general"]["user"])
+
+    logging.debug("Setting capabilities and changing uid")
+    logging.debug("User: %s, uid: %d, gid: %d",
+                  config["general"]["user"], uid.pw_uid, uid.pw_gid)
+
+    # Keep only the capabilities we need
+    # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
+    capng.capng_clear(capng.CAPNG_SELECT_BOTH)
+    capng.capng_update(capng.CAPNG_ADD,
+                       capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED,
+                       capng.CAP_NET_ADMIN)
+    capng.capng_change_id(uid.pw_uid, uid.pw_gid,
+                          capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING)
+
+    logging.info("Ready to serve requests")
+    try:
+        proxy.serve()
+    except Exception:
+        if opts.daemonize:
+            exc = "".join(traceback.format_exception(*sys.exc_info()))
+            logging.critical(exc)
+        raise
+
+
+# vim: set ts=4 sts=4 sw=4 et :
diff --git a/nfdhcpd/nfdhcpd.conf b/nfdhcpd/nfdhcpd.conf
new file mode 100644 (file)
index 0000000..9937dde
--- /dev/null
@@ -0,0 +1,26 @@
+## nfdhcpd sample configuration file
+## General options
+[general]
+pidfile = /var/run/nfdhcpd.pid
+datapath = /var/lib/nfdhcpd # Where the client configuration will be read from
+logdir = /var/log/nfdhcpd   # Where to write our logs
+user = nobody # An unprivileged user to run as
+
+## DHCP options
+[dhcp]
+enable_dhcp = yes
+lease_lifetime = 604800 # 1 week
+lease_renewal = 3600   # 1 hour
+server_ip = 192.0.2.1
+dhcp_queue = 42 # NFQUEUE number to listen on for DHCP requests
+# IPv4 nameservers to include in DHCP responses
+nameservers = 192.0.2.2, 192.0.2.3
+
+## IPv6-related functionality
+[ipv6]
+enable_ipv6 = yes
+ra_period = 300 # seconds
+rs_queue = 43 # NFQUEUE number to listen on for router solicitations
+ns_queue = 44 # NFQUEUE number to listen on for neighbor solicitations
+# IPv6 nameservers to send using the ICMPv6 RA RDNSS option (RFC 5006)
+nameservers = 2001:db8:100::1, 2001:db8:200::2
diff --git a/nfdhcpd/nfdhcpd.ferm b/nfdhcpd/nfdhcpd.ferm
new file mode 100644 (file)
index 0000000..9b737d5
--- /dev/null
@@ -0,0 +1,16 @@
+domain ip {
+       table mangle {
+               chain PREROUTING {
+                       proto udp dport 67 NFQUEUE queue-num 42;
+               }
+       }
+}
+
+domain ip6 {
+       table mangle {
+               chain PREROUTING {
+                       proto icmpv6 icmpv6-type router-solicitation NFQUEUE queue-num 43;
+                       proto icmpv6 icmpv6-type neighbour-solicitation NFQUEUE queue-num 44;
+               }
+       }
+}
diff --git a/pylintrc b/pylintrc
new file mode 100644 (file)
index 0000000..37a6102
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,110 @@
+# Configuration file for pylint (http://www.logilab.org/project/pylint). See
+# http://www.logilab.org/card/pylintfeatures for more detailed variable
+# descriptions.
+
+[MASTER]
+profile = no
+ignore =
+persistent = no
+cache-size = 50000
+load-plugins =
+
+[REPORTS]
+output-format = colorized
+include-ids = yes
+files-output = no
+reports = yes
+evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+comment = yes
+
+[BASIC]
+required-attributes =
+no-docstring-rgx = __.*__
+#no-docstring-rgx = .*
+module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+# added lower-case names
+const-rgx = ((_{0,2}[A-Za-z][A-Za-z0-9_]*)|(__.*__))$
+class-rgx = _?[A-Z][a-zA-Z0-9]+$
+# added lower-case names
+function-rgx = (_?([A-Z]+[a-z0-9]+([A-Z]+[a-z0-9]*)*)|main|([a-z_][a-z0-9_]*))$
+# add lower-case names, since derived classes must obey method names
+method-rgx = (_{0,2}[A-Z]+[a-z0-9]+([A-Z]+[a-z0-9]*)*|__.*__|([a-z_][a-z0-9_]*))$
+attr-rgx = [a-z_][a-z0-9_]{1,30}$
+argument-rgx = [a-z_][a-z0-9_]*$
+variable-rgx = (_?([a-z_][a-z0-9_]*)|(_?[A-Z0-9_]+))$
+inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
+good-names = i,j,k,_,d
+bad-names = foo,bar,baz,toto,tutu,tata,koko,lala,kot
+bad-functions = xrange
+
+[TYPECHECK]
+ignore-mixin-members = yes
+zope = no
+acquired-members =
+
+[VARIABLES]
+init-import = no
+dummy-variables-rgx = _
+additional-builtins =
+
+[CLASSES]
+ignore-iface-methods =
+defining-attr-methods = __init__,__new__,setUp
+
+[DESIGN]
+max-args = 15
+max-locals = 50
+max-returns = 10
+max-branchs = 80
+max-statements = 200
+max-parents = 7
+max-attributes = 20
+# zero as struct-like (PODS) classes don't export any methods
+min-public-methods = 0
+max-public-methods = 50
+
+[IMPORTS]
+deprecated-modules = regsub,string,TERMIOS,Bastion,rexec
+import-graph =
+ext-import-graph =
+int-import-graph =
+
+[FORMAT]
+max-line-length = 80
+max-module-lines = 10000
+indent-string = "    "
+
+[MISCELLANEOUS]
+notes = FIXME,XXX,TODO
+
+[SIMILARITIES]
+min-similarity-lines = 4
+ignore-comments = yes
+ignore-docstrings = yes
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+disable-checker=similarities
+
+# Enable all messages in the listed categories (IRCWEF).
+#enable-msg-cat=
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+disable-msg=W0511,R0922,W0201
+
+# The new pylint 0.21+ style (plus the similarities checker, which is no longer
+# a separate opiton, but a generic disable control)
+disable=W0511,R0922,W0201,R0922,R0801,I0011
diff --git a/remove-network b/remove-network
new file mode 100644 (file)
index 0000000..5477f9a
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+DIR=/var/lib/snf-network
+NAME=$1
+RT_TABLES=/etc/iproute2/rt_tables
+
+
+
+if [ $# -ne 1 ]; then
+  echo "$0 <name>"
+  exit 1
+fi
+
+# remove old entry
+sed -i '/rt_'"$NAME"'$/ d' $RT_TABLES
+
+rm $DIR/networks/$NAME 
diff --git a/rt_tables b/rt_tables
new file mode 100644 (file)
index 0000000..448f1e0
--- /dev/null
+++ b/rt_tables
@@ -0,0 +1,18 @@
+#
+# reserved values
+#
+255 local
+254 main
+253 default
+0 unspec
+#
+# local
+#
+#1  inr.ruhep
+# dev.grnet.gr, routing table used
+# for the public IP space allocated to Synnefo VMs
+# This *must* match the name of the link
+# in gnt-network for nfdhcpd to work properly.
+44  rt_net100
+45  rt_net101
+46  rt_public
diff --git a/tools/mac2eui64 b/tools/mac2eui64
new file mode 100755 (executable)
index 0000000..366073c
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+import sys
+
+from IPy import IP
+
+if len(sys.argv) != 3:
+    sys.stderr.write("Usage: %s <mac_address> <IPv6 prefix>\n" % sys.argv[0])
+    sys.exit(127)
+
+mac = sys.argv[1]
+try:
+    prefix = IP(sys.argv[2])
+except ValueError:
+    sys.stderr.write("Invalid IPv6 prefix '%s'\n" % sys.argv[2])
+    sys.exit(1)
+    
+
+if prefix.version() != 6:
+    sys.stderr.write("%s is not a valid IPv6 prefix\n" % prefix)
+    sys.exit(1)
+
+if prefix.prefixlen() != 64:
+    sys.stderr.write("Cannot generate an EUI-64 address on a non-64 subnet\n")
+    sys.exit(1)
+
+mac_parts = mac.split(":")
+pfx_parts = prefix.net().strFullsize().split(":")
+
+if len(mac_parts) != 6:
+    sys.stderr.write("%s is not a valid MAC-48 address\n" % mac)
+    sys.exit(1)
+
+eui64 = mac_parts[:3] + [ "ff", "fe" ] + mac_parts[3:]
+
+eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
+
+ip = ":".join(pfx_parts[:4])
+for l in range(0, len(eui64), 2):
+    ip += ":%s" % "".join(eui64[l:l+2])
+
+try:
+    print IP(ip).strCompressed()
+except ValueError, e:
+    sys.stderr.write("Ooops, something went wrong: '%s'!\n" % str(e))
+
+# vim: set ts=4 sts=4 sw=4 et ai :
diff --git a/vlans/if-down.d/vmrouter b/vlans/if-down.d/vmrouter
new file mode 100755 (executable)
index 0000000..2a52b7d
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+if [ ! -n "$IF_IP_ROUTING_TABLE" ]; then
+       # bail out early if there's no ip-routing-table defined
+       exit 0
+fi
+
+if [ "$ADDRFAM" = "inet" ]; then
+       IP="ip"
+elif [ "$ADDRFAM" = "inet6" ]; then
+       IP="ip -6"
+else
+       exit 0
+fi
+
+while $IP rule del iif $IFACE;do :; done 2>/dev/null
+
+if [ "$ADDRFAM" = "inet" ]; then
+       if [ -n "$IF_ARP_IP" ]; then
+               arptables -D OUTPUT -o $IFACE --opcode request -j mangle 2>/dev/null || true
+       fi
+fi
diff --git a/vlans/if-up.d/vmrouter b/vlans/if-up.d/vmrouter
new file mode 100755 (executable)
index 0000000..9317bdf
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# if-up.d hook to do some weird networking stuff on IP-less interfaces
+#
+# Example:
+#
+# auto bond0.90
+# iface bond0.90 inet manual
+#         ip-routing-table 90
+#         ip-routes 62.217.124.0/26
+#         ip-gateway 62.217.124.1
+#         ip-forwarding 1
+#         ip-proxy-arp 1
+#         arp-ip 62.217.124.58
+#
+# iface bond0.90 inet6 manual
+#         ip-routing-table 90
+#         ip-routes 2001:648:2ffc:105::/64
+#         ip-gateway 2001:648:2ffc:105::1
+#         ip-forwarding 1
+#         ip-proxy-ndp 1
+
+PATH=/usr/sbin:/usr/bin:/sbin:/bin
+
+ip link set $IFACE up
+
+if [ "$ADDRFAM" = "inet" ]; then
+       IP="ip"
+elif [ "$ADDRFAM" = "inet6" ]; then
+       IP="ip -6"
+else
+       exit 0
+fi
+
+if [ -n "$IF_IP_ROUTING_TABLE" ]; then
+       TABLE="table $IF_IP_ROUTING_TABLE"
+else
+       # bail out early if there's no ip-routing-table defined
+       exit 0
+fi
+
+while $IP rule del iif $IFACE;do :; done 2>/dev/null
+$IP rule add iif $IFACE $TABLE
+
+for route in $IF_IP_ROUTES; do
+       $IP route add $route dev $IFACE table main
+       $IP route add $route dev $IFACE $TABLE
+done
+
+if [ -n "$IF_IP_GATEWAY" ]; then
+       $IP route add default via $IF_IP_GATEWAY dev $IFACE $TABLE
+fi
+
+if [ -n "$IF_IP_FORWARDING" ]; then
+       [ "$ADDRFAM" = "inet" ] && echo 1 > /proc/sys/net/ipv4/conf/all/forwarding
+       [ "$ADDRFAM" = "inet6" ] && echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
+fi
+
+[ "$IF_IP_PROXY_NDP" = "1" ] && [ "$ADDRFAM" = "inet6" ] && echo 1 > /proc/sys/net/ipv6/conf/$IFACE/proxy_ndp
+
+if [ "$ADDRFAM" = "inet" ]; then
+       if [ -n "$IF_ARP_IP" ]; then
+               arptables -D OUTPUT -o $IFACE --opcode request -j mangle 2>/dev/null || true
+               arptables -A OUTPUT -o $IFACE --opcode request -j mangle --mangle-ip-s $IF_ARP_IP
+       fi
+fi
diff --git a/vlans/prv-net-helper b/vlans/prv-net-helper
new file mode 100755 (executable)
index 0000000..f760481
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+function usage {
+       echo "Usage: $0 <mode> <parent interface> <prv min> <prv max> <offset>"
+       exit 1
+}
+
+if [ $# -ne 5 ]; then
+       usage
+fi
+
+function up {
+       iface=$1
+       prv_min=$2
+       prv_max=$3
+       offset=$4
+
+       echo "Adding VLANs $2 - $3"
+       vconfig set_name_type DEV_PLUS_VID_NO_PAD
+       for prv in $(seq $prv_min $prv_max); do
+               vlan=$(($prv+$offset))
+               bridge=prv$prv
+
+               vconfig add $iface $vlan
+               ifconfig $iface.$vlan up
+               brctl addbr $bridge
+               brctl setfd $bridge 0
+               brctl addif $bridge $iface.$vlan # dev_plus_vid
+               ifconfig $bridge up
+       done
+}
+
+function down {
+       iface=$1
+       prv_min=$2
+       prv_max=$3
+       offset=$4
+
+       echo "Removing VLANs $2 - $3"
+       for prv in $(seq $prv_min $prv_max); do
+               vlan=$(($prv+$offset))
+               bridge=prv$prv
+
+               (
+               ifconfig $bridge down
+               brctl delif $bridge $iface.$vlan # dev_plus_vid
+               vconfig rem $iface.$vlan
+               brctl delbr $bridge
+               ) 2>/dev/null
+       done
+}
+
+mode=$1; shift
+if [ "$mode" = "up" ]; then
+       up $@
+elif [ "$mode" = "down" ]; then
+       down $@
+else
+       usage
+fi
diff --git a/vlans/vlan b/vlans/vlan
new file mode 100755 (executable)
index 0000000..4c40f38
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+
+add_vlan() {
+       if [ -n "`echo -n "$1" | tr -d '[0-9]'`" ]; then
+               echo "Invalid vlan tag $1"
+               exit 1
+       fi
+               
+       vlan=$1
+       ifce=$2
+
+       if [ -d "/sys/class/net/vlan${vlan}/bridge" ]; then
+               echo "Vlan $vlan already configured"
+               exit 0
+       fi
+
+       if ( grep -q "iface vlan${vlan}$" /etc/network/interfaces ); then
+               echo "Vlan $vlan configured but down, bringing up"
+       else
+               echo "Adding vlan $vlan to /etc/network/interfaces"
+               cat >>/etc/network/interfaces <<EOF
+auto vlan${vlan}
+iface vlan${vlan} inet manual
+       bridge_ports    ${ifce}.${vlan}
+       bridge_stp      off
+       bridge_maxwait  0
+       bridge_fd       0
+
+EOF
+       fi
+
+       /sbin/ifup "vlan${vlan}" >/dev/null 2>&1
+       exit 0
+}
+
+list_vlans() {
+       for iface in /sys/class/net/vlan*; do
+               if [ -d "$iface/bridge" ]; then
+                       vlan=`basename "$iface"`
+                       ( grep -q "iface $vlan$" /etc/network/interfaces )
+                       if [ $? == 0 ]; then
+                               echo "${vlan##vlan}"
+                       else
+                               echo "${vlan##vlan} (unconfigured)"
+                       fi
+               fi
+       done
+
+}
+
+case "$1" in
+       add)
+       if [ x"$3" != x"" ]; then
+               ifce=$3
+       else
+               ifce="bond0"
+       fi
+       add_vlan "$2" "$ifce"
+       ;;
+       remove)
+       remove_vlan "$2"
+       ;;
+       list)
+       list_vlans
+       ;;
+       *)
+       echo "Usage: vlan (add number [ifce="bond0"]|remove number|list)"
+       ;;
+esac;
+