Faking services on an entire IP subnet – controllerless proxying.

In part II we demonstrated faking IP services for an entire subnet. Now, let’s eliminate the need for an interactive OpenFlow control entirely, with the magic OVS learn actions. These are almost like OVS assembly language instructions, that allow OVS rewrite packets or even add flows to itself. Once new pipette has programmed OVS, once, OVS can respond to ARP requests and do the L2/L3 NAT translation entirely by itself.

This means we don’t have to rely on the controller being up to handle new connections, and connection handling is fast.

Here’s how OVS looks after it has just been programmed by pipette:

# ovs-ofctl -OOpenFlow13 dump-flows copro0
 cookie=0x0, duration=6.758s, table=0, n_packets=0, n_bytes=0, priority=1,in_port=enx0023565c8859,vlan_tci=0x1000/0x1000 actions=pop_vlan,goto_table:1
 cookie=0x0, duration=6.758s, table=0, n_packets=0, n_bytes=0, priority=1,tcp,in_port=enx0023565c8859 actions=goto_table:1
 cookie=0x0, duration=6.758s, table=0, n_packets=0, n_bytes=0, priority=1,udp,in_port=enx0023565c8859 actions=goto_table:1
 cookie=0x0, duration=6.758s, table=0, n_packets=0, n_bytes=0, priority=1,tcp,in_port=ovsfake0 actions=push_vlan:0x8100,set_field:4098->vlan_vid,goto_table:2
 cookie=0x0, duration=6.758s, table=0, n_packets=0, n_bytes=0, priority=1,udp,in_port=ovsfake0 actions=push_vlan:0x8100,set_field:4098->vlan_vid,goto_table:2
 cookie=0x0, duration=6.757s, table=0, n_packets=0, n_bytes=0, priority=1,arp,in_port=ovsfake0,dl_src=0e:00:00:00:00:66 actions=goto_table:2
 cookie=0x0, duration=6.759s, table=0, n_packets=6, n_bytes=621, priority=0 actions=drop
 cookie=0x0, duration=6.758s, table=1, n_packets=0, n_bytes=0, priority=1,tcp actions=move:NXM_OF_IP_SRC[]->NXM_NX_REG0[],load:0xa0a->NXM_NX_REG0[16..31],move:NXM_NX_REG0[0..7]->NXM_NX_REG0[8..15],move:NXM_OF_IP_DST[0..7]->NXM_NX_REG0[0..7],load:0x2->NXM_NX_REG1[0..2],load:0x1->NXM_NX_REG2[0..2],learn(table=1,hard_timeout=300,priority=2,eth_type=0x800,NXM_OF_IP_SRC[],NXM_OF_IP_DST[],load:NXM_NX_REG0[]->NXM_OF_IP_SRC[],load:0xa0a0001->NXM_OF_IP_DST[],load:0xe0000000067->NXM_OF_ETH_SRC[],load:0xe0000000066->NXM_OF_ETH_DST[],output:NXM_NX_REG1[0..1]),learn(table=2,idle_timeout=300,priority=2,eth_type=0x800,ip_src=10.10.0.1,NXM_OF_IP_DST[]=NXM_NX_REG0[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[],load:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:NXM_OF_IP_DST[]->NXM_OF_IP_SRC[],output:NXM_NX_REG2[0..1]),set_field:0e:00:00:00:00:67->eth_src,set_field:0e:00:00:00:00:66->eth_dst,move:NXM_NX_REG0[]->NXM_OF_IP_SRC[],set_field:10.10.0.1->ip_dst,output:ovsfake0
 cookie=0x0, duration=6.758s, table=1, n_packets=0, n_bytes=0, priority=1,udp actions=move:NXM_OF_IP_SRC[]->NXM_NX_REG0[],load:0xa0a->NXM_NX_REG0[16..31],move:NXM_NX_REG0[0..7]->NXM_NX_REG0[8..15],move:NXM_OF_IP_DST[0..7]->NXM_NX_REG0[0..7],load:0x2->NXM_NX_REG1[0..2],load:0x1->NXM_NX_REG2[0..2],learn(table=1,hard_timeout=300,priority=2,eth_type=0x800,NXM_OF_IP_SRC[],NXM_OF_IP_DST[],load:NXM_NX_REG0[]->NXM_OF_IP_SRC[],load:0xa0a0001->NXM_OF_IP_DST[],load:0xe0000000067->NXM_OF_ETH_SRC[],load:0xe0000000066->NXM_OF_ETH_DST[],output:NXM_NX_REG1[0..1]),learn(table=2,idle_timeout=300,priority=2,eth_type=0x800,ip_src=10.10.0.1,NXM_OF_IP_DST[]=NXM_NX_REG0[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[],load:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:NXM_OF_IP_DST[]->NXM_OF_IP_SRC[],output:NXM_NX_REG2[0..1]),set_field:0e:00:00:00:00:67->eth_src,set_field:0e:00:00:00:00:66->eth_dst,move:NXM_NX_REG0[]->NXM_OF_IP_SRC[],set_field:10.10.0.1->ip_dst,output:ovsfake0
 cookie=0x0, duration=6.759s, table=1, n_packets=0, n_bytes=0, priority=0 actions=drop
 cookie=0x0, duration=6.758s, table=2, n_packets=0, n_bytes=0, priority=1,arp,arp_op=1 actions=move:NXM_OF_ARP_TPA[]->NXM_NX_REG0[],load:0x2->NXM_OF_ARP_OP[0..2],move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],move:NXM_NX_REG0[]->NXM_OF_ARP_SPA[],set_field:0e:00:00:00:00:67->eth_src,set_field:0e:00:00:00:00:67->arp_sha,IN_PORT
 cookie=0x0, duration=6.758s, table=2, n_packets=0, n_bytes=0, priority=0 actions=drop

While scary looking, the flows are mostly just rearranging fields. Let’s look at the flow that responds to ARP:

 cookie=0x0, duration=6.758s, table=2, n_packets=0, n_bytes=0, priority=1,arp,arp_op=1 actions=move:NXM_OF_ARP_TPA[]->NXM_NX_REG0[],load:0x2->NXM_OF_ARP_OP[0..2],move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],move:NXM_NX_REG0[]->NXM_OF_ARP_SPA[],set_field:0e:00:00:00:00:67->eth_src,set_field:0e:00:00:00:00:67->arp_sha,IN_PORT

This flow matches ARP requests. Then, it turns the ARP packet into an ARP reply, rewrites source hardware addresses, and rewrites IP addresses (we temporarily store the ARP target address from the request in OVS’ software register reg0, so it doesn’t get overwritten and so we can use it at the end of the rewriting process). Finally, we say to output the packet on the same port it came in on.

Now let’s look at the really scary flow that implements learning:

cookie=0x0, duration=6.758s, table=1, n_packets=0, n_bytes=0, priority=1,tcp actions=move:NXM_OF_IP_SRC[]->NXM_NX_REG0[],load:0xa0a->NXM_NX_REG0[16..31],move:NXM_NX_REG0[0..7]->NXM_NX_REG0[8..15],move:NXM_OF_IP_DST[0..7]->NXM_NX_REG0[0..7],load:0x2->NXM_NX_REG1[0..2],load:0x1->NXM_NX_REG2[0..2],learn(table=1,hard_timeout=300,priority=2,eth_type=0x800,NXM_OF_IP_SRC[],NXM_OF_IP_DST[],load:NXM_NX_REG0[]->NXM_OF_IP_SRC[],load:0xa0a0001->NXM_OF_IP_DST[],load:0xe0000000067->NXM_OF_ETH_SRC[],load:0xe0000000066->NXM_OF_ETH_DST[],output:NXM_NX_REG1[0..1]),learn(table=2,idle_timeout=300,priority=2,eth_type=0x800,ip_src=10.10.0.1,NXM_OF_IP_DST[]=NXM_NX_REG0[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:NXM_OF_ETH_DST[]->NXM_OF_ETH_SRC[],load:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:NXM_OF_IP_DST[]->NXM_OF_IP_SRC[],output:NXM_NX_REG2[0..1]),set_field:0e:00:00:00:00:67->eth_src,set_field:0e:00:00:00:00:66->eth_dst,move:NXM_NX_REG0[]->NXM_OF_IP_SRC[],set_field:10.10.0.1->ip_dst,output:ovsfake0

First, we calculate the source NAT address and put it in reg0. Then we have two learn() sections – one adds a flow to table 1 to implement inbound translation, and the other adds a flow to table 2 for outbound. Finally, we have to do NAT translation on the packet we just got and send it.