Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pipelined] Add conntrack pipelined controller #2191

Merged
merged 4 commits into from Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
231 changes: 231 additions & 0 deletions lte/gateway/python/magma/pipelined/app/conntrack.py
@@ -0,0 +1,231 @@
"""
Copyright (c) 2016-present, Facebook, Inc.
All rights reserved.

This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
"""

from ryu.ofproto.nicira_ext import ofs_nbits
from ryu.lib.packet import ether_types
from ryu.ofproto.inet import IPPROTO_TCP

from .base import MagmaController, ControllerType
from magma.pipelined.openflow import flows
from magma.pipelined.openflow.magma_match import MagmaMatch
from magma.pipelined.openflow.registers import Direction


class ConntrackController(MagmaController):
"""
A controller that sets up tunnel/ue learn flows based on uplink UE traffic
to properly route downlink packets back to the UE (through the correct GRE
flow tunnel).

This is an optional controller and will only be used for setups with flow
based GRE tunnels.

conntrack flags 0 is nothing, 1 is commit

CT state reference tuple (x,y):
x:
0: -
1: +

y:
0x01: new
0x02: est
0x04: rel
0x08: rpl
0x10: inv
0x20: trk
"""

APP_NAME = "conntrack"
APP_TYPE = ControllerType.LOGICAL
CT_NEW = 0x01
CT_EST = 0x02
CT_REL = 0x04
CT_RPL = 0x08
CT_INV = 0x10
CT_TRK = 0x20

def __init__(self, *args, **kwargs):
super(ConntrackController, self).__init__(*args, **kwargs)
self.tbl_num = self._service_manager.get_table_num(self.APP_NAME)
self.next_table = \
self._service_manager.get_next_table_num(self.APP_NAME)
self.conntrack_scratch = \
self._service_manager.allocate_scratch_tables(self.APP_NAME, 1)[0]
self.connection_event_table = \
self._service_manager.INTERNAL_IPFIX_SAMPLE_TABLE_NUM
self._datapath = None

def initialize_on_connect(self, datapath):
self._datapath = datapath
self.delete_all_flows(datapath)
self._install_default_flows(self._datapath)

def cleanup_on_disconnect(self, datapath):
"""
Cleanup flows on datapath disconnect event.

Args:
datapath: ryu datapath struct
"""
self.delete_all_flows(datapath)

def delete_all_flows(self, datapath):
flows.delete_all_flows_from_table(datapath, self.tbl_num)
flows.delete_all_flows_from_table(datapath, self.conntrack_scratch)

def _install_default_flows(self, datapath):
parser = datapath.ofproto_parser

match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ct_state=(0x0, self.CT_TRK))
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.conntrack_scratch,
alg=0,
actions=[]
)]
flows.add_resubmit_next_service_flow(datapath, self.tbl_num,
match, actions,
priority=flows.DEFAULT_PRIORITY,
resubmit_table=self.next_table)

# Match all new connections
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ct_state=(self.CT_NEW | self.CT_TRK,
self.CT_NEW | self.CT_TRK))
actions = [parser.NXActionCT(
flags=0x1,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY)

# Match tcp terminations (fin)
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=IPPROTO_TCP,
tcp_flags=(0x1,0x1),
ct_state=(self.CT_EST | self.CT_TRK,
self.CT_EST | self.CT_TRK))
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY)
# match tcp fin
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=IPPROTO_TCP,
tcp_flags=(0x1, 0x1),
ct_state=(self.CT_TRK | self.CT_INV, self.CT_TRK | self.CT_INV))
# flags 0 is nothing, 1 is commit
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY)

# match tcp rst
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=IPPROTO_TCP,
tcp_flags=(0x4, 0x4),
ct_state=(self.CT_EST | self.CT_TRK, self.CT_EST | self.CT_TRK))
# flags 0 is nothing, 1 is commit
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY)

inbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
direction=Direction.IN)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you squash multiple patches in single commit, its tricky to review patch when first patch adds code and second removes it, let me know if there is way to look at final code on github.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure If i follow, when using github UI you can see the final code. https://github.com/magma/magma/pull/2191/files

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I was looking at commits. I generally pull files on my laptop.

outbound_match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
direction=Direction.OUT)
flows.add_resubmit_next_service_flow(
datapath, self.tbl_num, inbound_match, [],
priority=flows.MINIMUM_PRIORITY,
resubmit_table=self.next_table)
flows.add_resubmit_next_service_flow(
datapath, self.tbl_num, outbound_match, [],
priority=flows.MINIMUM_PRIORITY,
resubmit_table=self.next_table)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont see need to match on direction, given that action for both flows is same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah for some reason these are our default flows for a lot of controllers. I'm actually not sure why it was done this way initially. I've always been to lazy but I think I might finally refactor all controllers to use 1 default rule instead of 2.


# TODO Currently for testing, will nuke later
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ct_state=(self.CT_EST | self.CT_TRK, self.CT_EST | self.CT_TRK))
# flags 0 is nothing, 1 is commit
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it is work setting zone to avoid polluting root conntract table?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didn't think it mattered but I can add that

recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY-5)
# match tcp fin
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=IPPROTO_TCP,
tcp_flags=(0x1, 0x1),
ct_state=(self.CT_TRK, self.CT_TRK))
# flags 0 is nothing, 1 is commit
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY-1)
# match tcp fin
match = MagmaMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=IPPROTO_TCP,
tcp_flags=(0x1,0x1))
# flags 0 is nothing, 1 is commit
actions = [parser.NXActionCT(
flags=0x0,
zone_src=None,
zone_ofs_nbits=0,
recirc_table=self.connection_event_table,
alg=0,
actions=[]
)]
flows.add_drop_flow(datapath, self.conntrack_scratch,
match, actions,
priority=flows.DEFAULT_PRIORITY-1)
7 changes: 7 additions & 0 deletions lte/gateway/python/magma/pipelined/bridge_util.py
Expand Up @@ -101,6 +101,13 @@ def create_bridge(bridge_name, iface_name):
"tcp:127.0.0.1:6633", "tcp:127.0.0.1:6654"]).wait()
subprocess.Popen(["ifconfig", iface_name, "192.168.1.1/24"]).wait()

@staticmethod
def flush_conntrack():
"""
Cleanup the conntrack state
"""
subprocess.Popen(["ovs-dpctl", "flush-conntrack"]).wait()

@staticmethod
def destroy_bridge(bridge_name):
"""
Expand Down
8 changes: 8 additions & 0 deletions lte/gateway/python/magma/pipelined/service_manager.py
Expand Up @@ -46,6 +46,7 @@
from magma.pipelined.app.base import ControllerType
from magma.pipelined.app import of_rest_server
from magma.pipelined.app.access_control import AccessControlController
from magma.pipelined.app.conntrack import ConntrackController
from magma.pipelined.app.tunnel_learn import TunnelLearnController
from magma.pipelined.app.vlan_learn import VlanLearnController
from magma.pipelined.app.arp import ArpController
Expand Down Expand Up @@ -256,6 +257,7 @@ class ServiceManager:
TUNNEL_LEARN_SERVICE_NAME = 'tunnel_learn'
VLAN_LEARN_SERVICE_NAME = 'vlan_learn'
IPFIX_SERVICE_NAME = 'ipfix'
CONNTRACK_SERVICE_NAME = 'conntrack'
RYU_REST_SERVICE_NAME = 'ryu_rest_service'
RYU_REST_APP_NAME = 'ryu_rest_app'
STARTUP_FLOWS_RECIEVER_CONTROLLER = 'startup_flows'
Expand Down Expand Up @@ -345,6 +347,12 @@ class ServiceManager:
type=CheckQuotaController.APP_TYPE,
order_priority=300),
],
CONNTRACK_SERVICE_NAME: [
App(name=ConntrackController.APP_NAME,
module=ConntrackController.__module__,
type=ConntrackController.APP_TYPE,
order_priority=700),
],
IPFIX_SERVICE_NAME: [
App(name=IPFIXController.APP_NAME,
module=IPFIXController.__module__,
Expand Down
Expand Up @@ -14,7 +14,7 @@
import abc
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import sendp, srp, wrpcap
from scapy.all import sendp, srp, wrpcap, rdpcap


class PacketInjector(metaclass=abc.ABCMeta):
Expand Down Expand Up @@ -56,6 +56,12 @@ def send(self, pkt, count=1):
wrpcap(self._pcap_filename, pkt, append=True)
sendp(pkt, iface=self._iface, count=count, verbose=False)

def send_pcap(self, name):
pkts = rdpcap(name)
if self._pcap_filename:
wrpcap(self._pcap_filename, pkts, append=True)
sendp(pkts, iface=self._iface, verbose=False)

def get_response(self, pkt, timeout=1.5):
packets = srp(pkt, iface=self._iface, timeout=timeout, verbose=False)
if self._pcap_filename:
Expand Down
Expand Up @@ -109,6 +109,9 @@ class PipelinedController(Enum):
UplinkBridge = Controller(
'magma.pipelined.app.uplink_bridge', 'uplink_bridge'
)
Conntrack = Controller(
'magma.pipelined.app.conntrack', 'conntrack'
)


def assert_pipelined_not_running():
Expand Down
Binary file not shown.
@@ -0,0 +1,12 @@
cookie=0x0, table=ue_mac(main_table), n_packets=20, n_bytes=2323, priority=65535,ip,nw_src=145.254.160.237 actions=load:0x7594587a06d->OXM_OF_METADATA[],load:0x1->NXM_NX_REG1[],resubmit(,conntrack(main_table))
cookie=0x0, table=ue_mac(main_table), n_packets=23, n_bytes=22768, priority=65535,ip,nw_dst=145.254.160.237 actions=load:0x7594587a06d->OXM_OF_METADATA[],load:0x10->NXM_NX_REG1[],resubmit(,conntrack(main_table))
cookie=0x0, table=conntrack(main_table), n_packets=43, n_bytes=25091, priority=10,ct_state=-trk,ip actions=ct(table=conntrack(scratch_table_0)),resubmit(,egress(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=conntrack(main_table), n_packets=0, n_bytes=0, priority=0,ip,reg1=0x10 actions=resubmit(,egress(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=conntrack(main_table), n_packets=0, n_bytes=0, priority=0,ip,reg1=0x1 actions=resubmit(,egress(main_table)),set_field:0->reg0,set_field:0->reg3
cookie=0x0, table=conntrack(scratch_table_0), n_packets=3, n_bytes=926, priority=10,ct_state=+new+trk,ip actions=ct(commit,table=203)
cookie=0x0, table=conntrack(scratch_table_0), n_packets=2, n_bytes=108, priority=10,ct_state=+est+trk,tcp,tcp_flags=+fin actions=ct(table=203)
cookie=0x0, table=conntrack(scratch_table_0), n_packets=0, n_bytes=0, priority=10,ct_state=+inv+trk,tcp,tcp_flags=+fin actions=ct(table=203)
cookie=0x0, table=conntrack(scratch_table_0), n_packets=0, n_bytes=0, priority=10,ct_state=+est+trk,tcp,tcp_flags=+rst actions=ct(table=203)
cookie=0x0, table=conntrack(scratch_table_0), n_packets=0, n_bytes=0, priority=9,ct_state=+trk,tcp,tcp_flags=+fin actions=ct(table=203)
cookie=0x0, table=conntrack(scratch_table_0), n_packets=0, n_bytes=0, priority=9,tcp,tcp_flags=+fin actions=ct(table=203)
cookie=0x0, table=conntrack(scratchreetufchdkfblrkvuitknirkrcitjhij_table_0), n_packets=38, n_bytes=24057, priority=5,ct_state=+est+trk,ip actions=ct(table=203)