Revision cf51ea5b
b/README | ||
---|---|---|
32 | 32 |
Single bridge setup. Private IPs. Masquerade: |
33 | 33 |
|
34 | 34 |
For security and not being able to change ip-mac-tap key: |
35 |
# ebtables -t filter -D INPUT -i tap0 -j TAP0 |
|
36 |
# ebtables -t filter -D FORWARD -i tap0 -j TAP0 |
|
37 |
# ebtables -t filter -X TAP0 |
|
38 |
# ebtables -t filter -N TAP0 |
|
39 |
# ebtables -t filter -A TAP0 --ip-source \! 192.168.100.2 -p ipv4 -j DROP |
|
40 |
# ebtables -t filter -A TAP0 -s \! aa:00:00:8c:d3:a4 -j DROP |
|
41 |
# ebtables -t filter -A INPUT -i tap0 -j TAP0 (for masquerading) |
|
42 |
# ebtables -t filter -A FORWARD -i tap0 -j TAP0 (for private lans) |
|
43 |
|
|
35 |
# ebtables -N FROMTAP0 |
|
36 |
# ebtables -A FROMTAP0 --ip-source \! 192.168.100.2 -p ipv4 -j DROP |
|
37 |
# ebtables -A FROMTAP0 -s \! aa:00:00:8c:d3:a4 -j DROP |
|
38 |
# ebtables -A INPUT -i tap0 -j FROMTAP0 (for masquerading) |
|
39 |
# ebtables -A FORWARD -i tap0 -j FROMTAP0 (for private lans) |
|
40 |
# ebtables -N TOTAP0 |
|
41 |
# ebtables -A FORWARD -o tap0 -j TOTAP0 |
|
42 |
# ebtables -A OUTPUT -o tap0 -j TOTAP0 |
|
43 |
# ebtables -A TOTAP0 -s 6e:10:e1:a0:c3:0f -j ACCEPT (from gateway) |
|
44 |
# ebtables -A TOTAP0 -s \! aa:0:0:8c:d3:a4/ff:ff:ff:ff:0:0 -j DROP |
|
44 | 45 |
|
45 | 46 |
|
46 | 47 |
Private LANs: |
b/add-network | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
DIR=/var/lib/snf-network |
|
4 |
SUBNET=$1 |
|
5 |
GATEWAY=$2 |
|
6 |
TYPE=$3 |
|
7 |
NAME=$4 |
|
8 |
RT_TABLES=/etc/iproute2/rt_tables |
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
if [ $# -ne 4 ]; then |
|
13 |
echo "$0 <subnet> <gateway> <private/public> <name>" |
|
14 |
exit 1 |
|
15 |
fi |
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
cat > $DIR/networks/$NAME <<EOF |
|
20 |
SUBNET=$SUBNET |
|
21 |
GATEWAY=$GATEWAY |
|
22 |
TYPE=$TYPE |
|
23 |
EOF |
|
24 |
|
|
25 |
|
|
26 |
IDX=$(ls $DIR/networks | wc -l) |
|
27 |
|
|
28 |
# remove old entry |
|
29 |
sed -i '/^'"$IDX"'\ / d' $RT_TABLES |
|
30 |
|
|
31 |
echo "$IDX rt_$NAME" >> $RT_TABLES |
|
32 |
|
|
33 |
|
b/add-nodegroup | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
DIR=/var/lib/snf-network |
|
4 |
NODES=$1 |
|
5 |
ROUTER=$2 |
|
6 |
IFACE=$3 |
|
7 |
VLAN=$4 |
|
8 |
VLANS=$5 |
|
9 |
NAME=$6 |
|
10 |
|
|
11 |
|
|
12 |
if [ $# -ne 6 ]; then |
|
13 |
echo "$0 <list_nodes> <router> <iface> <public_vlan> <list_of_private_vlans> <name>" |
|
14 |
echo "$0 'dev88 89' 'dev88' 'eth0' '101' '2990 2999' 'default'" |
|
15 |
exit 1 |
|
16 |
fi |
|
17 |
|
|
18 |
|
|
19 |
|
|
20 |
cat > $DIR/nodegroups/$NAME <<EOF |
|
21 |
ROUTER=$ROUTER |
|
22 |
INTERFACE=$IFACE |
|
23 |
PUBLIC_VLAN=$VLAN |
|
24 |
PRIVATE_VLANS=$VLANS |
|
25 |
EOF |
|
26 |
|
|
27 |
|
b/conf/default/nfdhcpd | ||
---|---|---|
1 |
# Defaults for nfdhcpd initscript |
|
2 |
# sourced by /etc/init.d/nfdhcpd |
|
3 |
# installed at /etc/default/nfdhcpd by the maintainer scripts |
|
4 |
|
|
5 |
# |
|
6 |
# This is a POSIX shell fragment |
|
7 |
# |
|
8 |
|
|
9 |
RUN="yes" |
|
10 |
|
|
11 |
# Additional options that are passed to the Daemon. |
|
12 |
DAEMON_OPTS="" |
b/conf/init.d/nfdhcpd | ||
---|---|---|
1 |
#!/bin/sh |
|
2 |
# |
|
3 |
# This is free software; you may redistribute it and/or modify |
|
4 |
# it under the terms of the GNU General Public License as |
|
5 |
# published by the Free Software Foundation; either version 2, |
|
6 |
# or (at your option) any later version. |
|
7 |
# |
|
8 |
# This is distributed in the hope that it will be useful, but |
|
9 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 |
# GNU General Public License for more details. |
|
12 |
# |
|
13 |
# You should have received a copy of the GNU General Public License with |
|
14 |
# the Debian operating system, in /usr/share/common-licenses/GPL; if |
|
15 |
# not, write to the Free Software Foundation, Inc., 59 Temple Place, |
|
16 |
# Suite 330, Boston, MA 02111-1307 USA |
|
17 |
# |
|
18 |
### BEGIN INIT INFO |
|
19 |
# Provides: nfdhcpd |
|
20 |
# Required-Start: $network $local_fs $remote_fs |
|
21 |
# Required-Stop: $remote_fs |
|
22 |
# Should-Start: |
|
23 |
# Should-Stop: |
|
24 |
# Default-Start: 2 3 4 5 |
|
25 |
# Default-Stop: 0 1 6 |
|
26 |
# Short-Description: NFQueue DHCP/RA server |
|
27 |
### END INIT INFO |
|
28 |
|
|
29 |
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin |
|
30 |
|
|
31 |
DAEMON=/usr/sbin/nfdhcpd |
|
32 |
NAME=nfdhcpd |
|
33 |
DESC="NFQUEUE-based DHCP/RA server" |
|
34 |
LOGDIR=/var/log/nfdhcpd |
|
35 |
|
|
36 |
PIDFILE=/var/run/$NAME.pid |
|
37 |
|
|
38 |
test -x $DAEMON || exit 0 |
|
39 |
|
|
40 |
. /lib/lsb/init-functions |
|
41 |
|
|
42 |
# Default options, these can be overriden by the information |
|
43 |
# at /etc/default/$NAME |
|
44 |
DAEMON_OPTS="" # Additional options given to the server |
|
45 |
|
|
46 |
DIETIME=2 # Time to wait for the server to die, in seconds |
|
47 |
# If this value is set too low you might not |
|
48 |
# let some servers to die gracefully and |
|
49 |
# 'restart' will not work |
|
50 |
|
|
51 |
STARTTIME=1 # Time to wait for the server to start, in seconds |
|
52 |
# If this value is set each time the server is |
|
53 |
# started (on start or restart) the script will |
|
54 |
# stall to try to determine if it is running |
|
55 |
# If it is not set and the server takes time |
|
56 |
# to setup a pid file the log message might |
|
57 |
# be a false positive (says it did not start |
|
58 |
# when it actually did) |
|
59 |
|
|
60 |
LOGFILE=$LOGDIR/$NAME.log # Server logfile |
|
61 |
#DAEMONUSER=nfdhcp # Users to run the daemons as. If this value |
|
62 |
# is set start-stop-daemon will chuid the server |
|
63 |
|
|
64 |
# Include defaults if available |
|
65 |
if [ -f /etc/default/$NAME ] ; then |
|
66 |
. /etc/default/$NAME |
|
67 |
fi |
|
68 |
|
|
69 |
# Use this if you want the user to explicitly set 'RUN' in |
|
70 |
# /etc/default/ |
|
71 |
if [ "x$RUN" != "xyes" ] ; then |
|
72 |
log_failure_msg "$NAME disabled, please adjust the configuration to your needs " |
|
73 |
log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it." |
|
74 |
exit 1 |
|
75 |
fi |
|
76 |
|
|
77 |
# Check that the user exists (if we set a user) |
|
78 |
# Does the user exist? |
|
79 |
set -e |
|
80 |
|
|
81 |
running_pid() { |
|
82 |
# Check if a given process pid's cmdline matches a given name |
|
83 |
pid=$1 |
|
84 |
name=$2 |
|
85 |
[ -z "$pid" ] && return 1 |
|
86 |
[ ! -d /proc/$pid ] && return 1 |
|
87 |
cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` |
|
88 |
# Is this the expected server |
|
89 |
[ "$cmd" != "$name" ] && return 1 |
|
90 |
return 0 |
|
91 |
} |
|
92 |
|
|
93 |
running() { |
|
94 |
# Check if the process is running looking at /proc |
|
95 |
# (works for all users) |
|
96 |
|
|
97 |
# No pidfile, probably no daemon present |
|
98 |
[ ! -f "$PIDFILE" ] && return 1 |
|
99 |
pid=`cat $PIDFILE` |
|
100 |
running_pid $pid python || return 1 |
|
101 |
return 0 |
|
102 |
} |
|
103 |
|
|
104 |
start_server() { |
|
105 |
start_daemon -p $PIDFILE $DAEMON $DAEMON_OPTS |
|
106 |
errcode=$? |
|
107 |
return $errcode |
|
108 |
} |
|
109 |
|
|
110 |
stop_server() { |
|
111 |
killproc -p $PIDFILE $DAEMON |
|
112 |
rrcode=$? |
|
113 |
return $errcode |
|
114 |
} |
|
115 |
|
|
116 |
reload_server() { |
|
117 |
[ ! -f "$PIDFILE" ] && return 1 |
|
118 |
pid=pidofproc $PIDFILE # This is the daemon's pid |
|
119 |
# Send a SIGHUP |
|
120 |
kill -1 $pid |
|
121 |
return $? |
|
122 |
} |
|
123 |
|
|
124 |
force_stop() { |
|
125 |
# Force the process to die killing it manually |
|
126 |
[ ! -e "$PIDFILE" ] && return |
|
127 |
if running ; then |
|
128 |
kill -15 $pid |
|
129 |
# Is it really dead? |
|
130 |
sleep "$DIETIME"s |
|
131 |
if running ; then |
|
132 |
kill -9 $pid |
|
133 |
sleep "$DIETIME"s |
|
134 |
if running ; then |
|
135 |
echo "Cannot kill $NAME (pid=$pid)!" |
|
136 |
exit 1 |
|
137 |
fi |
|
138 |
fi |
|
139 |
fi |
|
140 |
rm -f $PIDFILE |
|
141 |
} |
|
142 |
|
|
143 |
|
|
144 |
case "$1" in |
|
145 |
start) |
|
146 |
log_daemon_msg "Starting $DESC " "$NAME" |
|
147 |
# Check if it's running first |
|
148 |
if running ; then |
|
149 |
log_progress_msg "apparently already running" |
|
150 |
log_end_msg 0 |
|
151 |
exit 0 |
|
152 |
fi |
|
153 |
if start_server ; then |
|
154 |
# NOTE: Some servers might die some time after they start, |
|
155 |
# this code will detect this issue if STARTTIME is set |
|
156 |
# to a reasonable value |
|
157 |
[ -n "$STARTTIME" ] && sleep $STARTTIME # Wait some time |
|
158 |
if running ; then |
|
159 |
# It's ok, the server started and is running |
|
160 |
log_end_msg 0 |
|
161 |
else |
|
162 |
# It is not running after we did start |
|
163 |
log_end_msg 1 |
|
164 |
fi |
|
165 |
else |
|
166 |
# Either we could not start it |
|
167 |
log_end_msg 1 |
|
168 |
fi |
|
169 |
;; |
|
170 |
stop) |
|
171 |
log_daemon_msg "Stopping $DESC" "$NAME" |
|
172 |
if running ; then |
|
173 |
# Only stop the server if we see it running |
|
174 |
errcode=0 |
|
175 |
stop_server || errcode=$? |
|
176 |
log_end_msg $errcode |
|
177 |
else |
|
178 |
# If it's not running don't do anything |
|
179 |
log_progress_msg "apparently not running" |
|
180 |
log_end_msg 0 |
|
181 |
exit 0 |
|
182 |
fi |
|
183 |
;; |
|
184 |
force-stop) |
|
185 |
# First try to stop gracefully the program |
|
186 |
$0 stop |
|
187 |
if running; then |
|
188 |
# If it's still running try to kill it more forcefully |
|
189 |
log_daemon_msg "Stopping (force) $DESC" "$NAME" |
|
190 |
errcode=0 |
|
191 |
force_stop || errcode=$? |
|
192 |
log_end_msg $errcode |
|
193 |
fi |
|
194 |
;; |
|
195 |
restart|force-reload) |
|
196 |
log_daemon_msg "Restarting $DESC" "$NAME" |
|
197 |
errcode=0 |
|
198 |
stop_server || errcode=$? |
|
199 |
# Wait some sensible amount, some server need this |
|
200 |
[ -n "$DIETIME" ] && sleep $DIETIME |
|
201 |
start_server || errcode=$? |
|
202 |
[ -n "$STARTTIME" ] && sleep $STARTTIME |
|
203 |
running || errcode=$? |
|
204 |
log_end_msg $errcode |
|
205 |
;; |
|
206 |
status) |
|
207 |
|
|
208 |
log_daemon_msg "Checking status of $DESC" "$NAME" |
|
209 |
if running ; then |
|
210 |
log_progress_msg "running" |
|
211 |
log_end_msg 0 |
|
212 |
else |
|
213 |
log_progress_msg "apparently not running" |
|
214 |
log_end_msg 1 |
|
215 |
exit 1 |
|
216 |
fi |
|
217 |
;; |
|
218 |
reload) |
|
219 |
log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" |
|
220 |
log_warning_msg "cannot re-read the config file (use restart)." |
|
221 |
;; |
|
222 |
*) |
|
223 |
N=/etc/init.d/$NAME |
|
224 |
echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 |
|
225 |
exit 1 |
|
226 |
;; |
|
227 |
esac |
|
228 |
|
|
229 |
exit 0 |
b/connect-network | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
DIR=/var/lib/snf-network |
|
4 |
NETWORK=$1 |
|
5 |
NODEGROUP=$2 |
|
6 |
MODE=$3 |
|
7 |
LINK=$4 |
|
8 |
|
|
9 |
source /etc/default/snf-network |
|
10 |
|
|
11 |
if [ $# -ne 4 ]; then |
|
12 |
echo "$0 <network> <nodegroup> <mode> <link>" |
|
13 |
exit 1 |
|
14 |
fi |
|
15 |
|
|
16 |
NETWORK_FILE=$DIR/networks/$NETWORK |
|
17 |
NODEGROUP_FILE=$DIR/nodegoups/$NODEGROUP |
|
18 |
INTERFACES=$DIR/interfaces/$NETWORK-$NODEGROUP |
|
19 |
|
|
20 |
source $NETWORK_FILE |
|
21 |
source $NODEGROUP_FILE |
|
22 |
|
|
23 |
if [ $MODE == "routed" ]; then |
|
24 |
VLAN=$LINK |
|
25 |
if [ $TYPE == "public" ]; then |
|
26 |
APR_IP=$(ipcalc $SUBNET | grep HostMax | awk '{print $2}') |
|
27 |
cat > $INTERFACES<<EOF |
|
28 |
# $VLAN $MODE |
|
29 |
auto $VLAN |
|
30 |
iface $VLAN inet manual |
|
31 |
# ip-routing-table rt_$NETWORK |
|
32 |
# ip-routes $SUBNET |
|
33 |
# ip-gateway $GATEWAY |
|
34 |
# ip-forwarding 1 |
|
35 |
# ip-proxy-arp 1 |
|
36 |
# arp-ip $ARP_IP |
|
37 |
EOF |
|
38 |
ifup -i $INTERFACES $VLAN |
|
39 |
ip link set $VLAN up |
|
40 |
|
|
41 |
ip rule add iif $VLAN table rt_$NAME |
|
42 |
|
|
43 |
ip route add $SUBNET dev $VLAN table main |
|
44 |
|
|
45 |
ip route add $SUBNET dev $VLAN table rt_$NAME |
|
46 |
ip route add default via $GATEWAY dev $VLAN table rt_$NAME |
|
47 |
|
|
48 |
echo 1 > /proc/sys/net/ipv4/conf/all/forwarding |
|
49 |
|
|
50 |
arptables -A OUTPUT -o $VLAN --opcode request -j mangle --mangle-ip-s $ARP_IP |
|
51 |
fi |
|
52 |
fi |
|
53 |
|
|
54 |
|
|
55 |
|
|
56 |
if [ $MODE == "bridged" ]; then |
|
57 |
BRIDGE=$LINK |
|
58 |
echo 1 > /proc/sys/net/ipv4/ip_forward |
|
59 |
if [ $TYPE == "public" ]; then |
|
60 |
VLAN=$INTERFACE.$PUBLIC_VLAN_ID |
|
61 |
elif [ $TYPE == "private" ]; then |
|
62 |
VLAN_ID=${PRIVATE_VLAN_IDS%% *} |
|
63 |
VLAN_IDS=${PRIVATE_VLAN_IDS#* } |
|
64 |
sed -i 's/PRIVATE_VLAN_IDS/ s/=.*/='"VLAN_IDS"'/' $NODEGROUP_FILE |
|
65 |
#set -- $PRIVATE_VLAN_IDS |
|
66 |
#VLAN=$1 |
|
67 |
#shift |
|
68 |
#VLANS=$@ |
|
69 |
VLAN=$INTERFACE.$VLAN_ID |
|
70 |
fi |
|
71 |
cat > $INTERFACES <<EOF |
|
72 |
# $VLAN $MODE $BRIDGE |
|
73 |
auto $VLAN |
|
74 |
iface $VLAN inet manual |
|
75 |
|
|
76 |
auto $BRIDGE |
|
77 |
iface $BRIDGE inet manual |
|
78 |
bridge_ports $VLAN |
|
79 |
bridge_stp off |
|
80 |
bridge_fd 2 |
|
81 |
EOF |
|
82 |
ifup -i $INTERFACES $BRIDGE |
|
83 |
ip link set $VLAN up |
|
84 |
ip route add $SUBNET dev $BRIDGE table main |
|
85 |
|
|
86 |
ip route add $SUBNET dev $BRIDGE table rt_$NETWORK |
|
87 |
if [ ! -z $GATEWAY ]; then |
|
88 |
ip route add default via dev $BRIDGE table rt_$NETWORK |
|
89 |
if [ $TYPE == "private" ]; then |
|
90 |
if [ ! -z $ROUTER ]; then |
|
91 |
if [ $(hostname) == $ROUTER ]; then |
|
92 |
NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}') |
|
93 |
ip addr add $GATEWAY/$NETMASK dev $BRIDGE |
|
94 |
iptables -t nat -A POSTROUTING -s $SUBNET \! -d $SUBNET -j MASQUERADE |
|
95 |
fi |
|
96 |
fi |
|
97 |
fi |
|
98 |
fi |
|
99 |
fi |
b/disconnect-network | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
DIR=/var/lib/snf-network |
|
4 |
NETWORK=$1 |
|
5 |
NODEGROUP=$2 |
|
6 |
|
|
7 |
source /etc/default/snf-network |
|
8 |
|
|
9 |
if [ $# -ne 2 ]; then |
|
10 |
echo "$0 <network> <nodegroup>" |
|
11 |
exit 1 |
|
12 |
fi |
|
13 |
|
|
14 |
NETWORK_FILE=$DIR/networks/$NETWORK |
|
15 |
NODEGROUP_FILE=$DIR/nodegoups/$NODEGROUP |
|
16 |
INTERFACES=$DIR/interfaces/$NETWORK-$NODEGROUP |
|
17 |
|
|
18 |
read x VLAN BRIDGE < $INTERFACES |
|
19 |
|
|
20 |
VLAN_ID=${VLAN#*:} |
|
21 |
|
|
22 |
source $NETWORK_FILE |
|
23 |
source $NODEGROUP_FILE |
|
24 |
|
|
25 |
if [ $MODE == "routed" ]; then |
|
26 |
if [ $TYPE == "public" ]; then |
|
27 |
APR_IP=$(ipcalc $SUBNET | grep HostMax | awk '{print $2}') |
|
28 |
ip rule del iif $VLAN table rt_$NAME |
|
29 |
|
|
30 |
ip route del $SUBNET dev $VLAN table main |
|
31 |
|
|
32 |
ip route del $SUBNET dev $VLAN table rt_$NAME |
|
33 |
ip route del default via $GATEWAY dev $VLAN table rt_$NAME |
|
34 |
|
|
35 |
arptables -D OUTPUT -o $VLAN --opcode request -j mangle --mangle-ip-s $ARP_IP |
|
36 |
ifdown -i $INTERFACES $VLAN |
|
37 |
rm $INTERFACES |
|
38 |
fi |
|
39 |
fi |
|
40 |
|
|
41 |
|
|
42 |
|
|
43 |
if [ $MODE == "bridged" ]; then |
|
44 |
if [ $TYPE == "private" ]; then |
|
45 |
VLAN_IDS="$VLAN_ID $PRIVATE_VLAN_IDS" |
|
46 |
sed -i 's/PRIVATE_VLAN_IDS/ s/=.*/='"VLAN_IDS"'/' $NODEGROUP_FILE |
|
47 |
fi |
|
48 |
|
|
49 |
ip route del $SUBNET dev $BRIDGE table main |
|
50 |
|
|
51 |
ip route del $SUBNET dev $BRIDGE table rt_$NETWORK |
|
52 |
if [ ! -z $GATEWAY ]; then |
|
53 |
ip route del default via $GATEWAY dev $BRIDGE table rt_$NETWORK |
|
54 |
if [ $TYPE == "private" ]; then |
|
55 |
if [ ! -z $ROUTER ]; then |
|
56 |
if [ $(hostname) == $ROUTER ]; then |
|
57 |
NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}') |
|
58 |
ip addr del $GATEWAY/$NETMASK dev $LINK |
|
59 |
iptables -t nat -D POSTROUTING -s $SUBNET \! -d $SUBNET -j MASQUERADE |
|
60 |
fi |
|
61 |
fi |
|
62 |
fi |
|
63 |
fi |
|
64 |
ifdown -i $INTERFACES $BRIDGE |
|
65 |
rm $INTERFACES |
|
66 |
fi |
b/interfaces | ||
---|---|---|
1 |
# IP-less inteface, used to route public IPv4 |
|
2 |
# for Synnefo VMs |
|
3 |
auto eth0.101 |
|
4 |
iface eth0.101 inet manual |
|
5 |
ip-routing-table rt_public |
|
6 |
ip-routes 62.217.123.128/27 |
|
7 |
ip-gateway 62.217.123.129 |
|
8 |
ip-forwarding 1 |
|
9 |
ip-proxy-arp 1 |
|
10 |
arp-ip 62.217.123.158 |
|
11 |
|
|
12 |
#auto eth0.100 |
|
13 |
iface eth0.100 inet manual |
|
14 |
up ip link set eth0.100 up |
|
15 |
|
|
16 |
#auto br100 |
|
17 |
iface br100 inet static |
|
18 |
# needed for being the rooter for the VMs |
|
19 |
address 192.168.100.1 |
|
20 |
netmask 255.255.255.240 |
|
21 |
bridge_ports eth0.100 |
|
22 |
# needed by nfdhcpd to make DHCP responses |
|
23 |
up ip route add 192.168.100.0/28 dev br100 table rt_net100 |
|
24 |
up ip route add default via 192.168.100.1 dev br100 table rt_net100 |
|
25 |
# needed for the VMs to connect to the world |
|
26 |
up iptables -t nat -A POSTROUTING -s 192.168.100.0/28 \! -d 192.168.100.0/28 -j MASQUERADE |
|
27 |
down iptables -t nat -D POSTROUTING -s 192.168.100.0/28 \! -d 192.168.100.0/28 -j MASQUERADE |
|
28 |
bridge_stp off |
|
29 |
bridge_fd 2 |
|
30 |
|
|
31 |
#auto br100:1 |
|
32 |
iface br100:1 inet static |
|
33 |
# needed for being the rooter for the VMs |
|
34 |
address 192.168.101.1 |
|
35 |
netmask 255.255.255.240 |
|
36 |
up ip route add 192.168.101.0/28 dev br100 table rt_net101 |
|
37 |
up ip route add default via 192.168.101.1 dev br100 table rt_net101 |
|
38 |
# needed for the VMs to connect to the world |
|
39 |
up iptables -t nat -A POSTROUTING -s 192.168.101.0/28 \! -d 192.168.101.0/28 -j MASQUERADE |
|
40 |
down iptables -t nat -D POSTROUTING -s 192.168.101.0/28 \! -d 192.168.101.0/28 -j MASQUERADE |
|
41 |
|
b/kvm-vif-bridge | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
# This is an example of a Ganeti kvm ifup script that configures network |
|
4 |
# interfaces based on the initial deployment of the Okeanos project |
|
5 |
|
|
6 |
TAP_CONSTANT_MAC=cc:47:52:4e:45:54 # GRNET in hex :-) |
|
7 |
MAC2EUI64=/usr/bin/mac2eui64 |
|
8 |
NFDHCPD_STATE_DIR=/var/lib/nfdhcpd |
|
9 |
|
|
10 |
function routed_setup_ipv4 { |
|
11 |
# get the link's default gateway |
|
12 |
gw=$(ip route list table $TABLE | sed -n 's/default via \([^ ]\+\).*/\1/p' | head -1) |
|
13 |
|
|
14 |
# mangle ARPs to come from the gw's IP |
|
15 |
arptables -D OUTPUT -o $INTERFACE --opcode request -j mangle >/dev/null 2>&1 |
|
16 |
arptables -A OUTPUT -o $INTERFACE --opcode request -j mangle --mangle-ip-s "$gw" |
|
17 |
|
|
18 |
# route interface to the proper routing table |
|
19 |
while ip rule del dev $INTERFACE; do :; done |
|
20 |
ip rule add dev $INTERFACE table $TABLE |
|
21 |
|
|
22 |
# static route mapping IP -> INTERFACE |
|
23 |
ip route replace $IP proto static dev $INTERFACE table $TABLE |
|
24 |
|
|
25 |
# Enable proxy ARP |
|
26 |
echo 1 > /proc/sys/net/ipv4/conf/$INTERFACE/proxy_arp |
|
27 |
} |
|
28 |
|
|
29 |
function routed_setup_ipv6 { |
|
30 |
# Add a routing entry for the eui-64 |
|
31 |
prefix=$(ip -6 route list table $TABLE | awk '/\/64/ {print $1; exit}') |
|
32 |
uplink=$(ip -6 route list table $TABLE | sed -n 's/default via .* dev \([^ ]\+\).*/\1/p' | head -1) |
|
33 |
eui64=$($MAC2EUI64 $MAC $prefix) |
|
34 |
|
|
35 |
while ip -6 rule del dev $INTERFACE; do :; done |
|
36 |
ip -6 rule add dev $INTERFACE table $TABLE |
|
37 |
ip -6 ro replace $eui64/128 dev $INTERFACE table $TABLE |
|
38 |
ip -6 neigh add proxy $eui64 dev $uplink |
|
39 |
|
|
40 |
# disable proxy NDP since we're handling this on userspace |
|
41 |
# this should be the default, but better safe than sorry |
|
42 |
echo 0 > /proc/sys/net/ipv6/conf/$INTERFACE/proxy_ndp |
|
43 |
} |
|
44 |
|
|
45 |
# pick a firewall profile per NIC, based on tags (and apply it) |
|
46 |
function routed_setup_firewall { |
|
47 |
ifprefix="synnefo:network:$INTERFACE_INDEX:" |
|
48 |
for tag in $TAGS; do |
|
49 |
case ${tag#$ifprefix} in |
|
50 |
protected) |
|
51 |
chain=protected |
|
52 |
;; |
|
53 |
unprotected) |
|
54 |
chain=unprotected |
|
55 |
;; |
|
56 |
limited) |
|
57 |
chain=limited |
|
58 |
;; |
|
59 |
esac |
|
60 |
done |
|
61 |
|
|
62 |
# Flush any old rules. We have to consider all chains, since |
|
63 |
# we are not sure the instance was on the same chain, or had the same |
|
64 |
# tap interface. |
|
65 |
for oldchain in protected unprotected limited; do |
|
66 |
iptables -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null |
|
67 |
ip6tables -D FORWARD -o $INTERFACE -j $oldchain 2>/dev/null |
|
68 |
done |
|
69 |
|
|
70 |
if [ "x$chain" != "x" ]; then |
|
71 |
iptables -A FORWARD -o $INTERFACE -j $chain |
|
72 |
ip6tables -A FORWARD -o $INTERFACE -j $chain |
|
73 |
fi |
|
74 |
} |
|
75 |
|
|
76 |
function routed_setup_nfdhcpd { |
|
77 |
umask 022 |
|
78 |
cat >$NFDHCPD_STATE_DIR/$INTERFACE <<EOF |
|
79 |
IFACE=$1 |
|
80 |
IP=$IP |
|
81 |
MAC=$MAC |
|
82 |
LINK=$TABLE |
|
83 |
HOSTNAME=$INSTANCE |
|
84 |
TAGS="$TAGS" |
|
85 |
EOF |
|
86 |
} |
|
87 |
|
|
88 |
function make_ebtables { |
|
89 |
TAP=$INTERFACE |
|
90 |
FROM=FROM${TAP^^} |
|
91 |
TO=TO${TAP^^} |
|
92 |
|
|
93 |
ebtables -D INPUT -i $TAP -j $FROM |
|
94 |
ebtables -D FORWARD -i $TAP -j $FROM |
|
95 |
ebtables -D FORWARD -o $TAP -j $TO |
|
96 |
ebtables -D OUTPUT -o $TAP -j $TO |
|
97 |
|
|
98 |
ebtables -X $FROM |
|
99 |
ebtables -X $TO |
|
100 |
|
|
101 |
ebtables -N $FROM |
|
102 |
ebtables -A $FROM --ip-source \! $IP -p ipv4 -j DROP |
|
103 |
ebtables -A $FROM -s \! $MAC -j DROP |
|
104 |
ebtables -A INPUT -i $TAP -j $FROM |
|
105 |
ebtables -A FORWARD -i $TAP -j $FROM |
|
106 |
ebtables -N $TO |
|
107 |
ebtables -A FORWARD -o $TAP -j $TO |
|
108 |
ebtables -A OUTPUT -o $TAP -j $TO |
|
109 |
if [ $TYPE == "private" ]; then |
|
110 |
ebtables -A $TO -s \! $MAC/$MAC_MASK -j DROP |
|
111 |
if [ ! -z $GATEWAY ]; then |
|
112 |
ebtables -A $TO -s $ROUTER_MAC -j ACCEPT |
|
113 |
fi |
|
114 |
fi |
|
115 |
} |
|
116 |
|
|
117 |
#FIXME: import router mac from the config files |
|
118 |
# must know node group!! how??? |
|
119 |
ROUTER_MAC=6e:10:e1:a0:c3:0f |
|
120 |
MAC_MASK=ff:ff:ff:0:0:0 |
|
121 |
|
|
122 |
TABLE=rt_$NETWORK |
|
123 |
|
|
124 |
source /var/lib/snf-network/networks/$NETWORK |
|
125 |
|
|
126 |
|
|
127 |
if [ "$MODE" = "routed" ]; then |
|
128 |
# special proxy-ARP/NDP routing mode |
|
129 |
|
|
130 |
# use a constant predefined MAC address for the tap |
|
131 |
ip link set $INTERFACE addr $TAP_CONSTANT_MAC |
|
132 |
# bring the tap up |
|
133 |
ifconfig $INTERFACE 0.0.0.0 up |
|
134 |
|
|
135 |
# Drop unicast BOOTP/DHCP packets |
|
136 |
iptables -D FORWARD -i $INTERFACE -p udp --dport 67 -j DROP 2>/dev/null |
|
137 |
iptables -A FORWARD -i $INTERFACE -p udp --dport 67 -j DROP |
|
138 |
|
|
139 |
routed_setup_ipv4 |
|
140 |
routed_setup_ipv6 |
|
141 |
routed_setup_firewall |
|
142 |
routed_setup_nfdhcpd $INTERFACE |
|
143 |
elif [ "$MODE" = "bridged" ]; then |
|
144 |
while ip rule del dev $INTERFACE; do :; done |
|
145 |
ifconfig $INTERFACE 0.0.0.0 up |
|
146 |
brctl addif $BRIDGE $INTERFACE |
|
147 |
routed_setup_nfdhcpd $BRIDGE |
|
148 |
make_ebtables |
|
149 |
fi |
b/modify-network | ||
---|---|---|
1 |
#!/bin/bash |
|
2 |
|
|
3 |
DIR=/var/lib/snf-network |
|
4 |
NEW_GATEWAY=$1 |
|
5 |
NEW_TYPE=$2 |
|
6 |
NETWORK=$3 |
|
7 |
RT_TABLES=/etc/iproute2/rt_tables |
|
8 |
|
|
9 |
if [ $# -ne 3 ]; then |
|
10 |
echo "$0 <gateway> <private/public> <name>" |
|
11 |
exit 1 |
|
12 |
fi |
|
13 |
|
|
14 |
source /etc/default/snf-network |
|
15 |
|
|
16 |
NETWORK_FILE=$DIR/networks/$NETWORK |
|
17 |
|
|
18 |
source $NETWORK_FILE |
|
19 |
|
|
20 |
OLD_GATEWAY=$GATEWAY |
|
21 |
OLD_TYPE=$TYPE |
|
22 |
|
|
23 |
INTERFACES=$(ls $DIR/interfaces/$NETWORK-*) |
|
24 |
|
|
25 |
|
|
26 |
for IFACES in $INTERFACES ; do |
|
27 |
|
|
28 |
NODEGROUP=$(echo $IFACES | sed 's/.*interfaces.*-//') |
|
29 |
source $DIR/nodegroups/$NODEGROUP |
|
30 |
|
|
31 |
read x VLAN MODE BRIDGE < $INTERFACES |
|
32 |
|
|
33 |
if [ $MODE == "routed" ]; then |
|
34 |
if [ $TYPE == "public" ]; then |
|
35 |
ip route replace default via $GATEWAY dev $VLAN table rt_$NETWORK |
|
36 |
fi |
|
37 |
fi |
|
38 |
|
|
39 |
if [ $MODE == "bridged" ]; then |
|
40 |
if [ ! -z $GATEWAY ]; then |
|
41 |
ip route replace default via $GATEWAY dev $BRIDGE table rt_$NETWORK |
|
42 |
if [ $TYPE == "private" ]; then |
|
43 |
if [ ! -z $ROUTER ]; then |
|
44 |
if [ $(hostname) == $ROUTER ]; then |
|
45 |
NETMASK=$(ipcalc $SUBNET | grep Netmask | awk '{print $4}') |
|
46 |
ip addr del $GATEWAY/$NETMASK dev $BRIDGE |
|
47 |
ip addr add $NEW_GATEWAY/$NETMASK dev $BRIDGE |
|
48 |
fi |
|
49 |
fi |
|
50 |
fi |
|
51 |
fi |
|
52 |
fi |
|
53 |
|
|
54 |
if [ ! -z $NEW_GATEWAY ]; then |
|
55 |
sed -i '/^GATEWAY/ s/=.*/='"$NEW_GATEWAY"'/' $NETWORK_FILE |
|
56 |
fi |
|
57 |
|
|
58 |
if [ ! -z $NEW_TYPE ]; then |
|
59 |
sed -i '/^TYPE/ s/=.*/='"$NEW_TYPE"'/' $NETWORK_FILE |
|
60 |
fi |
|
61 |
|
|
62 |
done |
b/nfdhcpd/nfdhcpd | ||
---|---|---|
1 |
#!/usr/bin/env python |
|
2 |
# |
|
3 |
|
|
4 |
# nfdcpd: A promiscuous, NFQUEUE-based DHCP server for virtual machine hosting |
|
5 |
# Copyright (c) 2010 GRNET SA |
|
6 |
# |
|
7 |
# This program is free software; you can redistribute it and/or modify |
|
8 |
# it under the terms of the GNU General Public License as published by |
|
9 |
# the Free Software Foundation; either version 2 of the License, or |
|
10 |
# (at your option) any later version. |
|
11 |
# |
|
12 |
# This program is distributed in the hope that it will be useful, |
|
13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 |
# GNU General Public License for more details. |
|
16 |
# |
|
17 |
# You should have received a copy of the GNU General Public License along |
|
18 |
# with this program; if not, write to the Free Software Foundation, Inc., |
|
19 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
# |
|
21 |
|
|
22 |
import os |
|
23 |
import re |
|
24 |
import sys |
|
25 |
import glob |
|
26 |
import time |
|
27 |
import logging |
|
28 |
import logging.handlers |
|
29 |
import threading |
|
30 |
import traceback |
|
31 |
import subprocess |
|
32 |
|
|
33 |
import daemon |
|
34 |
import daemon.pidlockfile |
|
35 |
import nfqueue |
|
36 |
import pyinotify |
|
37 |
|
|
38 |
import IPy |
|
39 |
import socket |
|
40 |
from select import select |
|
41 |
from socket import AF_INET, AF_INET6 |
|
42 |
|
|
43 |
from scapy.data import ETH_P_ALL |
|
44 |
from scapy.packet import BasePacket |
|
45 |
from scapy.layers.l2 import Ether |
|
46 |
from scapy.layers.inet import IP, UDP |
|
47 |
from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \ |
|
48 |
ICMPv6NDOptDstLLAddr, \ |
|
49 |
ICMPv6NDOptPrefixInfo, \ |
|
50 |
ICMPv6NDOptRDNSS |
|
51 |
from scapy.layers.dhcp import BOOTP, DHCP |
|
52 |
|
|
53 |
DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf" |
|
54 |
DEFAULT_PATH = "/var/run/ganeti-dhcpd" |
|
55 |
DEFAULT_USER = "nobody" |
|
56 |
DEFAULT_LEASE_LIFETIME = 604800 # 1 week |
|
57 |
DEFAULT_LEASE_RENEWAL = 600 # 10 min |
|
58 |
DEFAULT_RA_PERIOD = 300 # seconds |
|
59 |
DHCP_DUMMY_SERVER_IP = "1.2.3.4" |
|
60 |
|
|
61 |
LOG_FILENAME = "nfdhcpd.log" |
|
62 |
|
|
63 |
SYSFS_NET = "/sys/class/net" |
|
64 |
|
|
65 |
LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s" |
|
66 |
|
|
67 |
# Configuration file specification (see configobj documentation) |
|
68 |
CONFIG_SPEC = """ |
|
69 |
[general] |
|
70 |
pidfile = string() |
|
71 |
datapath = string() |
|
72 |
logdir = string() |
|
73 |
user = string() |
|
74 |
|
|
75 |
[dhcp] |
|
76 |
enable_dhcp = boolean(default=True) |
|
77 |
lease_lifetime = integer(min=0, max=4294967295) |
|
78 |
lease_renewal = integer(min=0, max=4294967295) |
|
79 |
server_ip = ip_addr() |
|
80 |
dhcp_queue = integer(min=0, max=65535) |
|
81 |
nameservers = ip_addr_list(family=4) |
|
82 |
|
|
83 |
[ipv6] |
|
84 |
enable_ipv6 = boolean(default=True) |
|
85 |
ra_period = integer(min=1, max=4294967295) |
|
86 |
rs_queue = integer(min=0, max=65535) |
|
87 |
ns_queue = integer(min=0, max=65535) |
|
88 |
nameservers = ip_addr_list(family=6) |
|
89 |
""" |
|
90 |
|
|
91 |
|
|
92 |
DHCPDISCOVER = 1 |
|
93 |
DHCPOFFER = 2 |
|
94 |
DHCPREQUEST = 3 |
|
95 |
DHCPDECLINE = 4 |
|
96 |
DHCPACK = 5 |
|
97 |
DHCPNAK = 6 |
|
98 |
DHCPRELEASE = 7 |
|
99 |
DHCPINFORM = 8 |
|
100 |
|
|
101 |
DHCP_TYPES = { |
|
102 |
DHCPDISCOVER: "DHCPDISCOVER", |
|
103 |
DHCPOFFER: "DHCPOFFER", |
|
104 |
DHCPREQUEST: "DHCPREQUEST", |
|
105 |
DHCPDECLINE: "DHCPDECLINE", |
|
106 |
DHCPACK: "DHCPACK", |
|
107 |
DHCPNAK: "DHCPNAK", |
|
108 |
DHCPRELEASE: "DHCPRELEASE", |
|
109 |
DHCPINFORM: "DHCPINFORM", |
|
110 |
} |
|
111 |
|
|
112 |
DHCP_REQRESP = { |
|
113 |
DHCPDISCOVER: DHCPOFFER, |
|
114 |
DHCPREQUEST: DHCPACK, |
|
115 |
DHCPINFORM: DHCPACK, |
|
116 |
} |
|
117 |
|
|
118 |
|
|
119 |
def parse_routing_table(table="main", family=4): |
|
120 |
""" Parse the given routing table to get connected route, gateway and |
|
121 |
default device. |
|
122 |
|
|
123 |
""" |
|
124 |
ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls", |
|
125 |
"table", table], stdout=subprocess.PIPE) |
|
126 |
routes = ipro.stdout.readlines() |
|
127 |
|
|
128 |
def_gw = None |
|
129 |
def_dev = None |
|
130 |
def_net = None |
|
131 |
|
|
132 |
for route in routes: |
|
133 |
match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route) |
|
134 |
if match: |
|
135 |
def_gw, def_dev = match.groups() |
|
136 |
break |
|
137 |
|
|
138 |
for route in routes: |
|
139 |
# Find the least-specific connected route |
|
140 |
m = re.match("^([^\\s]+) dev %s" % def_dev, route) |
|
141 |
if not m: |
|
142 |
continue |
|
143 |
|
|
144 |
if family == 6 and m.group(1).startswith("fe80:"): |
|
145 |
# Skip link-local declarations in "main" table |
|
146 |
continue |
|
147 |
|
|
148 |
def_net = m.group(1) |
|
149 |
|
|
150 |
try: |
|
151 |
def_net = IPy.IP(def_net) |
|
152 |
except ValueError, e: |
|
153 |
logging.warn("Unable to parse default route entry %s: %s", |
|
154 |
def_net, str(e)) |
|
155 |
|
|
156 |
return Subnet(net=def_net, gw=def_gw, dev=def_dev) |
|
157 |
|
|
158 |
|
|
159 |
def parse_binding_file(path): |
|
160 |
""" Read a client configuration from a tap file |
|
161 |
|
|
162 |
""" |
|
163 |
try: |
|
164 |
iffile = open(path, 'r') |
|
165 |
except EnvironmentError, e: |
|
166 |
logging.warn("Unable to open binding file %s: %s", path, str(e)) |
|
167 |
return None |
|
168 |
|
|
169 |
ifname = os.path.basename(path) |
|
170 |
mac = None |
|
171 |
ips = None |
|
172 |
link = None |
|
173 |
hostname = None |
|
174 |
|
|
175 |
for line in iffile: |
|
176 |
if line.startswith("IP="): |
|
177 |
ip = line.strip().split("=")[1] |
|
178 |
ips = ip.split() |
|
179 |
elif line.startswith("MAC="): |
|
180 |
mac = line.strip().split("=")[1] |
|
181 |
elif line.startswith("LINK="): |
|
182 |
link = line.strip().split("=")[1] |
|
183 |
elif line.startswith("HOSTNAME="): |
|
184 |
hostname = line.strip().split("=")[1] |
|
185 |
elif line.startswith("IFACE="): |
|
186 |
iface = line.strip().split("=")[1] |
|
187 |
|
|
188 |
return Client(ifname=ifname, mac=mac, ips=ips, link=link, hostname=hostname, iface=iface) |
|
189 |
|
|
190 |
|
|
191 |
class ClientFileHandler(pyinotify.ProcessEvent): |
|
192 |
def __init__(self, server): |
|
193 |
pyinotify.ProcessEvent.__init__(self) |
|
194 |
self.server = server |
|
195 |
|
|
196 |
def process_IN_DELETE(self, event): # pylint: disable=C0103 |
|
197 |
""" Delete file handler |
|
198 |
|
|
199 |
Currently this removes an interface from the watch list |
|
200 |
|
|
201 |
""" |
|
202 |
self.server.remove_iface(event.name) |
|
203 |
|
|
204 |
def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103 |
|
205 |
""" Add file handler |
|
206 |
|
|
207 |
Currently this adds an interface to the watch list |
|
208 |
|
|
209 |
""" |
|
210 |
self.server.add_iface(os.path.join(event.path, event.name)) |
|
211 |
|
|
212 |
|
|
213 |
class Client(object): |
|
214 |
def __init__(self, ifname=None, mac=None, ips=None, link=None, hostname=None, iface=None): |
|
215 |
self.mac = mac |
|
216 |
self.ips = ips |
|
217 |
self.hostname = hostname |
|
218 |
self.link = link |
|
219 |
self.iface = iface |
|
220 |
self.ifname = ifname |
|
221 |
|
|
222 |
@property |
|
223 |
def ip(self): |
|
224 |
return self.ips[0] |
|
225 |
|
|
226 |
def is_valid(self): |
|
227 |
return self.mac is not None and self.ips is not None\ |
|
228 |
and self.hostname is not None |
|
229 |
|
|
230 |
|
|
231 |
class Subnet(object): |
|
232 |
def __init__(self, net=None, gw=None, dev=None): |
|
233 |
if isinstance(net, str): |
|
234 |
self.net = IPy.IP(net) |
|
235 |
else: |
|
236 |
self.net = net |
|
237 |
self.gw = gw |
|
238 |
self.dev = dev |
|
239 |
|
|
240 |
@property |
|
241 |
def netmask(self): |
|
242 |
""" Return the netmask in textual representation |
|
243 |
|
|
244 |
""" |
|
245 |
return str(self.net.netmask()) |
|
246 |
|
|
247 |
@property |
|
248 |
def broadcast(self): |
|
249 |
""" Return the broadcast address in textual representation |
|
250 |
|
|
251 |
""" |
|
252 |
return str(self.net.broadcast()) |
|
253 |
|
|
254 |
@property |
|
255 |
def prefix(self): |
|
256 |
""" Return the network as an IPy.IP |
|
257 |
|
|
258 |
""" |
|
259 |
return self.net.net() |
|
260 |
|
|
261 |
@property |
|
262 |
def prefixlen(self): |
|
263 |
""" Return the prefix length as an integer |
|
264 |
|
|
265 |
""" |
|
266 |
return self.net.prefixlen() |
|
267 |
|
|
268 |
@staticmethod |
|
269 |
def _make_eui64(net, mac): |
|
270 |
""" Compute an EUI-64 address from an EUI-48 (MAC) address |
|
271 |
|
|
272 |
""" |
|
273 |
comp = mac.split(":") |
|
274 |
prefix = IPy.IP(net).net().strFullsize().split(":")[:4] |
|
275 |
eui64 = comp[:3] + ["ff", "fe"] + comp[3:] |
|
276 |
eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02) |
|
277 |
for l in range(0, len(eui64), 2): |
|
278 |
prefix += ["".join(eui64[l:l+2])] |
|
279 |
return IPy.IP(":".join(prefix)) |
|
280 |
|
|
281 |
def make_eui64(self, mac): |
|
282 |
""" Compute an EUI-64 address from an EUI-48 (MAC) address in this |
|
283 |
subnet. |
|
284 |
|
|
285 |
""" |
|
286 |
return self._make_eui64(self.net, mac) |
|
287 |
|
|
288 |
def make_ll64(self, mac): |
|
289 |
""" Compute an IPv6 Link-local address from an EUI-48 (MAC) address |
|
290 |
|
|
291 |
""" |
|
292 |
return self._make_eui64("fe80::", mac) |
|
293 |
|
|
294 |
|
|
295 |
class VMNetProxy(object): # pylint: disable=R0902 |
|
296 |
def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913 |
|
297 |
rs_queue_num=None, ns_queue_num=None, |
|
298 |
dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME, |
|
299 |
dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL, |
|
300 |
dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None, |
|
301 |
ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None): |
|
302 |
|
|
303 |
self.data_path = data_path |
|
304 |
self.lease_lifetime = dhcp_lease_lifetime |
|
305 |
self.lease_renewal = dhcp_lease_renewal |
|
306 |
self.dhcp_server_ip = dhcp_server_ip |
|
307 |
self.ra_period = ra_period |
|
308 |
if dhcp_nameservers is None: |
|
309 |
self.dhcp_nameserver = [] |
|
310 |
else: |
|
311 |
self.dhcp_nameservers = dhcp_nameservers |
|
312 |
|
|
313 |
if ipv6_nameservers is None: |
|
314 |
self.ipv6_nameservers = [] |
|
315 |
else: |
|
316 |
self.ipv6_nameservers = ipv6_nameservers |
|
317 |
|
|
318 |
self.ipv6_enabled = False |
|
319 |
|
|
320 |
self.clients = {} |
|
321 |
self.subnets = {} |
|
322 |
self.ifaces = {} |
|
323 |
self.v6nets = {} |
|
324 |
self.nfq = {} |
|
325 |
self.l2socket = socket.socket(socket.AF_PACKET, |
|
326 |
socket.SOCK_RAW, ETH_P_ALL) |
|
327 |
self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0) |
|
328 |
|
|
329 |
# Inotify setup |
|
330 |
self.wm = pyinotify.WatchManager() |
|
331 |
mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"] |
|
332 |
mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"] |
|
333 |
inotify_handler = ClientFileHandler(self) |
|
334 |
self.notifier = pyinotify.Notifier(self.wm, inotify_handler) |
|
335 |
self.wm.add_watch(self.data_path, mask, rec=True) |
|
336 |
|
|
337 |
# NFQUEUE setup |
|
338 |
if dhcp_queue_num is not None: |
|
339 |
self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response) |
|
340 |
|
|
341 |
if rs_queue_num is not None: |
|
342 |
self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response) |
|
343 |
self.ipv6_enabled = True |
|
344 |
|
|
345 |
if ns_queue_num is not None: |
|
346 |
self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response) |
|
347 |
self.ipv6_enabled = True |
|
348 |
|
|
349 |
def _cleanup(self): |
|
350 |
""" Free all resources for a graceful exit |
|
351 |
|
|
352 |
""" |
|
353 |
logging.info("Cleaning up") |
|
354 |
|
|
355 |
logging.debug("Closing netfilter queues") |
|
356 |
for q in self.nfq.values(): |
|
357 |
q.close() |
|
358 |
|
|
359 |
logging.debug("Closing socket") |
|
360 |
self.l2socket.close() |
|
361 |
|
|
362 |
logging.debug("Stopping inotify watches") |
|
363 |
self.notifier.stop() |
|
364 |
|
|
365 |
logging.info("Cleanup finished") |
|
366 |
|
|
367 |
def _setup_nfqueue(self, queue_num, family, callback): |
|
368 |
logging.debug("Setting up NFQUEUE for queue %d, AF %s", |
|
369 |
queue_num, family) |
|
370 |
q = nfqueue.queue() |
|
371 |
q.set_callback(callback) |
|
372 |
q.fast_open(queue_num, family) |
|
373 |
q.set_queue_maxlen(5000) |
|
374 |
# This is mandatory for the queue to operate |
|
375 |
q.set_mode(nfqueue.NFQNL_COPY_PACKET) |
|
376 |
self.nfq[q.get_fd()] = q |
|
377 |
|
|
378 |
def sendp(self, data, iface): |
|
379 |
""" Send a raw packet using a layer-2 socket |
|
380 |
|
|
381 |
""" |
|
382 |
if isinstance(data, BasePacket): |
|
383 |
data = str(data) |
|
384 |
|
|
385 |
self.l2socket.bind((iface, ETH_P_ALL)) |
|
386 |
count = self.l2socket.send(data) |
|
387 |
ldata = len(data) |
|
388 |
if count != ldata: |
|
389 |
logging.warn("Truncated send on %s (%d/%d bytes sent)", |
|
390 |
iface, count, ldata) |
|
391 |
|
|
392 |
def build_config(self): |
|
393 |
self.clients.clear() |
|
394 |
self.subnets.clear() |
|
395 |
|
|
396 |
for path in glob.glob(os.path.join(self.data_path, "*")): |
|
397 |
self.add_iface(path) |
|
398 |
|
|
399 |
def get_ifindex(self, iface): |
|
400 |
""" Get the interface index from sysfs |
|
401 |
|
|
402 |
""" |
|
403 |
path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex")) |
|
404 |
if not path.startswith(SYSFS_NET): |
|
405 |
return None |
|
406 |
|
|
407 |
ifindex = None |
|
408 |
|
|
409 |
try: |
|
410 |
f = open(path, 'r') |
|
411 |
except EnvironmentError: |
|
412 |
logging.debug("%s is probably down, removing", iface) |
|
413 |
self.remove_iface(iface) |
|
414 |
|
|
415 |
return ifindex |
|
416 |
|
|
417 |
try: |
|
418 |
ifindex = f.readline().strip() |
|
419 |
try: |
|
420 |
ifindex = int(ifindex) |
|
421 |
except ValueError, e: |
|
422 |
logging.warn("Failed to get ifindex for %s, cannot parse sysfs" |
|
423 |
" output '%s'", iface, ifindex) |
|
424 |
except EnvironmentError, e: |
|
425 |
logging.warn("Error reading %s's ifindex from sysfs: %s", |
|
426 |
iface, str(e)) |
|
427 |
self.remove_iface(iface) |
|
428 |
finally: |
|
429 |
f.close() |
|
430 |
|
|
431 |
return ifindex |
|
432 |
|
|
433 |
|
|
434 |
def get_iface_hw_addr(self, iface): |
|
435 |
""" Get the interface hardware address from sysfs |
|
436 |
|
|
437 |
""" |
|
438 |
path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address")) |
|
439 |
if not path.startswith(SYSFS_NET): |
|
440 |
return None |
|
441 |
|
|
442 |
addr = None |
|
443 |
try: |
|
444 |
f = open(path, 'r') |
|
445 |
except EnvironmentError: |
|
446 |
logging.debug("%s is probably down, removing", iface) |
|
447 |
self.remove_iface(iface) |
|
448 |
return addr |
|
449 |
|
|
450 |
try: |
|
451 |
addr = f.readline().strip() |
|
452 |
except EnvironmentError, e: |
|
453 |
logging.warn("Failed to read hw address for %s from sysfs: %s", |
|
454 |
iface, str(e)) |
|
455 |
finally: |
|
456 |
f.close() |
|
457 |
|
|
458 |
return addr |
|
459 |
|
|
460 |
def add_iface(self, path): |
|
461 |
""" Add an interface to monitor |
|
462 |
|
|
463 |
""" |
|
464 |
iface = os.path.basename(path) |
|
465 |
|
|
466 |
logging.debug("Updating configuration for %s", iface) |
|
467 |
binding = parse_binding_file(path) |
|
468 |
if binding is None: |
|
469 |
return |
|
470 |
ifindex = self.get_ifindex(binding.iface) |
|
471 |
|
|
472 |
if ifindex is None: |
|
473 |
logging.warn("Stale configuration for %s found", iface) |
|
474 |
else: |
|
475 |
if binding.is_valid(): |
|
476 |
self.clients[binding.mac] = binding |
|
477 |
self.subnets[binding.link] = parse_routing_table(binding.link) |
|
478 |
logging.debug("Added client %s on %s", binding.hostname, iface) |
|
479 |
self.ifaces[ifindex] = binding.iface |
|
480 |
self.v6nets[iface] = parse_routing_table(binding.link, 6) |
|
481 |
|
|
482 |
def remove_iface(self, ifname): |
|
483 |
""" Cleanup clients on a removed interface |
|
484 |
|
|
485 |
""" |
|
486 |
if ifname in self.v6nets: |
|
487 |
del self.v6nets[ifname] |
|
488 |
|
|
489 |
for mac in self.clients.keys(): |
|
490 |
if self.clients[mac].ifname == ifname: |
|
491 |
iface = self.client[mac].iface |
|
492 |
del self.clients[mac] |
|
493 |
|
|
494 |
for ifindex in self.ifaces.keys(): |
|
495 |
if self.ifaces[ifindex] == ifname == iface: |
|
496 |
del self.ifaces[ifindex] |
|
497 |
|
|
498 |
logging.debug("Removed interface %s", ifname) |
|
499 |
|
|
500 |
def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914 |
|
501 |
""" Generate a reply to a BOOTP/DHCP request |
|
502 |
|
|
503 |
""" |
|
504 |
logging.info("%s",payload) |
|
505 |
indev = payload.get_indev() |
|
506 |
try: |
|
507 |
# Get the actual interface from the ifindex |
|
508 |
iface = self.ifaces[indev] |
|
509 |
except KeyError: |
|
510 |
# We don't know anything about this interface, so accept the packet |
|
511 |
# and return |
|
512 |
logging.debug("Ignoring DHCP request on unknown iface %d", indev) |
|
513 |
# We don't know what to do with this packet, so let the kernel |
|
514 |
# handle it |
|
515 |
payload.set_verdict(nfqueue.NF_ACCEPT) |
|
516 |
return |
|
517 |
|
|
518 |
# Decode the response - NFQUEUE relays IP packets |
|
519 |
pkt = IP(payload.get_data()) |
|
520 |
|
|
521 |
# Signal the kernel that it shouldn't further process the packet |
|
522 |
payload.set_verdict(nfqueue.NF_DROP) |
|
523 |
|
|
524 |
# Get the client MAC address |
|
525 |
resp = pkt.getlayer(BOOTP).copy() |
|
526 |
hlen = resp.hlen |
|
527 |
mac = resp.chaddr[:hlen].encode("hex") |
|
528 |
mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1) |
|
529 |
|
|
530 |
logging.info("%s %s %s ", resp, hlen, mac) |
|
531 |
# Server responses are always BOOTREPLYs |
|
532 |
resp.op = "BOOTREPLY" |
|
533 |
del resp.payload |
|
534 |
|
|
535 |
try: |
|
536 |
binding = self.clients[mac] |
|
537 |
except KeyError: |
|
538 |
logging.warn("Invalid client %s on %s", mac, iface) |
|
539 |
return |
|
540 |
|
|
541 |
if iface != binding.iface: |
|
542 |
logging.warn("Received spoofed DHCP request for %s from interface" |
|
543 |
" %s instead of %s", mac, iface, binding.iface) |
|
544 |
return |
|
545 |
|
|
546 |
resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\ |
|
547 |
IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\ |
|
548 |
UDP(sport=pkt.dport, dport=pkt.sport)/resp |
|
549 |
subnet = self.subnets[binding.link] |
|
550 |
|
|
551 |
if not DHCP in pkt: |
|
552 |
logging.warn("Invalid request from %s on %s, no DHCP" |
|
553 |
" payload found", binding.mac, iface) |
|
554 |
return |
|
555 |
|
|
556 |
dhcp_options = [] |
|
557 |
requested_addr = binding.ip |
|
558 |
for opt in pkt[DHCP].options: |
|
559 |
if type(opt) is tuple and opt[0] == "message-type": |
|
560 |
req_type = opt[1] |
|
561 |
if type(opt) is tuple and opt[0] == "requested_addr": |
|
562 |
requested_addr = opt[1] |
|
563 |
|
|
564 |
logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"), |
|
565 |
binding.mac, iface) |
|
566 |
|
|
567 |
if req_type == DHCPREQUEST and requested_addr != binding.ip: |
|
568 |
resp_type = DHCPNAK |
|
569 |
logging.info("Sending DHCPNAK to %s on %s: requested %s" |
|
570 |
" instead of %s", binding.mac, iface, requested_addr, |
|
571 |
binding.ip) |
|
572 |
|
|
573 |
elif req_type in (DHCPDISCOVER, DHCPREQUEST): |
|
574 |
resp_type = DHCP_REQRESP[req_type] |
|
575 |
resp.yiaddr = self.clients[mac].ip |
|
576 |
dhcp_options += [ |
|
577 |
("hostname", binding.hostname), |
|
578 |
("domain", binding.hostname.split('.', 1)[-1]), |
|
579 |
("router", subnet.gw), |
|
580 |
("broadcast_address", str(subnet.broadcast)), |
|
581 |
("subnet_mask", str(subnet.netmask)), |
|
582 |
("renewal_time", self.lease_renewal), |
|
583 |
("lease_time", self.lease_lifetime), |
|
584 |
] |
|
585 |
dhcp_options += [("name_server", x) for x in self.dhcp_nameservers] |
|
586 |
|
|
587 |
elif req_type == DHCPINFORM: |
|
588 |
resp_type = DHCP_REQRESP[req_type] |
|
589 |
dhcp_options += [ |
|
590 |
("hostname", binding.hostname), |
|
591 |
("domain", binding.hostname.split('.', 1)[-1]), |
|
592 |
] |
|
593 |
dhcp_options += [("name_server", x) for x in self.dhcp_nameservers] |
|
594 |
|
|
595 |
elif req_type == DHCPRELEASE: |
|
596 |
# Log and ignore |
|
597 |
logging.info("DHCPRELEASE from %s on %s", binding.mac, iface) |
|
598 |
return |
|
599 |
|
|
600 |
# Finally, always add the server identifier and end options |
|
601 |
dhcp_options += [ |
|
602 |
("message-type", resp_type), |
|
603 |
("server_id", DHCP_DUMMY_SERVER_IP), |
|
604 |
"end" |
|
605 |
] |
|
606 |
resp /= DHCP(options=dhcp_options) |
|
607 |
|
|
608 |
logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac, |
|
609 |
binding.ip, iface) |
|
610 |
self.sendp(resp, iface) |
|
611 |
|
|
612 |
def rs_response(self, i, payload): # pylint: disable=W0613 |
|
613 |
""" Generate a reply to a BOOTP/DHCP request |
|
614 |
|
|
615 |
""" |
|
616 |
indev = payload.get_indev() |
|
617 |
try: |
|
618 |
# Get the actual interface from the ifindex |
|
619 |
iface = self.ifaces[indev] |
|
620 |
except KeyError: |
|
621 |
logging.debug("Ignoring router solicitation on" |
|
622 |
" unknown interface %d", indev) |
|
623 |
# We don't know what to do with this packet, so let the kernel |
|
624 |
# handle it |
|
625 |
payload.set_verdict(nfqueue.NF_ACCEPT) |
|
626 |
return |
|
627 |
|
|
628 |
ifmac = self.get_iface_hw_addr(iface) |
|
629 |
subnet = self.v6nets[iface] |
|
630 |
ifll = subnet.make_ll64(ifmac) |
|
631 |
|
|
632 |
# Signal the kernel that it shouldn't further process the packet |
|
633 |
payload.set_verdict(nfqueue.NF_DROP) |
|
634 |
|
|
635 |
resp = Ether(src=self.get_iface_hw_addr(iface))/\ |
|
636 |
IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\ |
|
637 |
ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix), |
|
638 |
prefixlen=subnet.prefixlen) |
|
639 |
|
|
640 |
if self.ipv6_nameservers: |
|
641 |
resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers, |
|
642 |
lifetime=self.ra_period * 3) |
|
643 |
|
|
644 |
logging.info("RA on %s for %s", iface, subnet.net) |
|
645 |
self.sendp(resp, iface) |
|
646 |
|
|
647 |
def ns_response(self, i, payload): # pylint: disable=W0613 |
|
648 |
""" Generate a reply to an ICMPv6 neighbor solicitation |
|
649 |
|
|
650 |
""" |
|
651 |
indev = payload.get_indev() |
|
652 |
try: |
|
653 |
# Get the actual interface from the ifindex |
|
654 |
iface = self.ifaces[indev] |
|
655 |
except KeyError: |
|
656 |
logging.debug("Ignoring neighbour solicitation on" |
|
657 |
" unknown interface %d", indev) |
|
658 |
# We don't know what to do with this packet, so let the kernel |
|
659 |
# handle it |
|
660 |
payload.set_verdict(nfqueue.NF_ACCEPT) |
|
661 |
return |
|
662 |
|
|
663 |
ifmac = self.get_iface_hw_addr(iface) |
|
664 |
subnet = self.v6nets[iface] |
|
665 |
ifll = subnet.make_ll64(ifmac) |
|
666 |
|
|
667 |
ns = IPv6(payload.get_data()) |
|
668 |
|
|
669 |
if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)): |
|
670 |
logging.debug("Received NS for a non-routable IP (%s)", ns.tgt) |
|
671 |
payload.set_verdict(nfqueue.NF_ACCEPT) |
|
672 |
return 1 |
|
673 |
|
|
674 |
payload.set_verdict(nfqueue.NF_DROP) |
|
675 |
|
|
676 |
try: |
|
677 |
client_lladdr = ns.lladdr |
|
678 |
except AttributeError: |
|
679 |
return 1 |
|
680 |
|
|
681 |
resp = Ether(src=ifmac, dst=client_lladdr)/\ |
Also available in: Unified diff