faking services on an entire IP subnet – part II (L3 NAT and fake services in docker)

In our last post, we used a new L2 OVS proxy to fake TCP services on an entire IP subnet. We used network namespaces for isolation. However, using namespaces can make running fake services under docker somewhat inconvenient. So this time around, we’re going to have pipette do L3 NAT for us as well, so we can just attach services as we please. We’re going to fake a webserver on port 80 (as docker based webservers are easy to come by).

We are using the same physical setup as last time. However, we are now using a slightly different FAUCET ACL. We are going to work with TCP port 80 only, and only on 192.168.2.0/24. Note that we are also instructing FAUCET to always add a VLAN VID tag when it dispatches the intercepted traffic to the coprocessor (not strictly necessary but does make things more consistent).

acls:  
  coprocessssh:
  - rule:
      dl_type: 0x800
      ip_proto: 6
      ipv4_src: 192.168.2.0/24
      ipv4_dst: 192.168.2.0/24
      tcp_dst: 80
      actions:
        output:
          vlan_vid: 2
          ports: [18]
  - rule:
      actions:
        allow: 1

We’ll also need a new version of pipette – this one is a bit smarter – it can do L3 NAT. Specifically, it will NAT 192.168.2.0/24 to 192.168.101.0/24 on the coprocessor. That way we can just run services that listen on 192.168.101.1/24, and they’ll appear in the real 192.168.2.0/24 network, magically with the right MAC addresses.

We’ll start pipette like this:

#!/bin/bash

# interface connected to FAUCET coprocessor port.
COPROINT=enx0023565c8859
# interface that will be created for fake services to run on.
FAKEINT=fake0
# Reserved MAC addresses for fake services to use to talk to clients.
FAKEHW=0e:00:00:00:00:66
FAKECLIENTHW=0e:00:00:00:00:67
# address fake services will be run on (will be proxied from real IPs)
FAKEIP=192.168.101.1/24
# OVS bridge name
BR=copro0
# pipette OF port
OF=6699

# Configure pipette's OVS switch.
# Remove all IP addresses, disable IPv6.
ip link add dev $FAKEINT type veth peer name ovs$FAKEINT
for i in $COPROINT $FAKEINT ovs$FAKEINT ovs-system ; do
  echo 1 > /proc/sys/net/ipv6/conf/$i/disable_ipv6
  ifconfig $i 0.0.0.0
done
ifconfig $COPROINT up
ifconfig ovs$FAKEINT up
ifconfig $FAKEINT hw ether $FAKEHW $FAKEIP up
ovs-vsctl del-br $BR 
ovs-vsctl add-br $BR
ovs-ofctl del-flows $BR
for i in $COPROINT ovs$FAKEINT ; do
  ovs-vsctl add-port $BR $i
done
ovs-vsctl set-controller $BR tcp:127.0.0.1:$OF

# Run pipette.
ryu-manager pipette.py --ofp-tcp-listen-port $OF  --verbose

Now, we can start a webserver on 192.168.101.1:

pi@coprocessor:~ $ docker run -d -p 192.168.101.1:80:80 hypriot/rpi-busybox-httpd
89425b1198b3eb30267f05fa10bc3691efc7b36710297e4695030705ee09c9bb
pi@coprocessor:~ $ docker ps
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS              PORTS                      NAMES
89425b1198b3        hypriot/rpi-busybox-httpd   "/bin/busybox httpd …"   11 seconds ago      Up 9 seconds        192.168.101.1:80->80/tcp   keen_goldberg

Now all it remains for us to do, is from outside the coprocessor, try to access the webserver “allegedly” running on 192.168.2.1:

pi@pi8021x:~ $ wget -q -O- 192.168.2.1 80
<html>
<head><title>Pi armed with Docker by Hypriot</title>
  <body style="width: 100%; background-color: black;">
    <div id="main" style="margin: 100px auto 0 auto; width: 800px;">
      <img src="pi_armed_with_docker.jpg" alt="pi armed with docker" style="width: 800px">
    </div>
  </body>
</html>

With pipette now doing L3 NAT, we can start pretty much whatever services we like listening on fake0, and provided we use the right FAUCET ACL to intercept the traffic, those services will appear in the dataplane.