Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – III

In the first blog post
Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – I
of this series about virtual networking between network namespaces I had discussed some basic CLI Linux commands to set up and enter network namespaces on a Linux system. In a second post
Fun with veth-devices, Linux bridges and VLANs in unnamed Linux network namespaces – II
I suggested and described several networking experiments which can quickly be set up by these tools. As containers are based on namespaces we can study virtual networking between containers on a host in principle just by connecting network namespaces. Makes e.g. the planning of firewall rules and VLANs a bit easier ...

The virtual environment we want to build up and explore step by step is displayed in the following graphics:

In this article we shall cover experiment 1 and experiment 2 discussed in the last article - i.e. we start with the upper left corner of the drawing.

Experiment 1: Connect two network namespaces directly

This experiments creates the dotted line between netns1 and netns2. Though simple this experiments lays the foundation for all other experiments.

We place the two different Ethernet interfaces of a veth device in the two (unnamed) network namespaces (with hostnames) netns1 and netns2. We assign IP addresses (of the same network class) to the interfaces and check a basic communication between the network namespaces. The situation corresponds to the following simple picture:

What shell commands can be used for achieving this? You may put the following lines in a file for keeping them for further experiments or to create a shell script:

unshare --net --uts /bin/bash &
export pid_netns1=$!
nsenter -t $pid_netns1 -u hostname netns1
unshare --net --uts /bin/bash &
export pid_netns2=$!
nsenter -t $pid_netns2 -u hostname netns2
ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2
nsenter -t $pid_netns1 -u -n /bin/bash
ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
ip link set veth11 up
ip link set lo up
ip a s
exit
nsenter -t $pid_netns2 -u -n /bin/bash
ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
ip link set veth22 up
ip a s
exit
lsns -t net -t uts

If you now copy these lines to the prompt of a root shell of some host "mytux" you will get something like the following:

mytux:~ # unshare --net --uts /bin/bash &
[2] 32146
mytux:~ # export pid_netns1=$!

[2]+  Stopped                 unshare --net --uts /bin/bash
mytux:~ # nsenter -t $pid_netns1 -u hostname netns1
mytux:~ # unshare --net --uts /bin/bash &
[3] 32154
mytux:~ # export pid_netns2=$!

[3]+  Stopped                 unshare --net --uts /bin/bash
mytux:~ # nsenter -t $pid_netns2 -u hostname netns2
mytux:~ # ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2
mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
netns1:~ # ip link set veth11 up
netns1:~ # ip link set lo up
netns1:~ # ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth11: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether da:34:49:a6:18:ce brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.1/24 brd 192.168.5.255 scope global veth11
       valid_lft forever preferred_lft forever
netns1:~ # exit
exit
mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
netns2:~ # ip link set veth22 up
netns2:~ # ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth22: <NO-CARRIER,BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether f2:ee:52:f9:92:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.2/24 brd 192.168.5.255 scope global veth22
       valid_lft forever preferred_lft forever
    inet6 fe80::f0ee:52ff:fef9:9240/64 scope link tentative 
       valid_lft forever preferred_lft forever
netns2:~ # exit
exit
mytux:~ # lsns -t net -t uts
        NS TYPE NPROCS   PID USER  COMMAND
4026531838 uts     387     1 root  /usr/lib/systemd/systemd --switched
4026531963 net     385     1 root  /usr/lib/systemd/systemd --switched
4026532178 net       1   581 root  /usr/sbin/haveged -w 1024 -v 0 -F
4026540861 net       1  4138 rtkit /usr/lib/rtkit/rtkit-daemon
4026540984 uts       1 32146 root  /bin/bash
4026540986 net       1 32146 root  /bin/bash
4026541078 uts       1 32154 root  /bin/bash
4026541080 net       1 32154 root  /bin/bash
rux:~ # 

Of course, you recognize some of the commands from my first blog post. Still, some details are worth a comment:

Unshare, background shells and shell variables:
We create a separate network (and uts) namespace with the "unshare" command and background processes.

unshare --net --uts /bin/bash &

Note the options! We export shell variables with the PIDs of the started background processes [$!] to have these PIDs available in subshells later on. Note: From our original terminal window (in my case a KDE "konsole" window) we can always open a subshell window with:

mytux:~ # konsole &>/dev/null

You may use another terminal window command on your system. The output redirection is done only to avoid KDE message clattering. In the subshell you may enter a previously created network namespace netnsX by

nsenter -t $pid_netnsX -u -n /bin/bash

Hostnames to distinguish namespaces at the shell prompt:
Assignment of hostnames to the background processes via commands like

nsenter -t $pid_netns1 -u hostname netns1

This works through the a separation of the uts namespace. See the first post for an explanation.

Create veth devices with the "ip" command:
The key command to create a veth device and to assign its two interfaces to 2 different network namespaces is:

ip link add veth11 netns $pid_netns1 type veth peer name veth22 netns $pid_netns2

Note, that we can use PIDs to identify the target network namespaces! Explicit names of the network namespaces are not required!

The importance of a running lo-device in each network namespace:
We intentionally did not set the loopback device "lo" up in netns2. This leads to an interesting observation, which many admins are not aware of:

The lo device is required (in UP status) to be able to ping network interfaces (here e.g. veth11) in the local namespace!

This is standard: If you do not specify the interface to ping from via an option "-I" the ping command will use device lo as a default! The ping traffic runs through it! Normally, we just do not realize this point, because lo almost always is UP on a standard system (in its root namespace).

For testing the role of "lo" we now open a separate terminal window:

mytux:~ # konsole &>/dev/null 

There:

mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
^C
--- 192.168.5.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1008ms

netns2:~ # ip link set lo up
netns2:~ # ping 192.168.5.2 -c2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.017 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.033 ms

--- 192.168.5.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 998ms
rtt min/avg/max/mdev = 0.017/0.034 ms

And: Within the same namespace and "lo" down you cannot even ping the second Ethernet interface of a veth device from the first interface - even if they belong to the same network class:
Open anew sub shell and enter e.g. netns1 there:

netns1:~ # ip link add vethx type veth peer name vethy 
netns1:~ # ip addr add 192.168.20.1/24 brd 192.168.20.255 dev vethx 
netns1:~ # ip addr add 192.168.20.2/24 brd 192.168.20.255 dev vethy 
netns1:~ # ip link set vethx up
netns1:~ # ip link set vethy up
netns1:~ # ping 192.168.20.2 -I 192.168.20.1
PING 192.168.20.2 (192.168.20.2) from 192.168.20.1 : 56(84) bytes of data.
^C
--- 192.168.20.2 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3000ms
netns1:~ # ip link set lo up
netns1:~ # ping 192.168.20.2 -I 192.168.20.1                                                                               
PING 192.168.20.2 (192.168.20.2) from 192.168.20.1 : 56(84) bytes of data.                                                 
64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.019 ms                                                                
64 bytes from 192.168.20.2: icmp_seq=2 ttl=64 time=0.052 ms                                                                
^C                                                                                                                         
--- 192.168.20.2 ping statistics ---                                                                                       
2 packets transmitted, 2 received, 0% packet loss, time 999ms                                                              
rtt min/avg/max/mdev = 0.019/0.035/0.052/0.017 ms                                                                          
netns1:~ #                                           

Connection test:
Now back to our experiment. Let us now try to ping netns1 from netns2:

netns2:~ # ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth22: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether f2:ee:52:f9:92:40 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.2/24 brd 192.168.5.255 scope global veth22
       valid_lft forever preferred_lft forever
    inet6 fe80::f0ee:52ff:fef9:9240/64 scope link 
       valid_lft forever preferred_lft forever
netns2:~ # ping 192.168.5.1
PING 192.168.5.1 (192.168.5.1) 56(84) bytes of data.
64 bytes from 192.168.5.1: icmp_seq=1 ttl=64 time=0.030 ms
64 bytes from 192.168.5.1: icmp_seq=2 ttl=64 time=0.033 ms
64 bytes from 192.168.5.1: icmp_seq=3 ttl=64 time=0.036 ms
^C
--- 192.168.5.1 ping statistics ---                                                                  
3 packets transmitted, 3 received, 0% packet loss, time 1998ms                                       
rtt min/avg/max/mdev = 0.030/0.033/0.036/0.002 ms                                                    
netns2:~ #     

OK! And vice versa:

mytux:~ #  nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ #  nsenter -t $pid_netns2 -u -n /bin/bash
netns1:~ # ping 192.168.5.2 -c2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.023 ms

--- 192.168.5.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1003ms
rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms
netns1:~ # 

Our direct communication via veth works as expected! Network packets are not stopped by network namespace borders - this would not make much sense.

Experiment 2: Connect two namespaces via a bridge in a third namespace

We now try a connection of netns1 and netns2 via a Linux bridge "brx", which we place in a third namespace netns3:

Note:

This is a standard way to connect containers on a host!

LXC tools as well as libvirt/virt-manager would help you to establish such a bridge! However, the bridge would normally be place inside the host's root namespace. In my opinion this is not a good idea:

A separate 3rd namespace gets the the bridge and related firewall and VLAN rules outside the control of the containers. But a separate namespace also helps to isolate the host against any communication (and possible attacks) coming from the containers!

So, let us close our sub terminals from the first experiment and kill the background shells:

mytux:~ # kill -9 32146
[2]-  Killed                  unshare --net --uts /bin/bash
mytux:~ # kill -9 32154
[3]+  Killed                  unshare --net --uts /bin/bash

We adapt our setup commands now to create netns3 and bridge "brx" there by using "brctl bradd". Futhermore we add two different veth devices; each with one interface in netns3. We attach the interface to the bridge via "brctl addif":

unshare --net --uts /bin/bash &
export pid_netns1=$!
nsenter -t $pid_netns1 -u hostname netns1
unshare --net --uts /bin/bash &
export pid_netns2=$!
nsenter -t $pid_netns2 -u hostname netns2
unshare --net --uts /bin/bash &
export pid_netns3=$!
nsenter -t $pid_netns3 -u hostname netns3
nsenter -t $pid_netns3 -u -n /bin/bash
brctl addbr brx  
ip link set brx up
exit 
ip link add veth11 netns $pid_netns1 type veth peer name veth13 netns $pid_netns3
ip link add veth22 netns $pid_netns2 type veth peer name veth23 netns $pid_netns3
nsenter -t $pid_netns1 -u -n /bin/bash
ip addr add 192.168.5.1/24 brd 192.168.5.255 dev veth11
ip link set veth11 up
ip link set lo up
ip a s
exit
nsenter -t $pid_netns2 -u -n /bin/bash
ip addr add 192.168.5.2/24 brd 192.168.5.255 dev veth22
ip link set veth22 up
ip a s
exit
nsenter -t $pid_netns3 -u -n /bin/bash
ip link set veth13 up
ip link set veth23 up
brctl addif brx veth13
brctl addif brx veth23
exit

It is not necessary to show the reaction of the shell to these commands. But note the following:

  • The bridge has to be set into an UP status.
  • The veth interfaces located in netns3 do not get an IP address. Actually, a veth interface plays a different role on a bridge than in normal surroundings.
  • The bridge itself does not get an IP address.

Bridge ports
By attaching the veth interfaces to the bridge we create a "port" on the bridge, which corresponds to some complicated structures (handled by the kernel) for dealing with Ethernet packets crossing the port. You can imagine the situation as if e.g. the veth interface veth13 corresponds to the RJ45 end of a cable which is plugged into the port. Ethernet packets are taken at the plug, get modified sometimes and then are transferred across the port to the inside of the bridge.

However, when we assign an Ethernet address to the other interface, e.g. veth11 in netns1, then the veth "cable" ends in a full Ethernet device, which accepts network commands as "ping" or "nc".

No IP address for the bridge itself!
We do NOT assign an IP address to the bridge itself; this is a bit in contrast to what e.g. happens when you set up a bridge for networking with the tools of virt-manager. Or what e.g. Opensuse does, when you setup a KVM virtualization host with YaST. In all these cases something like

ip addr add 192.168.5.100/24 brd 192.168.5.255 dev brx 

happens in the background. However, I do not like this kind of implicit politics, because it opens ways into the namespace surrounding the bridge! And it is easy to forget this bridge interface both in VLAN and firewall rules.

Almost always, there is no necessity to provide an IP address to the bridge itself. If we need an interface of a namespace, a container or the host to a Linux bridge we can always use a veth device. This leads to a much is much clearer situation; you see the Ethernet interface and the port to the bridge explicitly - thus you have much better control, especially with respect to firewall rules.

Enter network namespace netns3:
Now we open a terminal as a sub shell (as we did in the previous example) and enter netns3 to have a look at the interfaces and the bridge.

mytux:~ # nsenter -t $pid_netns3 -u -n /bin/bash
netns3:~ # brctl show brx
bridge name     bridge id               STP enabled     interfaces
brx             8000.000000000000       no
netns3:~ # ip a s
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ce:fa:74:92:b5:00 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::1c08:76ff:fe0c:7dfe/64 scope link 
       valid_lft forever preferred_lft forever
3: veth13@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master brx state UP group default qlen 1000
    link/ether ce:fa:74:92:b5:00 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::ccfa:74ff:fe92:b500/64 scope link 
       valid_lft forever preferred_lft forever
4: veth23@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master brx state UP group default qlen 1000
    link/ether fe:5e:0b:d1:44:69 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::fc5e:bff:fed1:4469/64 scope link 
       valid_lft forever preferred_lft forever
netns3:~ # bridge link
3: veth13 state UP @brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master brx state forwarding priority 32 cost 2 
4: veth23 state UP @brx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master brx state forwarding priority 32 cost 2 

Let us briefly discuss some useful commands:

Incomplete information of "brctl show":
Unfortunately, the standard command

brctl show brx

does not work properly inside network namespaces; it does not produce a complete output. E.g., the attached interfaces are not shown. However, the command

ip a s

shows all interfaces and their respective "master". The same is true for the very useful "bridge" command :

bridge link

If you want to see even more details on interfaces use

ip -d a s

and grep the line for a specific interface.

Just for completeness: To create a bridge and add a veth devices to the bridge, we could also have used:

ip link add name brx type bridge
ip link set brx up
ip link set dev veth13 master brx
ip link set dev veth23 master brx

Connectivity test with ping
Now, let us turn to netns1 and test connectivity:

mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # ip a s 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: veth11@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 6a:4d:0c:30:12:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.1/24 brd 192.168.5.255 scope global veth11
       valid_lft forever preferred_lft forever                                                       
    inet6 fe80::684d:cff:fe30:1204/64 scope link                                                     
       valid_lft forever preferred_lft forever                                                       
netns1:~ # ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.039 ms
64 bytes from 192.168.5.2: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 192.168.5.2: icmp_seq=3 ttl=64 time=0.054 ms
^C
--- 192.168.5.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.039/0.046/0.054/0.006 ms
netns1:~ # nc -l 41234

Note that - as expected - we do not see anything of the bridge and its interfaces in netns1! Note that the bridge basically is a device on the data link layer, i.e. OSI layer 2. In the current configuration we did nothing to stop the propagation of Ethernet packets on this layer - this will change in further experiments.

Connectivity test with netcat
At the end of our test we used the netcat command "nc" to listen on a TCP port 41234. At another (sub) terminal we can now start a TCP communication from netns2 to the TCP port 41234 in netns1:

mytux:~ # nsenter -t $pid_netns2 -u -n /bin/bash
netns2:~ # nc 192.168.5.1 41234
alpha
beta

This leads to an output after the last command in netns1:

netns1:~ # nc -l 41234
alpha
beta

So, we have full connectivity - not only for ICMP packets, but also for TCP packets. In yet another terminal:

  
mytux:~ # nsenter -t $pid_netns1 -u -n /bin/bash
netns1:~ # netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 *:41234                 *:*                     LISTEN      
tcp        0      0 192.168.5.1:41234       192.168.5.2:45122       ESTABLISHED 
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags       Type       State         I-Node Path
netns1:~ # 

Conclusion

It is pretty easy to connect network namespaces with veth devices. The interfaces can be assigned to different network namespaces by using a variant of the "ip" command. The target network namespaces can be identified by PIDs of their basic processes. We can link to namespaces directly via the interfaces of one veth device.

An alternative is to use a Linux bridge (for Layer 2 transport) in yet another namespace. The third namespace provides better isolation; the bridge is out of the view and control of the other namespaces.

We have seen that the commands "ip a s" and "bridge link" are useful to get information about the association of bridges and their assigned interfaces/ports in network namespaces.

In the coming article we extend our efforts to creating VLANs with the help of our Linux bridge. Stay tuned ....

Linux bridges – can iptables be used against MiM attacks based on ARP spoofing ? – III

This small series of blog contributions was written to understand a little better how to use iptables in the context of Linux bridges as a countermeasure against some of the effects of a man-in-the-middle [MiM] attack based on ARP spoofing. The attacking system as well as the attacked systems are in our scenarios attached to Linux bridge ports. My objective was to block redirected TCP/IP packets to the the attacking system.

In the first article
Linux bridges – can iptables be used against MiM attacks based on ARP spoofing ? – I
we had discussed how we have to set up iptables rules for ports of a single Linux bridge and their associated IP-addresses to get the desired blocking. We found that a certain order of DENY and ACCEPT rules is required.

In the second article
Linux bridges – can iptables be used against MiM attacks based on ARP spoofing ? – II
we investigated how iptables reacts to the existence of multiple and linked Linux bridges on one and the same hast. We defined "border ports" as ports that connect a Linux bridge to other bridges, to extended external network segments or to the virtualization host itself - but not to guests directly attached to the bridge via tap or veth-devices. "Border ports" may be passed by packets traveling to their destination across multiple bridges. We then extended our previous considerations on iptables rules to such "border ports" and found a general recipe for the order of the required DENY and ACCEPT rules for the ports of the multiple bridges.

In this article we shall test the required rules for the bridge test setup presented in the previous article. We consider some examples of attack variations with respect to the 2 bridges and ICMP packets. However, the tests would also work for any TCP based service. The reason is that the central DENY rules are very general and compiled without reference to any specific service type.

We assume that you have had a look at the screenshots of the logical rules displayed in a FWbuilder interface in the last article (II).

Setup and iptables rules created by FWbuilder

See the following drawing for the setup of our test scenario:

bridge3

The general DENY rules and the ICMP-related ACCEPT rules displayed in the last article are compiled by FWbuilder to create the following script commands:


    # Rule 2 (vk63)
    # 
    echo "Rule 2 (vk63)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_2
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk63 !  -d 192.168.50.13   -j Out_RULE_2
    $IPTABLES -A Out_RULE_2  -j LOG  --log-level info --log-prefix "RULE 2 -- DENY "
    $IPTABLES -A Out_RULE_2  -j DROP
    # 
    # Rule 3 (vk64)
    # 
    echo "Rule 3 (vk64)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_3
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk64 !  -d 192.168.50.14   -j Out_RULE_3
    $IPTABLES -A Out_RULE_3  -j LOG  --log-level info --log-prefix "RULE 3 -- DENY "
    $IPTABLES -A Out_RULE_3  -j DROP
    # 
    # Rule 4 (vk65)
    # 
    echo "Rule 4 (vk65)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_4
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk65 !  -d 192.168.50.15   -j Out_RULE_4
    $IPTABLES -A Out_RULE_4  -j LOG  --log-level info --log-prefix "RULE 4 -- DENY "
    $IPTABLES -A Out_RULE_4  -j DROP
    # 
    # Rule 5 (vk42)
    # 
    echo "Rule 5 (vk42)"
    # 
    # virbr4 guest port
    $IPTABLES -N Out_RULE_5
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk42 !  -d 192.168.50.12   -j Out_RULE_5
    $IPTABLES -A Out_RULE_5  -j LOG  --log-level info --log-prefix "RULE 5 -- DENY "
    $IPTABLES -A Out_RULE_5  -j DROP
    # 
    # Rule 6 (vk63)
    # 
    echo "Rule 6 (vk63)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_6
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk63  -s 192.168.50.13   -j Out_RULE_6
    $IPTABLES -A Out_RULE_6  -j LOG  --log-level info --log-prefix "RULE 6 -- DENY "
    $IPTABLES -A Out_RULE_6  -j DROP
    # 
    # Rule 7 (vk64)
    # 
    echo "Rule 7 (vk64)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_7
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk64  -s 192.168.50.14   -j Out_RULE_7
    $IPTABLES -A Out_RULE_7  -j LOG  --log-level info --log-prefix "RULE 7 -- DENY "
    $IPTABLES -A Out_RULE_7  -j DROP
    # 
    # Rule 8 (vk65)
    # 
    echo "Rule 8 (vk65)"
    # 
    # virbr6 guest port
    $IPTABLES -N Out_RULE_8
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk65  -s 192.168.50.15   -j Out_RULE_8
    $IPTABLES -A Out_RULE_8  -j LOG  --log-level info --log-prefix "RULE 8 -- DENY "
    $IPTABLES -A Out_RULE_8  -j DROP
    # 
    # Rule 9 (vk42)
    # 
    echo "Rule 9 (vk42)"
    # 
    # virbr4 guest port
    $IPTABLES -N Out_RULE_9
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk42  -s 192.168.50.12   -j Out_RULE_9
    $IPTABLES -A Out_RULE_9  -j LOG  --log-level info --log-prefix "RULE 9 -- DENY "
    $IPTABLES -A Out_RULE_9  -j DROP
    # 
    # Rule 10 (vk63)
    # 
    echo "Rule 10 (vk63)"
    # 
    # virbr6 guest port
    $IPTABLES -N In_RULE_10
    $IPTABLES -A INPUT -m physdev --physdev-in vk63 !  -s 192.168.50.13   -j In_RULE_10
    $IPTABLES -A FORWARD -m physdev --physdev-in vk63 !  -s 192.168.50.13   -j In_RULE_10
    $IPTABLES -A In_RULE_10  -j LOG  --log-level info --log-prefix "RULE 10 -- DENY "
    $IPTABLES -A In_RULE_10  -j DROP
    # 
    # Rule 11 (vk64)
    # 
    echo "Rule 11 (vk64)"
    # 
    # virbr6 guest port
    $IPTABLES -N In_RULE_11
    $IPTABLES -A INPUT -m physdev --physdev-in vk64 !  -s 192.168.50.14   -j In_RULE_11
    $IPTABLES -A FORWARD -m physdev --physdev-in vk64 !  -s 192.168.50.14   -j In_RULE_11
    $IPTABLES -A In_RULE_11  -j LOG  --log-level info --log-prefix "RULE 11 -- DENY "
    $IPTABLES -A In_RULE_11  -j DROP
    # 
    # Rule 12 (vk65)
    # 
    echo "Rule 12 (vk65)"
    # 
    # virbr6 guest port
    $IPTABLES -N In_RULE_12
    $IPTABLES -A INPUT -m physdev --physdev-in vk65 !  -s 192.168.50.15   -j In_RULE_12
    $IPTABLES -A FORWARD -m physdev --physdev-in vk65 !  -s 192.168.50.15   -j In_RULE_12
    $IPTABLES -A In_RULE_12  -j LOG  --log-level info --log-prefix "RULE 12 -- DENY "
    $IPTABLES -A In_RULE_12  -j DROP
    # 
    # Rule 13 (vk42)
    # 
    echo "Rule 13 (vk42)"
    # 
    # virbr4 guest port
    $IPTABLES -N In_RULE_13
    $IPTABLES -A INPUT -m physdev --physdev-in vk42 !  -s 192.168.50.12   -j In_RULE_13
    $IPTABLES -A FORWARD -m physdev --physdev-in vk42 !  -s 192.168.50.12   -j In_RULE_13
    $IPTABLES -A In_RULE_13  -j LOG  --log-level info --log-prefix "RULE 13 -- DENY "
    $IPTABLES -A In_RULE_13  -j DROP
    # 
    # Rule 15 (vethb2)
    # 
    echo "Rule 15 (vethb2)"
    # 
    # br6 border out
    $IPTABLES -N Cid7404X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vethb2  -j Cid7404X2034.0
    $IPTABLES -A Cid7404X2034.0  -s 192.168.50.13   -j RETURN
    $IPTABLES -A Cid7404X2034.0  -s 192.168.50.14   -j RETURN
    $IPTABLES -A Cid7404X2034.0  -s 192.168.50.15   -j RETURN
    $IPTABLES -N Out_RULE_15_3
    $IPTABLES -A Cid7404X2034.0  -j Out_RULE_15_3
    $IPTABLES -A Out_RULE_15_3  -j LOG  --log-level info --log-prefix "RULE 15 -- DENY "
    $IPTABLES -A Out_RULE_15_3  -j DROP
    # 
    # Rule 16 (vethb2)
    # 
    echo "Rule 16 (vethb2)"
    # 
    # br6 border out
    $IPTABLES -N Cid7478X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vethb2  -j Cid7478X2034.0
    $IPTABLES -A Cid7478X2034.0  -d 192.168.50.1   -j RETURN
    $IPTABLES -A Cid7478X2034.0  -d 192.168.0.37   -j RETURN
    $IPTABLES -A Cid7478X2034.0  -d 192.168.50.12   -j RETURN
    $IPTABLES -N Out_RULE_16_3
    $IPTABLES -A Cid7478X2034.0  -j Out_RULE_16_3
    $IPTABLES -A Out_RULE_16_3  -j LOG  --log-level info --log-prefix "RULE 16 -- DENY "
    $IPTABLES -A Out_RULE_16_3  -j DROP
    # 
    # Rule 17 (vethb1)
    # 
    echo "Rule 17 (vethb1)"
    # 
    # br4 border out
    $IPTABLES -N Cid8637X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vethb1  -j Cid8637X2034.0
    $IPTABLES -A Cid8637X2034.0  -s 192.168.50.1   -j RETURN
    $IPTABLES -A Cid8637X2034.0  -s 192.168.0.37   -j RETURN
    $IPTABLES -A Cid8637X2034.0  -s 192.168.50.12   -j RETURN
    $IPTABLES -N Out_RULE_17_3
    $IPTABLES -A Cid8637X2034.0  -j Out_RULE_17_3
    $IPTABLES -A Out_RULE_17_3  -j LOG  --log-level info --log-prefix "RULE 17 -- DENY "
    $IPTABLES -A Out_RULE_17_3  -j DROP
    # 
    # Rule 18 (vethb1)
    # 
    echo "Rule 18 (vethb1)"
    # 
    # br4 border out
    $IPTABLES -N Cid8753X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vethb1  -j Cid8753X2034.0
    $IPTABLES -A Cid8753X2034.0  -d 192.168.50.13   -j RETURN
    $IPTABLES -A Cid8753X2034.0  -d 192.168.50.14   -j RETURN
    $IPTABLES -A Cid8753X2034.0  -d 192.168.50.15   -j RETURN
    $IPTABLES -N Out_RULE_18_3
    $IPTABLES -A Cid8753X2034.0  -j Out_RULE_18_3
    $IPTABLES -A Out_RULE_18_3  -j LOG  --log-level info --log-prefix "RULE 18 -- DENY "
    $IPTABLES -A Out_RULE_18_3  -j DROP
    # 
    # Rule 19 (vmh1)
    # 
    echo "Rule 19 (vmh1)"
    # 
    # br4 border out
    $IPTABLES -N Cid8374X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vmh1  -j Cid8374X2034.0
    $IPTABLES -A Cid8374X2034.0  -s 192.168.50.12   -j RETURN
    $IPTABLES -A Cid8374X2034.0  -s 192.168.50.13   -j RETURN
    $IPTABLES -A Cid8374X2034.0  -s 192.168.50.14   -j RETURN
    $IPTABLES -A Cid8374X2034.0  -s 192.168.50.15   -j RETURN
    $IPTABLES -N Out_RULE_19_3
    $IPTABLES -A Cid8374X2034.0  -j Out_RULE_19_3
    $IPTABLES -A Out_RULE_19_3  -j LOG  --log-level info --log-prefix "RULE 19 -- DENY "
    $IPTABLES -A Out_RULE_19_3  -j DROP
    # 
    # Rule 20 (vmh1)
    # 
    echo "Rule 20 (vmh1)"
    # 
    # br4 border out
    $IPTABLES -N Cid8530X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vmh1  -j Cid8530X2034.0
    $IPTABLES -A Cid8530X2034.0  -d 192.168.50.1   -j RETURN
    $IPTABLES -A Cid8530X2034.0  -d 192.168.0.37   -j RETURN
    $IPTABLES -N Out_RULE_20_3
    $IPTABLES -A Cid8530X2034.0  -j Out_RULE_20_3
    $IPTABLES -A Out_RULE_20_3  -j LOG  --log-level info --log-prefix "RULE 20 -- DENY "
    $IPTABLES -A Out_RULE_20_3  -j DROP
    # 
    # Rule 22 (vethb2)
    # 
    echo "Rule 22 (vethb2)"
    # 
    # br6 border IN
    $IPTABLES -N Cid7917X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vethb2  -j Cid7917X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb2  -j Cid7917X2034.0
    $IPTABLES -A Cid7917X2034.0  -d 192.168.50.13   -j RETURN
    $IPTABLES -A Cid7917X2034.0  -d 192.168.50.14   -j RETURN
    $IPTABLES -A Cid7917X2034.0  -d 192.168.50.15   -j RETURN
    $IPTABLES -N In_RULE_22_3
    $IPTABLES -A Cid7917X2034.0  -j In_RULE_22_3
    $IPTABLES -A In_RULE_22_3  -j LOG  --log-level info --log-prefix "RULE 22 -- DENY "
    $IPTABLES -A In_RULE_22_3  -j DROP
    # 
    # Rule 23 (vethb2)
    # 
    echo "Rule 23 (vethb2)"
    # 
    # br6 border IN
    $IPTABLES -N Cid8013X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vethb2  -j Cid8013X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb2  -j Cid8013X2034.0
    $IPTABLES -A Cid8013X2034.0  -s 192.168.50.1   -j RETURN
    $IPTABLES -A Cid8013X2034.0  -s 192.168.0.37   -j RETURN
    $IPTABLES -A Cid8013X2034.0  -s 192.168.50.12   -j RETURN
    $IPTABLES -N In_RULE_23_3
    $IPTABLES -A Cid8013X2034.0  -j In_RULE_23_3
    $IPTABLES -A In_RULE_23_3  -j LOG  --log-level info --log-prefix "RULE 23 -- DENY "
    $IPTABLES -A In_RULE_23_3  -j DROP
    # 
    # Rule 24 (vethb1)
    # 
    echo "Rule 24 (vethb1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid7639X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vethb1  -j Cid7639X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb1  -j Cid7639X2034.0
    $IPTABLES -A Cid7639X2034.0  -s 192.168.50.13   -j RETURN
    $IPTABLES -A Cid7639X2034.0  -s 192.168.50.14   -j RETURN
    $IPTABLES -A Cid7639X2034.0  -s 192.168.50.15   -j RETURN
    $IPTABLES -N In_RULE_24_3
    $IPTABLES -A Cid7639X2034.0  -j In_RULE_24_3
    $IPTABLES -A In_RULE_24_3  -j LOG  --log-level info --log-prefix "RULE 24 -- DENY "
    $IPTABLES -A In_RULE_24_3  -j DROP
    # 
    # Rule 25 (vethb1)
    # 
    echo "Rule 25 (vethb1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid7736X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vethb1  -j Cid7736X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb1  -j Cid7736X2034.0
    $IPTABLES -A Cid7736X2034.0  -d 192.168.50.1   -j RETURN
    $IPTABLES -A Cid7736X2034.0  -d 192.168.0.37   -j RETURN
    $IPTABLES -A Cid7736X2034.0  -d 192.168.50.12   -j RETURN
    $IPTABLES -N In_RULE_25_3
    $IPTABLES -A Cid7736X2034.0  -j In_RULE_25_3
    $IPTABLES -A In_RULE_25_3  -j LOG  --log-level info --log-prefix "RULE 25 -- DENY "
    $IPTABLES -A In_RULE_25_3  -j DROP
    # 
    # Rule 26 (vmh1)
    # 
    echo "Rule 26 (vmh1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid8881X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vmh1  -j Cid8881X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vmh1  -j Cid8881X2034.0
    $IPTABLES -A Cid8881X2034.0  -s 192.168.50.1   -j RETURN
    $IPTABLES -A Cid8881X2034.0  -s 192.168.0.37   -j RETURN
    $IPTABLES -N In_RULE_26_3
    $IPTABLES -A Cid8881X2034.0  -j In_RULE_26_3
    $IPTABLES -A In_RULE_26_3  -j LOG  --log-level info --log-prefix "RULE 26 -- DENY "
    $IPTABLES -A In_RULE_26_3  -j DROP
    # 
    # Rule 27 (vmh1)
    # 
    echo "Rule 27 (vmh1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid9010X2034.0
    $IPTABLES -A INPUT -m physdev --physdev-in vmh1  -j Cid9010X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vmh1  -j Cid9010X2034.0
    $IPTABLES -A Cid9010X2034.0  -d 192.168.50.12   -j RETURN
    $IPTABLES -A Cid9010X2034.0  -d 192.168.50.13   -j RETURN
    $IPTABLES -A Cid9010X2034.0  -d 192.168.50.14   -j RETURN
    $IPTABLES -A Cid9010X2034.0  -d 192.168.50.15   -j RETURN
    $IPTABLES -N In_RULE_27_3
    $IPTABLES -A Cid9010X2034.0  -j In_RULE_27_3
    $IPTABLES -A In_RULE_27_3  -j LOG  --log-level info --log-prefix "RULE 27 -- DENY "
    $IPTABLES -A In_RULE_27_3  -j DROP
    # 
    # Rule 29 (vmh2)
    # 
    echo "Rule 29 (vmh2)"
    # 
    # host OUT
    $IPTABLES -N Cid10297X2034.0
    $IPTABLES -A OUTPUT -o vmh2   -s 192.168.0.19   -j Cid10297X2034.0
    $IPTABLES -A OUTPUT -o vmh2   -s 192.168.50.1   -j Cid10297X2034.0
    $IPTABLES -A Cid10297X2034.0  -d 192.168.50.12   -j RETURN
    $IPTABLES -A Cid10297X2034.0  -d 192.168.50.13   -j RETURN
    $IPTABLES -A Cid10297X2034.0  -d 192.168.50.14   -j RETURN
    $IPTABLES -A Cid10297X2034.0  -d 192.168.50.15   -j RETURN
    $IPTABLES -N Out_RULE_29_3
    $IPTABLES -A Cid10297X2034.0  -j Out_RULE_29_3
    $IPTABLES -A Out_RULE_29_3  -j LOG  --log-level info --log-prefix "RULE 29 -- DENY "
    $IPTABLES -A Out_RULE_29_3  -j DROP
    # 
    # Rule 30 (vmh2)
    # 
    echo "Rule 30 (vmh2)"
    # 
    # host IN
    $IPTABLES -N Cid10437X2034.0
    $IPTABLES -A INPUT -i vmh2   -d 192.168.0.19   -j Cid10437X2034.0
    $IPTABLES -A INPUT -i vmh2   -d 192.168.50.1   -j Cid10437X2034.0
    $IPTABLES -A FORWARD -i vmh2   -d 192.168.0.37   -j Cid10437X2034.0
    $IPTABLES -A Cid10437X2034.0  -s 192.168.50.12   -j RETURN
    $IPTABLES -A Cid10437X2034.0  -s 192.168.50.13   -j RETURN
    $IPTABLES -A Cid10437X2034.0  -s 192.168.50.14   -j RETURN
    $IPTABLES -A Cid10437X2034.0  -s 192.168.50.15   -j RETURN
    $IPTABLES -N In_RULE_30_3
    $IPTABLES -A Cid10437X2034.0  -j In_RULE_30_3
    $IPTABLES -A In_RULE_30_3  -j LOG  --log-level info --log-prefix "RULE 30 -- DENY "
    $IPTABLES -A In_RULE_30_3  -j DROP
    # 
    # Rule 32 (vk63)
    # 
    echo "Rule 32 (vk63)"
    # 
    # br6 IN
    $IPTABLES -N In_RULE_32
    $IPTABLES -A INPUT -m physdev --physdev-in vk63 -p icmp  -m icmp  -s 192.168.50.13   --icmp-type any  -m state --state NEW  -j In_RULE_32
    $IPTABLES -A FORWARD -m physdev --physdev-in vk63 -p icmp  -m icmp  -s 192.168.50.13   --icmp-type any  -m state --state NEW  -j In_RULE_32
    $IPTABLES -A In_RULE_32  -j LOG  --log-level info --log-prefix "RULE 32 -- ACCEPT "
    $IPTABLES -A In_RULE_32  -j ACCEPT
    # 
    # Rule 33 (vk64)
    # 
    echo "Rule 33 (vk64)"
    # 
    # br6 IN
    $IPTABLES -N In_RULE_33
    $IPTABLES -A INPUT -m physdev --physdev-in vk64 -p icmp  -m icmp  -s 192.168.50.14   --icmp-type any  -m state --state NEW  -j In_RULE_33
    $IPTABLES -A FORWARD -m physdev --physdev-in vk64 -p icmp  -m icmp  -s 192.168.50.14   --icmp-type any  -m state --state NEW  -j In_RULE_33
    $IPTABLES -A In_RULE_33  -j LOG  --log-level info --log-prefix "RULE 33 -- ACCEPT "
    $IPTABLES -A In_RULE_33  -j ACCEPT
    # 
    # Rule 34 (vk64)
    # 
    echo "Rule 34 (vk64)"
    # 
    # br6 IN HTTP
    $IPTABLES -N In_RULE_34
    $IPTABLES -A FORWARD -m physdev --physdev-in vk64 -p tcp -m tcp  -m multiport  -s 192.168.50.14   -d 192.168.0.37   --dports 80,443  -m state --state NEW  -j In_RULE_34
    $IPTABLES -A In_RULE_34  -j LOG  --log-level info --log-prefix "RULE 34 -- ACCEPT "
    $IPTABLES -A In_RULE_34  -j ACCEPT
    # 
    # Rule 35 (vk65)
    # 
    echo "Rule 35 (vk65)"
    # 
    # br6 IN
    $IPTABLES -N In_RULE_35
    $IPTABLES -A INPUT -m physdev --physdev-in vk65 -p icmp  -m icmp  -s 192.168.50.15   --icmp-type any  -m state --state NEW  -j In_RULE_35
    $IPTABLES -A FORWARD -m physdev --physdev-in vk65 -p icmp  -m icmp  -s 192.168.50.15   --icmp-type any  -m state --state NEW  -j In_RULE_35
    $IPTABLES -A In_RULE_35  -j LOG  --log-level info --log-prefix "RULE 35 -- ACCEPT "
    $IPTABLES -A In_RULE_35  -j ACCEPT
    # 
    # Rule 36 (vethb2)
    # 
    echo "Rule 36 (vethb2)"
    # 
    # br6 border IN
    $IPTABLES -N Cid9698X2034.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb2 -p icmp  -m icmp  -s 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid9698X2034.0
    $IPTABLES -N In_RULE_36
    $IPTABLES -A Cid9698X2034.0  -d 192.168.50.13   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.0  -d 192.168.50.14   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.0  -d 192.168.50.15   -j In_RULE_36
    $IPTABLES -N Cid9698X2034.1
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb2 -p icmp  -m icmp  -s 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid9698X2034.1
    $IPTABLES -A Cid9698X2034.1  -d 192.168.50.13   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.1  -d 192.168.50.14   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.1  -d 192.168.50.15   -j In_RULE_36
    $IPTABLES -N Cid9698X2034.2
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb2 -p icmp  -m icmp  --icmp-type any  -m state --state NEW  -j Cid9698X2034.2
    $IPTABLES -N Cid9698X2034.3
    $IPTABLES -A Cid9698X2034.2  -s 192.168.0.37   -j Cid9698X2034.3
    $IPTABLES -A Cid9698X2034.2  -s 192.168.50.12   -j Cid9698X2034.3
    $IPTABLES -A Cid9698X2034.3  -d 192.168.50.13   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.3  -d 192.168.50.14   -j In_RULE_36
    $IPTABLES -A Cid9698X2034.3  -d 192.168.50.15   -j In_RULE_36
    $IPTABLES -A In_RULE_36  -j LOG  --log-level info --log-prefix "RULE 36 -- ACCEPT "
    $IPTABLES -A In_RULE_36  -j ACCEPT
    # 
    # Rule 38 (vk42)
    # 
    echo "Rule 38 (vk42)"
    # 
    # br4 IN
    $IPTABLES -N In_RULE_38
    $IPTABLES -A INPUT -m physdev --physdev-in vk42 -p icmp  -m icmp  -s 192.168.50.12   --icmp-type any  -m state --state NEW  -j In_RULE_38
    $IPTABLES -A FORWARD -m physdev --physdev-in vk42 -p icmp  -m icmp  -s 192.168.50.12   --icmp-type any  -m state --state NEW  -j In_RULE_38
    $IPTABLES -A In_RULE_38  -j LOG  --log-level info --log-prefix "RULE 38 -- ACCEPT "
    $IPTABLES -A In_RULE_38  -j ACCEPT
    # 
    # Rule 39 (vethb1)
    # 
    echo "Rule 39 (vethb1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid15566X9203.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb1 -p icmp  -m icmp  -d 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid15566X9203.0
    $IPTABLES -N In_RULE_39
    $IPTABLES -A Cid15566X9203.0  -s 192.168.50.13   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.0  -s 192.168.50.14   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.0  -s 192.168.50.15   -j In_RULE_39
    $IPTABLES -N Cid15566X9203.1
    $IPTABLES -A INPUT -m physdev --physdev-in vethb1 -p icmp  -m icmp  -d 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid15566X9203.1
    $IPTABLES -A Cid15566X9203.1  -s 192.168.50.13   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.1  -s 192.168.50.14   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.1  -s 192.168.50.15   -j In_RULE_39
    $IPTABLES -N Cid15566X9203.2
    $IPTABLES -A FORWARD -m physdev --physdev-in vethb1 -p icmp  -m icmp  --icmp-type any  -m state --state NEW  -j Cid15566X9203.2
    $IPTABLES -N Cid15566X9203.3
    $IPTABLES -A Cid15566X9203.2  -d 192.168.0.37   -j Cid15566X9203.3
    $IPTABLES -A Cid15566X9203.2  -d 192.168.50.12   -j Cid15566X9203.3
    $IPTABLES -A Cid15566X9203.3  -s 192.168.50.13   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.3  -s 192.168.50.14   -j In_RULE_39
    $IPTABLES -A Cid15566X9203.3  -s 192.168.50.15   -j In_RULE_39
    $IPTABLES -A In_RULE_39  -j LOG  --log-level info --log-prefix "RULE 39 -- ACCEPT "
    $IPTABLES -A In_RULE_39  -j ACCEPT
    # 
    # Rule 40 (vmh1)
    # 
    echo "Rule 40 (vmh1)"
    # 
    # br4 border IN
    $IPTABLES -N Cid16232X9203.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vmh1 -p icmp  -m icmp  -s 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid16232X9203.0
    $IPTABLES -N In_RULE_40
    $IPTABLES -A Cid16232X9203.0  -d 192.168.50.12   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.0  -d 192.168.50.13   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.0  -d 192.168.50.14   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.0  -d 192.168.50.15   -j In_RULE_40
    $IPTABLES -N Cid16232X9203.1
    $IPTABLES -A FORWARD -m physdev --physdev-in vmh1 -p icmp  -m icmp  -s 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid16232X9203.1
    $IPTABLES -A Cid16232X9203.1  -d 192.168.50.12   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.1  -d 192.168.50.13   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.1  -d 192.168.50.14   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.1  -d 192.168.50.15   -j In_RULE_40
    $IPTABLES -N Cid16232X9203.2
    $IPTABLES -A FORWARD -m physdev --physdev-in vmh1 -p icmp  -m icmp  -s 192.168.0.37   --icmp-type any  -m state --state NEW  -j Cid16232X9203.2
    $IPTABLES -A Cid16232X9203.2  -d 192.168.50.12   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.2  -d 192.168.50.13   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.2  -d 192.168.50.14   -j In_RULE_40
    $IPTABLES -A Cid16232X9203.2  -d 192.168.50.15   -j In_RULE_40
    $IPTABLES -A In_RULE_40  -j LOG  --log-level info --log-prefix "RULE 40 -- ACCEPT "
    $IPTABLES -A In_RULE_40  -j ACCEPT
    # 
    # Rule 42 (vmh2)
    # 
    echo "Rule 42 (vmh2)"
    # 
    # external are IN
    $IPTABLES -N Cid16691X6788.0
    $IPTABLES -A INPUT -i vmh2  -p icmp  -m icmp  -d 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid16691X6788.0
    $IPTABLES -N In_RULE_42
    $IPTABLES -A Cid16691X6788.0  -s 192.168.50.12   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.0  -s 192.168.50.13   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.0  -s 192.168.50.14   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.0  -s 192.168.50.15   -j In_RULE_42
    $IPTABLES -N Cid16691X6788.1
    $IPTABLES -A FORWARD -i vmh2  -p icmp  -m icmp  -d 192.168.0.37   --icmp-type any  -m state --state NEW  -j Cid16691X6788.1
    $IPTABLES -A Cid16691X6788.1  -s 192.168.50.12   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.1  -s 192.168.50.13   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.1  -s 192.168.50.14   -j In_RULE_42
    $IPTABLES -A Cid16691X6788.1  -s 192.168.50.15   -j In_RULE_42
    $IPTABLES -A In_RULE_42  -j LOG  --log-level info --log-prefix "RULE 42 -- ACCEPT "
    $IPTABLES -A In_RULE_42  -j ACCEPT
    # 
    # Rule 43 (vmh2)
    # 
    echo "Rule 43 (vmh2)"
    # 
    # host border OUT
    $IPTABLES -N Cid16236X6788.0
    $IPTABLES -A OUTPUT -o vmh2  -p icmp  -m icmp  -s 192.168.50.1   --icmp-type any  -m state --state NEW  -j Cid16236X6788.0
    $IPTABLES -N Out_RULE_43
    $IPTABLES -A Cid16236X6788.0  -d 192.168.50.12   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.0  -d 192.168.50.13   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.0  -d 192.168.50.14   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.0  -d 192.168.50.15   -j Out_RULE_43
    $IPTABLES -N Cid16236X6788.1
    $IPTABLES -A FORWARD -o vmh2  -p icmp  -m icmp  -s 192.168.0.37   --icmp-type any  -m state --state NEW  -j Cid16236X6788.1
    $IPTABLES -A Cid16236X6788.1  -d 192.168.50.12   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.1  -d 192.168.50.13   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.1  -d 192.168.50.14   -j Out_RULE_43
    $IPTABLES -A Cid16236X6788.1  -d 192.168.50.15   -j Out_RULE_43
    $IPTABLES -A Out_RULE_43  -j LOG  --log-level info --log-prefix "RULE 43 -- ACCEPT "
    $IPTABLES -A Out_RULE_43  -j ACCEPT
    # 
    # Rule 45 (br0)
    # 
    echo "Rule 45 (br0)"
    # 
    # external
    $IPTABLES -A OUTPUT -o br0   -m state --state NEW  -j ACCEPT
    # 
    # Rule 46 (br0)
    # 
    echo "Rule 46 (br0)"
    # 
    # external
    $IPTABLES -A FORWARD -i br0   -s 192.168.0.10   -d 192.168.0.255   -m state --state NEW  -j ACCEPT
    $IPTABLES -A INPUT -i br0   -s 192.168.0.10   -d 192.168.0.255   -m state --state NEW  -j ACCEPT
    $IPTABLES -A INPUT -i br0   -s 192.168.0.10   -m state --state NEW  -j ACCEPT
    # 
    # Rule 47 (global)
    # 
    echo "Rule 47 (global)"
    # 
    $IPTABLES -N RULE_47
    $IPTABLES -A OUTPUT  -j RULE_47
    $IPTABLES -A INPUT  -j RULE_47
    $IPTABLES -A FORWARD  -j RULE_47
    $IPTABLES -A RULE_47  -j LOG  --log-level info --log-prefix "RULE 47 -- DENY "
    $IPTABLES -A RULE_47  -j DROP
}

 
The variable "$IPTABLES" identifies the local iptables command. As already discussed in the last articles we arranged our (virtual) guest systems, the virtualization host and external systems in 3 defined host groups in FWbuilder (see the last article):

  • br6_grp: kali3, kali4, kali5,
  • br4_grp: kali2,
  • ext_grp: the host and some external web server "lamp".

Remember that rules for bridge-ports are investigated separately and independently as a packet moves from one bridge to another. Note that the host and further systems attached to "virbr4" via a veth device "vmh2" are recognized as members of a distinct logical host area for which iptables rules again are reinvestigated separatly by the kernel during packet transport. Therefore we need ACCEPT rules to allow for incoming and outgoing packets at the host's interface "vmh2".

Examples of spoofing scenarios

With 2 bridges in place we can define already a variety of ARP spoofing scenarios with a subsequent MiM-attack. We only test some selected, but typical scenarios. Note again that we cannot prevent the act of spoofing itself with iptables - however, we can prevent that redirected packets arrive at the MiM system.

Example 1: kali2 of virbr4 attacks the communication between kali3 and kali5 within virbr6

Which rule do we expect to prevent this? Actually as kali2 tries to redirect the intended communication from bridge virbr6 into bridge virbr4 we would already expect a DENY rule at the border port "vethb2" to stop redirected packets. In our rules list this would be rule 16.

So let us see. We start ARP spoofing on kali2:

root@kali2: ~# echo 1 > /proc/sys/net/ipv4/ip_forward
root@kali2: ~# iptables -A OUTPUT -p icmp --icmp-type redirect -j REJECT
root@kali2: ~# arpspoof -i eth3 -t 192.168.50.13 192.168.50.14 & 2> /dev/null
root@kali2: ~# arpspoof -i eth3 -t 192.168.50.14 192.168.50.13 & 2> /dev/null

 
eth3 is the relevant Ethernet interface to net 192.168.50.0/24 on guest kali2.
After some time we get the following ARP information on e.g. kali3:

bridges_1

Consequently, after a "journalctl -f" on the virtualization host we find: :

Mar 17 13:21:53 mytux kernel: RULE 16 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=52:54:00:f2:a4:8d:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.14 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=16140 DF PROTO=ICMP TYPE=8 CODE=0 ID=1756 SEQ=1 
Mar 17 13:21:54 mytux kernel: RULE 16 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=52:54:00:f2:a4:8d:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.14 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=16252 DF PROTO=ICMP TYPE=8 CODE=0 ID=1756 SEQ=2

 
Our test example shows that rules for border ports help to isolate bridges against misguided packets.

Rule 16 deserves a closer look as it contains a logical negation of 2 separately defined groups of hosts. We see that FWbuilder compiles the negation internally correctly: The related subchain definition contains all required hosts.

As described in the previous articles we stop the attack by the command "killall arpspoof" on kali2. Remember that due to time limits for ARP and port caching information on the guests and the bridge, respectively, it may take some time until normal operation is possible again. See the first article of this series for more information on this topic.

Example 2: kali2 of virbr4 attacks the communication between kali3 and the virtualization host

In this scenario a regular (!) packet would propagate from virbr6 through virbr4 and then to the host. Therefore, neither border port rules for virbr6 nor for virbr4 can block the traffic. We must, instead, rely on the analysis of redirected packets following an OUT direction to port vk42 - this is rule 5.

Therefore, this example is just a repetition of what we learned in the first article of this series
Linux bridges – can iptables be used against MiM attacks based on ARP spoofing ? – I

Actually, after another spoofing attack by kali2

root@kali2: ~# arpspoof -i eth3 -t 192.168.50.1 192.168.50.13 & 2> /dev/null
root@kali2: ~# arpspoof -i eth3 -t 192.168.50.13 192.168.50.1 & 2> /dev/null

 
and sending pings from kali3 to the host we get:

Mar 17 18:44:51 mytux kernel: RULE 32 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=54:00:f2:a4:8d:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.1 LEN=84 TOS=0x00 PREC=0x00 =64 ID=48428 DF PROTO=ICMP TYPE=8 CODE=0 ID=2872 SEQ=2 
Mar 17 18:44:51 mytux kernel: RULE 5 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vk42 MAC=52:50:f2:a4:8d:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.1 LEN=84 TOS=0x00 PREC=0x00 TTL ID=48428 DF PROTO=ICMP TYPE=8 CODE=0 ID=2872 SEQ=2 

 
We see that the transition from bridge virbr6 to virbr4 works as planned - however the packets redirected to the MiM kali2 are stopped at vk42. Good!

Example 3: kali3 of virbr6 attacks the communication between kali4 of virbr4 and the virtualization host

We look at pings issued from the host to kali4 after an attack of kali3. In this case the border port rules again must not block. Instead, we rely on local port rules at port vk63, .i.e. rule 2. Indeed:

Mar 17 19:00:39 mytux kernel: RULE 43 -- ACCEPT IN= OUT=vmh2 SRC=192.168.50.1 DST=192.168.50.14 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=53576 DF PROTO=ICMP TYPE=8 CODE=0 ID=2981 SEQ=1 
Mar 17 19:00:39 mytux kernel: RULE 40 -- ACCEPT IN=virbr4 OUT=virbr4 PHYSIN=vmh1 PHYSOUT=vethb1 MAC=52:54:00:b1:5d:1f:7a:ff:fc:bd:68:b6:08:00 SRC=192.168.50.1 DST=192.168.50.14 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=53576 DF PROTO=ICMP TYPE=8 CODE=0 ID=2981 SEQ=1 
Mar 17 19:00:39 mytux kernel: RULE 2 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vethb2 PHYSOUT=vk63 MAC=52:54:00:b1:5d:1f:7a:ff:fc:bd:68:b6:08:00 SRC=192.168.50.1 DST=192.168.50.14 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=53576 DF PROTO=ICMP TYPE=8 CODE=0 ID=2981 SEQ=1 

 

Example 4: kali3 of virbr6 attacks the communication between kali2 and the virtualization host

In this case border rules should stop redirected packets. For our test case this would in particular be rule 18.

And - after the initialization of the attack by kali3 and the trial to send pings from kali2 to the host, we get:

Mar 18 16:47:30 mytux kernel: RULE 18 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vk42 PHYSOUT=vethb1 MAC=52:54:00:b1:5d:1f:52:54:00:f2:a4:8d:08:00 SRC=192.168.50.12 DST=192.168.50.1 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=4405 DF PROTO=ICMP TYPE=8 CODE=0 ID=2420 SEQ=1 

 
And vice versa :

Mar 18 16:48:42 mytux kernel: RULE 43 -- ACCEPT IN= OUT=vmh2 SRC=192.168.50.1 DST=192.168.50.12 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=63251 DF PROTO=ICMP TYPE=8 CODE=0 ID=21778 SEQ=1 
Mar 18 16:48:42 mytux kernel: RULE 18 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vmh1 PHYSOUT=vethb1 MAC=52:54:00:b1:5d:1f:f2:be:a1:5a:cd:6e:08:00 SRC=192.168.50.1 DST=192.168.50.12 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=63251 DF PROTO=ICMP TYPE=8 CODE=0 ID=21778 SEQ=1 

 
As expected!

Example 5: The host attacks communication between guests attached to an inner bridge

One may think such an example is just academic. Actually, in my opinion it is not. Although the administrator of a virtualization host has in principle a variety of means available to follow any communication across a bridge, ARP spoofing should NOT be such a measure under normal operation conditions. In addition, there may be legal aspects in a professional hosting situation.

But more important: From the perspective of the involved bridges, in our setup the host is attached to bridge virbr4 as an external guest over a border port. Rules for the virtualization host are, therefore, only an example for similar rules applied to other external hosts which may have the allowance to communicate with bridge guests - via forwarding and a respective route defined on the virtualization host.

We expect rule 20 to stop packages redirected by the MiM:

         
Mar 18 17:38:38 rux kernel: RULE 32 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=f2:be:a1:5a:cd:6e:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.12 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=36173 DF PROTO=ICMP TYPE=8 CODE=0 ID=2218 SEQ=1 
Mar 18 17:38:38 rux kernel: RULE 20 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vmh1 MAC=f2:be:a1:5a:cd:6e:52:54:00:b1:5d:1f:08:00 SRC=192.168.50.13 DST=192.168.50.12 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=36173 DF PROTO=ICMP TYPE=8 CODE=0 ID=2218 SEQ=1 

 
And vice versa

       
Mar 18 17:39:39 rux kernel: RULE 20 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vk42 PHYSOUT=vmh1 MAC=f2:be:a1:5a:cd:6e:52:54:00:f2:a4:8d:08:00 SRC=192.168.50.12 DST=192.168.50.13 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=10910 DF PROTO=ICMP TYPE=8 CODE=0 ID=2730 SEQ=1 
Mar 18 17:39:40 rux kernel: RULE 20 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vk42 PHYSOUT=vmh1 MAC=f2:be:a1:5a:cd:6e:52:54:00:f2:a4:8d:08:00 SRC=192.168.50.12 DST=192.168.50.13 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=11113 DF PROTO=ICMP TYPE=8 CODE=0 ID=2730 SEQ=2 

 

Summary

So, all in all, for our few examples we could verify that our recipe for setting up iptables rules in case of several linked Linux bridges with guests on one [KVM] virtualization host guided us correctly. After associating unique IP addresses with bridge ports we can define rules that block the transport of packets redirected to a MiM system - even when multiple bridges are present on the virtualization host. Additional and special rules for the bridges' border ports help to prevent irregular traffic between defined groups of guests and/or external hosts.

Note that we only demonstrated this for specific allowance rules for the ICMP protocol. Yet, it is easy to understand that the very same principles should work for any protocol on level 4.

In the next article of this series we will look at an example with an external host and eventually discuss what our test results mean for administrators of virtualization hosts.

Linux bridges – can iptables be used against MiM attacks based on ARP spoofing ? – I

This article and a following one are about some simple iptables exercises on Linux virtual bridges. Linux bridges are typically used in virtualization environments. However, guest systems or even the host attached to a Linux bridge may become targets of "man in the middle" attacks. During such attacks the guests and the bridge may be manipulated to send packets to the "man in the middle" system and not directly to the intended communication partners. My objective is to get a clearer picture of iptables' possible contribution to defense measures against such attacks.

Some "Howtos" on the Internet warn explicitly against using iptables at all on Linux bridges - especially not with active connection tracking. An example is the "libvirt wiki": http://wiki.libvirt.org/page/Networking. Some of the warnings refer to an original discussion published here: http://patchwork.ozlabs.org/patch/29319/
See also: https://bugzilla.redhat.com/show_bug.cgi?id=512206

I think these concerns justify a closer look at iptables rules with respect to bridge ports. Comments are welcome.

Scenarios, limitations and objectives

In our test case we work with a KVM host with one bridge and later on also with two linked Linux bridges. In this first article we use one of the Linux guests on one of the bridges to initiate a "man in the middle attack" [MiM] against other guests of the very same bridge via ARP spoofing and packet redirection. We then define some reasonable iptables rules with the intention to block the redirected traffic (to the MiM) and analyze the impact of these rules.
In a second article we extend the game to 2 bridges and the host port at one of the bridges.

Limitations and restrictions:
It is obvious that we cannot prevent ARP-spoofing itself with iptables. iptables works on network layers 3/4, but not on layer 2 (Ethernet). iptables, therefore, does not allow for direct restrictions regarding the ARP protocol. So, the prevention of ARP packets with false MAC addresses, which typically initiate a MiM attack is not the objective of this article. It requires ebtables and/or arptables to block ARP spoofing at its roots. So, do not misunderstand me:

I do not and would not recommend to base any packet filter security across a Linux bridge on iptables alone. If you must use netfilter on bridges always combine iptables with basic ebtables/arptables rules - and test thoroughly against different kinds of attacks which try to break guest isolation. Always be aware of the fact that a bridge creates a global context in which packets must be inspected and followed precisely in their changing role as outgoing or incoming with respect to the bridge itself and its virtual interface ports. Global connection tracking on the TCP/IP level may be dangerous. If you give the bridge itself an IP - a situation which I do not at all like from a security perspective - take extra care. Things get even more complicated with multiple bridges on one and the same host.

Objectives :
Nevertheless I think that one can learn something even from academic and unusual test configurations with iptables alone in place:

If we cannot prevent ARP spoofing itself by iptables - can we at least use iptables rules to deal with some consequences of ARP spoofing? More precisely:
Can we block the redirection of packets between ARP poisoned guests over the MiM system by means of IPtables alone? What relations of IP addresses and port devices have to be defined? And would a tool like FWbuilder support us reasonably enough with this task?

If so: How would we extend IPtables rules to situations

  • where two Linux bridges are linked (by veth devices)
  • or segregated network parts with all guests belonging to the same logical IP segment are coupled via STP and border Ethernet interfaces of a central Linux bridge?

In both cases the spoofed communication may pass border NICs of a Linux bridge.

In this first article on the topic we look at one bridge alone with three guests. In a following article we shall consider linked bridges.

One bridge - 3 guests

Let us assume that we have 3 guests "kali3, kali4 and kali5" on a Linux bridge "virbr6". The bridge device itself has no IP. The guest systems are attached to the bridge via standard tap device ports (vk63, vk64 and vk65, respectively) created e.g. by libvirt's virt-manager. See article KVM/qemu, libvirt, virt-manager – persistent names for virtual network bridge ports of guest systems about how to set persistent names for the bridge sided end of "tap"-devices.

The corresponding Ethernet interface side (here "eth0") of the guest operative systems - i.e. the guest side of the tap devices - are given the following IP addresses: 192.168.50.13 (eth0), 192.168.50.14 (eth0) and 192.168.50.15 (eth0), respectively.

bridge

How does the host see the bridge-ports?

mytux:~ # brctl showmacs virbr6
port no mac addr                is local?       ageing timer
  1     52:54:00:8e:f2:d7       yes                0.00
  2     5e:f4:32:30:f1:3a       yes                0.00
  2     aa:bf:ba:dc:52:31       no                 1.35
  3     fe:54:00:9f:5d:c1       yes                0.00
  4     fe:54:00:74:60:4a       yes                0.00
  5     fe:54:00:0f:34:4f       yes                0.00

 
5 ports instead of 3 ? Yeah, actually my virbr6 bridge is connected to another bridge (virbr4) by a veth pair which we most of the time ignore in this article. If you are interested in Linux bridge linking via "veth" devices see
Fun with veth devices, Linux virtual bridges, KVM, VMware – attach the host and connect bridges via veth

The veth pair explains the 2 MACs on port Nr. 2. A parallel look at the outcome of "ifconfig" or "ip link show" would show that port 3 actually corresponds to tap device "vk63", port 4 corresponds to "vk64" and port 5 to "vk65". And port 1? The Linux bridge itself could also work as an Ethernet device which could get an IP address on the host. We do not use this property here - nevertheless there is an Ethernet port associated with the bridge itself.

How does the host see the (regular) IP-MAC relations so far? After pinging our 3 guests from the host we get :

mytux:~ # arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.50.15            ether   52:54:00:0f:34:4f   C                     vmh2
....
192.168.50.13            ether   52:54:00:9f:5d:c1   C                     vmh2
192.168.50.14            ether   52:54:00:74:60:4a   C                     vmh2

 
We recognize our tap devices attached to the bridge. [By the way: vmh2 is a device that connects the host to one of the bridges (virbr4).]

Addendum 24.02.2016: Note a small, but decisive difference in the HW/MAC addresses:
The first digit pair in the Ethernet address of the port device (i.e. the bridge sided end of the tap device) has a "fe", whereas the Ethernet device of the Linux guest (i.e. the guest sided end of the tap device) has a "52". The rest of the digits being the same. Logically, and also from the perspective of the bridge, these are 2 different (!) devices (though incorporated in one virtual tap). From the point of view of the bridge multiple MACs or even a new bridge may be located at the Ethernet segment behind a port.

Be aware of the fact that the so called "forward database" of a bridge [FDB], which relates MACs to ports, keeps track of the relation of our Linux guest MACs to their specific ports. Whereas the port MAC (with the leading "fe") is permanently associated with the bridge, the MAC of the guest may disappear from the FDB after a timeout period, if no packets are received at the bridge from this guest MAC address.

In addition we have set

mytux:~ # brctl setageing virbr6 30
mytux:~ # brctl setageing virbr4 30

to be sure that the bridge works in a switch like mode (and not as a hub). Note that this defines a timeout period for the bridge's FDB - i.e. after this period "stale" entries in the FDB of each bridge may be deleted. So the bridge may no longer know at which port the deleted MAC is located - and therefore temporarily flood all ports with packets. Therefore, bridge flooding is a situation we may need to cover with iptables rules later on.

If you want to monitor changes of the bridges' FDB or monitor general changes over all bridge links use the following commands:

bridge monitor all

and

bridge -statistics fdb show

The continuous output of the first command will show you directly when a stale MAC entry in the FDB is deleted. If you issue the second command twice with a reasonable time period in between you may search the output for missing or new MAC entries of guests.

Note further that we did not give the bridge itself any IP address! The bridge may therefore be called "transparent". As "virbr6" has no IP address the guests (kali3 to 5) can not directly communicate with the host through the bridge itself as an Ethernet device. Just for information: In our scenario the host can only be reached indirectly over a transparently linked second bridge (virbr4) and a further veth pair there which leads to an external Ethernet device with address 192.168.50.1.

ICMP packages and regular pinging - what do we allow?

First we shall have a look at ICMP packages, only. Our basic policy with iptables is that we deny everything that is not explicitly allowed. Regarding further rules we should be aware of the following:

When setting up iptables rules on bridges we must be precise and specific with respect to the packet direction across the involved bridge port interfaces. It is the perspective of the bridge and NOT the perspective of the guest that counts. I always use a 3D picture to be sure: Assume the bridge and its ports to be located above the guests. Then a packet going up is incoming, a packet moving downwards is outgoing.

Let us assume you want to ping from kali3 to kali5. From the point of view of the bridge there are 2 packet directions involved: We first get an incoming ICMP (type 8) packet via bridge interface "vk63", which then is directed (or "forwarded") outwards through "vk65". To allow for the pinging we would need rules of the logical form

bridge vibr6 rule : src 192.168.50.13, dest 192.168.50.15 - ICMP in via vk31, out via vk65 => ALLOW

and analogously for the other guests and interfaces. Actually this rule may be split up into 2 subsequent rules:

bridge vibr6 rule :  src 192.168.50.13, dest 192.168.50.15 - ICMP in via vk31 => ALLOW
bridge vibr6 rule :  src 192.168.50.13, dest 192.168.50.15 - ICMP out via vk65 => ALLOW

 
which are to be considered in the basic chains of iptables. This leads us to the next question: Which iptables chain is relevant here?

In our example it is the FORWARD chain. For the interaction of netfilter components (ebtables/iptables) in kernels with activated netfilter see the following link: http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html
That we need to set up FORWARD rules is also logical as the bridge does nothing else than forwarding packets between its ports and thus transfer the packets to attached destination guests or into segregated network parts behind some of the ports (with the "spanning tree protocol" STP set to ON).

ARP spoofing and the bridge

Consider now a situation in which guest "kali4" acts as a "man in the middle" who wants to sniff the traffic (e.g. for "secrets") between kali3 and kali5. A user with root rights on kali4 would use a ARP spoofing tool like "dsniff" and arp-poison its neighbouring guests via the following command sequence:

root@kali4: ~# echo 1 > /proc/sys/net/ipv4/ip_forward
root@kali4: ~# iptables -A OUTPUT -p icmp --icmp-type redirect -j REJECT
root@kali4: ~# arpspoof -i eth0 -t 192.168.50.13 192.168.50.15 & 2> /dev/null
root@kali4: ~# arpspoof -i eth0 -t 192.168.50.15 192.168.50.13 & 2> /dev/null

 
The first command guarantees that redirected and sniffed packets are forwarded (routed) via the MiM system (kali4) to their original targets. The second command on the MiM-system avoids sporadic ICMP "redirect" answers to the poisoned and pinging guests - such answers would/could indicate to these guests that something is wrong. The 3rd and the 4th command eventually poison the internal ARP caching tables of the guests. I.e., these commands spoil the cached information on IP-MAC relations after some time. Let us look at kali3 - before the attack:

kali3_arp

And during the attack:

kali3_arp2

In a previous article of this blog we saw that a Linux bridge learns about the relation of MAC addresses and bridge ports - and thus pins a specific communication down to just the 2 involved ports of a specific communication (basic guest isolation). The bridge normally does not spread communication packets over all ports (at least with a setageing parameter > 0).

Note that this does not help to prevent MiM attacks. As the bridge itself works on layer 2 it ignores IP-MAC relations during packet forwarding. (It may learn about IP-MAC relations only through the ARP protocol.) The bridge furthermore does not know whether routing may occur somewhere. And the guests themselves cannot ignore that situations where several IP addresses may be associated with one and the same MAC are possible. Because of all these reasons Ethernet packets are inevitably sent and forwarded across the bridge were the guests think they should be sent to - according to their own internal ARP tables, which become poisoned during the attack.

Therefore after ARP spoofing the bridge would receive 2 subsequent ping request packets from kali3 and from kali4 with the logical route

src 192.168.50.13, dest 192.168.50.15 - ICMP ping request in via vk63, out via vk64
src 192.168.50.13, dest 192.168.50.15 - ICMP ping request in via vk64, out via vk65

 
And the ping answers back via

src 192.168.50.15, dest 192.168.50.13 - ICMP ping answer in via vk65, out via vk64
src 192.168.50.15, dest 192.168.50.13 - ICMP ping answer in via vk64, out via vk63

 

A small side aspect: I should mention that despite the switch-like operational mode of the Linux bridge, I sometimes - very rarely - saw that even the KVM host reacted towards the ARP poisoning and showed some wrong entries in its internal ARP cache table - some time after the attack started. I have not clarified, yet, what the reason for this change of the hosts ARP table actually is. If some reader knows the reason please write me a mail. I suspect gratuitous packets, or (more likely) some rare hub like flooding situation on the bridge, but ...

E.g. after a restart of all virtual machines, the begin of the ARP poisoning and after pinging the host continuously from the MiM system for a while, you may eventually find the following ARP table change on the KVM host :

mytux:~ # arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.50.15            ether   52:54:00:0f:34:4f   C                     vmh2
192.168.50.13            ether   52:54:00:9f:5d:c1   C                     vmh2
192.168.50.14            ether   52:54:00:74:60:4a   C                     vmh2
mytux::~ # arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.50.15            ether   52:54:00:74:60:4a   C                     vmh2
192.168.50.13            ether   52:54:00:74:60:4a   C                     vmh2
192.168.50.14            ether   52:54:00:74:60:4a   C                     vmh2

 

And the port-MAC-association? It remains as it was:

mytux:~ # brctl showmacs virbr6
port no mac addr                is local?       ageing timer
  ...
  5     fe:54:00:0f:34:4f       yes                0.00
  4     fe:54:00:74:60:4a       yes                0.00
  3     fe:54:00:9f:5d:c1       yes                0.00

 
Be aware, however, of the fact that this information tells us nothing about the present state of the FDB table of the bridge! Actually, due to our "setageing" parameter certain MAC addresses of guests may drop out of the forward list of the bridge, if the guests are inactive with respect to network communication, and this in turn may result in a subsequent (temporary) bridge port flooding.

So, if you stop the ARP poisoning, reset the ARP tables and start the spoofing again, an ARP poisoning of the host itself it may not happen directly. It may, however, happen after some time. (By the way: Any direct pinging from the host to the guests will correct the ARP table to the real values again - at least for some time.)

Anyway and whatever the precise reason - it is interesting that there obviously are circumstances under which the local poisoning of bridge guests may impact even the ARP table on the bridge host itself. On the defense side this may give us a secondary chance (besides monitoring the violation of ebtables rules) to detect ARP spoofing attacks: by monitoring the host's internal ARP table and analyzing its contents for implausible changes.

iptables rules to prevent misguided packets

To avoid part of the redirected packet transport across the Linux bridge we would require a rule of the logical form

bridge vibr6 rule :  src any, dest 192.168.50.15 - in any, out via vk64 => DENY

 
We can reformulate the rule with a negation (!) in a more general way:

bridge vibr6 rule :  src any, !dest 192.168.50.14 - in any, out via vk64 => DENY

 
In addition it is reasonable to forbid packets which (seem to) come from kali3 and are "outbound" to kali3:

bridge vibr6 rule :  src 192.168.50.13, dest any - in any, out via vk63 => DENY

 
Also incoming packets via vk63 from sources not being kali3 make no sense:

bridge vibr6 rule :  ! src 192.168.50.13, dest any - in vk63, out any => DENY

 
Actually, on our bridge we would have to cover analogous variants of all of the above DENY rules for all other guest ports and protocols.

Note that all these rules define fixed relations between each of the defined bridge ports, an associated IP and certain packet directions across the port: with iptables alone we are restricted to such types of relations.

Graphical help - FWbuilder

A problem with the relations above is that they are potentially many - depending at least quadratically on the number of guests on a bridge. An efficient administration requires either a tool or good scripting experience or both. A tool like FWbuilder at least supports us graphically:

fwb_4

The rules created for the shown conditions look like:

    # 
    # Rule 2 (vk63)
    # 
    echo "Rule 2 (vk63)"
    # 
    $IPTABLES -N Out_RULE_2
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk63 !  -d 192.168.50.13   -j Out_RULE_2
    $IPTABLES -A Out_RULE_2  -j LOG  --log-level info --log-prefix "RULE 2 -- DENY "
    $IPTABLES -A Out_RULE_2  -j DROP
    # 
    # Rule 3 (vk64)
    # 
    echo "Rule 3 (vk64)"
    # 
    $IPTABLES -N Out_RULE_3
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk64 !  -d 192.168.50.14   -j Out_RULE_3
    $IPTABLES -A Out_RULE_3  -j LOG  --log-level info --log-prefix "RULE 3 -- DENY "
    $IPTABLES -A Out_RULE_3  -j DROP
    # 
    # Rule 4 (vk65)
    # 
    echo "Rule 4 (vk65)"
    # 
    $IPTABLES -N Out_RULE_4
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk65 !  -d 192.168.50.15   -j Out_RULE_4
    $IPTABLES -A Out_RULE_4  -j LOG  --log-level info --log-prefix "RULE 4 -- DENY "
    $IPTABLES -A Out_RULE_4  -j DROP
    # 
    .....
    .....
    # Rule 6 (vk63)
    # 
    echo "Rule 6 (vk63)"
    # 
    $IPTABLES -N Out_RULE_6
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk63  -s 192.168.50.13   -j Out_RULE_6
    $IPTABLES -A Out_RULE_6  -j LOG  --log-level info --log-prefix "RULE 6 -- DENY "
    $IPTABLES -A Out_RULE_6  -j DROP
    # 
    # Rule 7 (vk64)
    # 
    echo "Rule 7 (vk64)"
    # 
    $IPTABLES -N Out_RULE_7
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk64  -s 192.168.50.14   -j Out_RULE_7
    $IPTABLES -A Out_RULE_7  -j LOG  --log-level info --log-prefix "RULE 7 -- DENY "
    $IPTABLES -A Out_RULE_7  -j DROP
    # 
    # Rule 8 (vk65)
    # 
    echo "Rule 8 (vk65)"
    # 
    $IPTABLES -N Out_RULE_8
    $IPTABLES -A FORWARD -m physdev --physdev-is-bridged --physdev-out vk65  -s 192.168.50.15   -j Out_RULE_8
    $IPTABLES -A Out_RULE_8  -j LOG  --log-level info --log-prefix "RULE 8 -- DENY "
    $IPTABLES -A Out_RULE_8  -j DROP
    # 
    ....
    ....
    # 
    # Rule 11 (vk63)
    # 
    echo "Rule 11 (vk63)"
    # 
    $IPTABLES -N In_RULE_11
    $IPTABLES -A INPUT -m physdev --physdev-in vk63 !  -s 192.168.50.13   -j In_RULE_11
    $IPTABLES -A FORWARD -m physdev --physdev-in vk63 !  -s 192.168.50.13   -j In_RULE_11
    $IPTABLES -A In_RULE_11  -j LOG  --log-level info --log-prefix "RULE 11 -- DENY "
    $IPTABLES -A In_RULE_11  -j DROP
    # 
    # Rule 12 (vk64)
    # 
    echo "Rule 12 (vk64)"
    # 
    $IPTABLES -N In_RULE_12
    $IPTABLES -A INPUT -m physdev --physdev-in vk64 !  -s 192.168.50.14   -j In_RULE_12
    $IPTABLES -A FORWARD -m physdev --physdev-in vk64 !  -s 192.168.50.14   -j In_RULE_12
    $IPTABLES -A In_RULE_12  -j LOG  --log-level info --log-prefix "RULE 12 -- DENY "
    $IPTABLES -A In_RULE_12  -j DROP
    # 
    # Rule 13 (vk65)
    # 
    echo "Rule 13 (vk65)"
    # 
    $IPTABLES -N In_RULE_13
    $IPTABLES -A INPUT -m physdev --physdev-in vk65 !  -s 192.168.50.15   -j In_RULE_13
    $IPTABLES -A FORWARD -m physdev --physdev-in vk65 !  -s 192.168.50.15   -j In_RULE_13
    $IPTABLES -A In_RULE_13  -j LOG  --log-level info --log-prefix "RULE 13 -- DENY "
    $IPTABLES -A In_RULE_13  -j DROP

 

 
Ignoring some optimization potential, this is actually what we need. You see the clue:
FWbuilder knows about the bridge situation (see below) and creates rules with options

-m physdev --physdev-in/out device

The documentation from http://www.fwbuilder.org/4.0/docs/users_guide5/host-interface.shtml says accordingly:

Bridge port: This option is used for a port of a bridged firewall. The compilers skip bridge ports when they pick interfaces to attach policy and NAT rules to. For target firewall platforms that support bridging and require special configuration parameters to match bridged packets, compilers use this attribute to generate a proper configuration. For example, in case of iptables, the compiler uses -m physdev --physdev-in or -m physdev --physdev-out for bridge port interfaces. (This object applies to firewall objects only.)

It requires, however, a special configuration of FWbuilder with respect to the defined interfaces and the bridges on the firewall system - i.e. the virtualization host in our test situation:

fwb_4

The same of course for bridge "virbr6".

Note that our rules (produced by FWbuilder above) for the bridge ports vk63, vk64, vk65 would also work in case of a port flooding situation - if they are not circumvented by other leading rules. The latter being a point we shall come back to.

What packets do we allow?

On a firewall with a basic drop policies we need, of course, to define acceptance conditions for packets, too. Without going into details we need logical rules like:

bridge vibr6 rule :  src 192.168.50.13, dest 192.168.50.15, 192.168.50.14, any ICMP - in via vk31   => ALLOW

 

An example is shown here:

fwb_5

# Rule 21 (vk63)
    # 
    echo "Rule 21 (vk63)"
    # 
    $IPTABLES -N In_RULE_21
    $IPTABLES -A FORWARD -m physdev --physdev-in vk63 -p icmp  -m icmp  -s 192.168.50.13   -d 192.168.50.1   --icmp-type any  -m state --state NEW  -j In_RULE_21
    $IPTABLES -A INPUT -m physdev --physdev-in vk63 -p icmp  -m icmp  -s 192.168.50.13   -d 192.168.50.1   --icmp-type any  -m state --state NEW  -j In_RULE_21
    $IPTABLES -N Cid8093X19506.0
    $IPTABLES -A FORWARD -m physdev --physdev-in vk63 -p icmp  -m icmp  -s 192.168.50.13   --icmp-type any  -m state --state NEW  -j Cid8093X19506.0
    $IPTABLES -A Cid8093X19506.0  -d 192.168.50.12   -j In_RULE_21
    $IPTABLES -A Cid8093X19506.0  -d 192.168.50.14   -j In_RULE_21
    $IPTABLES -A Cid8093X19506.0  -d 192.168.50.15   -j In_RULE_21
    $IPTABLES -A In_RULE_21  -j LOG  --log-level info --log-prefix "RULE 21 -- ACCEPT "
    $IPTABLES -A In_RULE_21  -j ACCEPT

 
We need of course all variants for all the other bridge interfaces. To make life simpler you could define groups of recipients in a tool like FWbuilder.

Rules order

We eventually come to a trivial but important point: In which order must we arrange the discussed iptables DENY and ACCEPT commands? A little thinking shows:

We need the "DENY"-rules first before we allow anything else - i.e. we need the basic DENY rules discussed above as the leading rules in all affected chains!

If a packet is first allowed - e.g. due to some reasonable IN rule - then it definitely is allowed. To be on the safe side we, therefore, must probe the critical FORWARD rules for unacceptable outgoing and incoming packets over certain bridge ports, first.

A really critical aspect in the context is a potentially applied overall acceptance of packets for established connections (connection tracking). For most stateful inspection packet filters the general acceptance of incoming packets for established connections is a default.

E.g., in FWbuilder you have to turn this policy off explicitly, if you do not want to have it. Otherwise, FWbuilder will create general acceptance rules for all 3 basic chains ahead of all other rules:

    # ================ Table 'filter', automatic rules
    # accept established sessions
    $IPTABLES -A INPUT   -m state --state ESTABLISHED,RELATED -j ACCEPT 
    $IPTABLES -A OUTPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT 
    $IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT   

 
Note, that these rules would cover ALL bridges and ALL related interfaces/ports on a virtualization host (global context of acceptance)! This makes such leading rules potentially dangerous on hosts with bridges! Both during ARP spoofing attacks, but also in port flooding situations - as the ports work in a promiscuous mode. Be aware of the fact that the attack pattern discussed above could in principle be extended to guests on other bridges on the host, if the attacker knew the relevant IP addresses.

On the other side acceptance rules for established connections actually can really be convenient. My conclusion is: Either you use a set of very general iptables rules that require no connection tracking on the bridge at all - and then your guest systems must establish their own firewalls. Or :

Whatever your FW-Tool generates: Edit the resulting script and move the acceptance rule for established connections after/below the set of critical "DENY" rules on the bridge interfaces discussed above. Check in addition that the DENY rules themselves really are set as stateless rules.

Testing

Let us say kali3 pings kali5 after ARP poisoning. What can the MiM on "kali4" really see then - if no firewall rules are implemented on the host? As expected all and everything:

kali4_wshark_1

You see the poisoning packets and the redirected (duplicated) messages between kali3 and kali5. The same would of course be true for any kind of real TCP/IP communication. So without any measures the MiM can follow all communication after spoofing.

Now let us implement the iptables rules discussed above. In our test case we expect our "rule 3" to block the redirected (misguided) traffic to kali4. And really:

kali3_ping1

And at the same time on the host:

mytux:~/bin # tail -f /var/log/firewall
...
...
2016-02-23T14:41:17.783163+01:00 mytux kernel: [33572.296587] RULE 3 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk64 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=837 DF PROTO=ICMP TYPE=8 CODE=0 ID=2401 SEQ=1 
2016-02-23T14:41:18.790152+01:00 mytux kernel: [33573.304717] RULE 3 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk64 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=869 DF PROTO=ICMP TYPE=8 CODE=0 ID=2401 SEQ=2 
2016-02-23T14:41:19.798127+01:00 mytux kernel: [33574.313685] RULE 3 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk64 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=977 DF PROTO=ICMP TYPE=8 CODE=0 ID=2401 SEQ=3

 
Good! Defense is obviously possible - even on the IP-level - as soon as we relate bridge ports to IP information!

Stopping ARP spoofing - with potential port flooding on the bridge as an aftermath

At some point in time the MiM attacker may stop his spoofing by

root@kali4:~# killall arpspoof
root@kali4:~# echo 1 > /proc/sys/net/ipv4/ip_forward

Before the poisoning jobs terminate themselves they send some packets which try to correct the corrupted ARP information on the attacked guests. However, depending on the load of the guests and the host this correction may go wrong - on one or both poisoned guests - and the old spoofed information may remain in the guests' ARP tables:

kali3_ping2

And even some seconds later:

kali3_ping3

kali3 still thinks that 192.168.50.15 is located at the MAC address of kali4! How long this wrong information is kept depends on the relevant timeout parameter for local ARP table cache entries - see:

cat /proc/sys/net/ipv4/neigh/default/gc_stale_time

For our Debian guests this parameter typically has a value of 60 secs.

The picture above shows that on kali3 first 9 ICMP request packets were sent which got no answer. Later on a second series of pinging requests work normally again. In this specific test case - with a remaining wrong ARP information on kali3 - actually 2 interesting things happened in parallel:

mytux:~/bin # tail -f /var/log/firewall
2016-02-23T16:18:32.972806+01:00 mytux kernel: [ 1909.777744] RULE 21 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk65 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
2016-02-23T16:18:32.972820+01:00 mytux kernel: [ 1909.777774] RULE 3 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk64 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
2016-02-23T16:18:32.972822+01:00 mytux kernel: [ 1909.777785] RULE 21 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
2016-02-23T16:18:32.972823+01:00 mytux kernel: [ 1909.777806] RULE 5 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vnet0 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
2016-02-23T16:18:32.972824+01:00 mytux kernel: [ 1909.777818] RULE 35 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vmw1 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
2016-02-23T16:18:32.972825+01:00 mytux kernel: [ 1909.777827] RULE 35 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vmh1 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=19648 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=1 
....
.....
2016-02-23T16:18:40.972821+01:00 mytux kernel: [ 1917.786358] RULE 21 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk65 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
2016-02-23T16:18:40.972847+01:00 mytux kernel: [ 1917.786378] RULE 3 -- DENY IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk64 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
2016-02-23T16:18:40.972850+01:00 mytux kernel: [ 1917.786385] RULE 21 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vethb2 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
2016-02-23T16:18:40.972852+01:00 mytux kernel: [ 1917.786397] RULE 5 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vnet0 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
2016-02-23T16:18:40.972854+01:00 mytux kernel: [ 1917.786404] RULE 35 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vmw1 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
2016-02-23T16:18:40.972856+01:00 mytux kernel: [ 1917.786410] RULE 35 -- DENY IN=virbr4 OUT=virbr4 PHYSIN=vethb1 PHYSOUT=vmh1 MAC=52:54:00:74:60:4a:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=20666 DF PROTO=ICMP TYPE=8 CODE=0 ID=1373 SEQ=9 
.....
2016-02-23T16:19:07.924844+01:00 mytux kernel: [ 1944.768264] RULE 21 -- ACCEPT IN=virbr6 OUT=virbr6 PHYSIN=vk63 PHYSOUT=vk65 MAC=52:54:00:0f:34:4f:52:54:00:9f:5d:c1:08:00 SRC=192.168.50.13 DST=192.168.50.15 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=25179 DF PROTO=ICMP TYPE=8 CODE=0 ID=1376 SEQ=1 

 
Where do the reactions at other ports than vk64 come from? The first part of the explanation is that the bridge temporarily flooded all its ports (vk64, vk65, vethb1) with the ping requests of kali3! This in turn lead to local denial reactions on virbr6 and also on our second bridge (vribr4). For the reason of the flooding see below.

The second strange thing is that during each of the nine ping trials a successful packet submission occurs through port vk65 - but there is no log entry for an answer packet. Why is this?

Port flooding means copying of packets for the submission over all bridge ports other than the port of the incoming packet. The (in our case wrong) destination MAC addresses of the packets included. The bridge "hopes" for an answer of the addressed MAC at one of the ports. But is this going to happen in our test situation - in which kali3 sends requests out still to the wrong MAC of kali 4?

No - because despite flooding and acceptance for transport over port vk65, kali5 rightfully ignores the copied packets due to their wrong destination MAC. On the other side kali4 will not receive anything due to the iptables rules and cannot react either. So, we end up in a situation where ICMP request packets are sent by kali3 - but no answer will return from any bridge port. This in turn leads to the fact that the bridge is not learning what it needs to learn to stop the flooding. This situation will at least remain until the ARP cache table on kali3 is corrected/updated.

So only with a subsequent new ping series - and after the ARP table of kali3 has been updated - everything works as expected.

Addendum, 24.02.2016:
Some reader asked me via mail to explain why flooding occurred at all. This is a good question - and I have therefore added relevant remarks into the text above. Due to our limited "setageing" parameter some "guest MAC - port" relation may be deleted from the bridges forward database (FDB) after some time. (In addition we may have impacts of the STP protocol.) With our setaging parameter and the active iptables rules kali5 will drop out of the FDB pretty soon (after 30 secs) : the original pinging during the attack situation will not reach kali5, and kali5 otherwise remains passive. However, also kali4 drops out 30 secs after stopping the spoofing attack from the FDB. So, we may reach a situation where kali3 still has the wrong ARP information, but kali4's MAC is no longer in the FDB. We ended up in a kind of race condition between timeouts of the bridge's FDB and ARP table cache renewal on the guests.

Due to the fact that either of the spoofed guests may still have wrong ARP information after the spoofing is stopped by the attacker various strange situations may occur. kali3 may have the right ARP information, but kali5 not yet. Then answering packets may be created which try to reach kali4 instead of kali3. Such packets must not be allowed by any acceptance rules (including established relation rules) - hence again: we need the DENY rules first.

What have we learned from all this?

  1. The stop of the ARP spoofing can leave the bridge and some of the guests in a unconsolidated mode for some time - despite a few final packets from the attacker system to restore ARP information on the attacked systems. One or two of the attacked guests and the host may still keep wrong entries in its/their ARP table(s). The duration of such a situation depends on the local timeout parameter for the ARP caching table entries on the guest systems.
  2. With a limited "setageing" parameter of the bridge, port flooding is not improbable during a period after the end of a ARP spoofing attack. As a consequence, the firewall rules must prevent the consequences of port flooding, too. Therefore, we need to take care not only of guest ports, but also of border ports which lead to segregated parts of the net or to other bridges.
  3. In the course of port flooding, response packets may be directed to wrong recipients. This status will remain until the ARP tables of the guests are updated. During such phase the defined DENY rules must be probed first before any kind of acceptance rules.
  4. Regarding the competition of the different timeouts on ARP caching tables and bridge FDBs: A conclusion in case of relatively stable guest-port relations might be to set the FDB timeout (setaging parameter of the bridge) to reasonably large values (in the range of a few minutes) to avoid flooding situations. On the other side the timeout for local ARP caching could be reduced as long as this does not create unreasonable ARP traffic.

What about TCP/IP packets?

If we think a bit about the general rules discussed above, we may understand that they would work also for standard TCP/IP packets of general TCP protocols. Actually, we have defined the leading denial rules for wrong "IP/port/direction"-associations without any reference to a specific protocol. So, our rules should hold in the general case, too. The reader may test this by configuring one of the guests as a web server or by using "netcat" to set up a simple server on one of the bridge guests.

We shall investigate a related full TCP scenario in one of the next articles - where we shall follow packets across 2 bridges and to the host. So, be patient, if you do not want to perform experiments, yourself.

Summary

Obviously netfilter iptables rules can not prevent ARP spoofing and resulting "man in the middle attack" trials on virtual guest systems attached to Linux bridges of a virtualization host. However, properly designed iptables rules can intercept and interrupt the redirected traffic a MiM system attached to the bridge wants to provoke.

Appropriate iptables rules testing predefined IP-port relations on bridges may therefore supplement and accompany additional measures on the ebtables/arptables level of netfilter. However, such rules should not be undermined by leading acceptance rules related to connection tracking.

Even an already stopped ARP spoofing attack may leave the bridge and its guests in an unconsolidated status for a while. In addition flooding of packets to all bridge ports may occur. Appropriate denial rules for guest ports and Ethernet border ports in STP situations must block the resulting improper traffic. The reduction of flooding situations may require an adaption of the "setageing" parameter to reasonably large values for predictably stable configurations of guests on a bridge.

Most important: General acceptance rules for established connections should only be applied in the sequence of firewall rules AFTER all critical (denial) rules regarding unacceptable traffic across certain ports have been tested for incoming/outgoing packets. This may require explicit changes of the scripts created by Firewall tools like FWbuilder.

A significant problem is the requirement that the association of IP addresses and ports must be known or determined at the time of the definition and/or application of the filter rules. This requires persistent port naming techniques and under certain circumstances also persistent MAC distribution techniques plus DHCP restrictions for the guests within the used virtualization environment.

In the next article of this series
Linux bridges - can iptables be used against MiM attacks based on ARP spoofing ? - II
we discuss how we can extend our rules to scenarios with multiple bridges on one host - and discover that we need a special treatment of packets crossing bridge borders.