示例#1
0
    def __init__(self):
        self.controller = SimpleSwitchAPI(_THRIFT_PORT)
        self.controller.reset_state()
        self.seq_dict = {}
        self.slice_rules = {}
        self.last_free_counter = 0
        self.allocated_counters = []
        self.running = True

        # Remove the CPU port from multicast
        ports = [
            port for port in self.controller.client.bm_dev_mgr_show_ports()
            if port.port_num != 255
        ]
        mc_grp_id = 1
        rid = 0
        for port in ports:
            other_ports = ports[:]  # clone the port
            del (other_ports[other_ports.index(port)])
            self.controller.mc_mgrp_create(mc_grp_id)
            handle = self.controller.mc_node_create(
                rid, [p.port_num for p in other_ports])
            self.controller.mc_node_associate(mc_grp_id, handle)
            self.controller.table_add("multicast", "set_mcast_grp",
                                      [str(port.port_num)], [str(mc_grp_id)])
            rid += 1
            mc_grp_id += 1
示例#2
0
 def __init__(self):
     self.controller = SimpleSwitchAPI(_THRIFT_PORT)
     self.controller.reset_state()
     self.running = True
     ports = self.controller.client.bm_dev_mgr_show_ports()
     mc_grp_id = 1
     rid = 0
     for port in ports:
         other_ports = ports[:]  # clone the port
         del (other_ports[other_ports.index(port)])
         self.controller.mc_mgrp_create(mc_grp_id)
         handle = self.controller.mc_node_create(
             rid, [p.port_num for p in other_ports])
         self.controller.mc_node_associate(mc_grp_id, handle)
         self.controller.table_add("multicast", "set_mcast_grp",
                                   [str(port.port_num)], [str(mc_grp_id)])
         rid += 1
         mc_grp_id += 1
class Controller(object):
    def __init__(self):
        self.controller = SimpleSwitchAPI(_THRIFT_PORT)
        self.controller.reset_state()
        self.seq_dict = {}
        self.slice_rules = {}
        ports = self.controller.client.bm_dev_mgr_show_ports()
        mc_grp_id = 1
        rid = 0
        for port in ports:
            other_ports = ports[:]  # clone the port
            del (other_ports[other_ports.index(port)])
            self.controller.mc_mgrp_create(mc_grp_id)
            handle = self.controller.mc_node_create(
                rid, [p.port_num for p in other_ports])
            self.controller.mc_node_associate(mc_grp_id, handle)
            self.controller.table_add("multicast", "set_mcast_grp",
                                      [str(port.port_num)], [str(mc_grp_id)])
            rid += 1
            mc_grp_id += 1

    def on_notify(self, msg):
        topic, device_id, ctx_id, list_id, buffer_id, num = struct.unpack(
            "<iQiiQi", msg[:32])
        self.process_digest(msg, num)
        # Acknowledge
        self.controller.client.bm_learning_ack_buffer(ctx_id, list_id,
                                                      buffer_id)

    def process_digest(self, msg, num_samples):
        log("digest incoming!")
        digest = []
        offset = 32
        for _ in range(num_samples):
            if msg[offset] == 0:
                mac0, mac1, ingress_port = struct.unpack(
                    ">LHH", msg[offset + 1:offset + 9])
                mac_addr = hex((mac0 << 16) + mac1)
                log("learn {} on {}".format(mac_addr, ingress_port))
                self.controller.table_add("source", "NoAction",
                                          [str(mac_addr)], [])
                self.controller.table_add("dest", "forward", [str(mac_addr)],
                                          [str(ingress_port)])
                offset += 9
            else:
                # last bucket, N, mean, stdev
                last, n, mean, stdev = struct.unpack(
                    ">HHLL", msg[offset + 1:offset + 13])
                log("traffic volume anomaly detected! last bucket = {}, n = {}, mean = {}, stdev = {}"
                    .format(last, n, mean, stdev))
                offset += 13
        return digest

    def await_notifications(self):
        sub = nnpy.Socket(nnpy.AF_SP, nnpy.SUB)
        sock = self.controller.client.bm_mgmt_get_info().notifications_socket
        log("socket = {}".format(sock))
        sub.connect(sock)
        sub.setsockopt(nnpy.SUB, nnpy.SUB_SUBSCRIBE, '')
        log("connected to socket.")

        self.controller.table_add("dest_track", "track", ["10.0.0.1/32"],
                                  ["0"])

        while True:
            self.on_notify(sub.recv())
示例#4
0
class Controller(object):
    def __init__(self):
        self.controller = SimpleSwitchAPI(_THRIFT_PORT)
        self.controller.reset_state()
        self.seq_dict = {}
        self.slice_rules = {}
        self.last_free_counter = 0
        self.allocated_counters = []
        self.running = True
        ports = self.controller.client.bm_dev_mgr_show_ports()
        mc_grp_id = 1
        rid = 0
        for port in ports:
            other_ports = ports[:]  # clone the port
            del (other_ports[other_ports.index(port)])
            self.controller.mc_mgrp_create(mc_grp_id)
            handle = self.controller.mc_node_create(
                rid, [p.port_num for p in other_ports])
            self.controller.mc_node_associate(mc_grp_id, handle)
            self.controller.table_add("multicast", "set_mcast_grp",
                                      [str(port.port_num)], [str(mc_grp_id)])
            rid += 1
            mc_grp_id += 1

    def on_notify(self, msg):
        topic, device_id, ctx_id, list_id, buffer_id, num = struct.unpack(
            "<iQiiQi", msg[:32])
        self.process_digest(msg, num)
        # Acknowledge
        self.controller.client.bm_learning_ack_buffer(ctx_id, list_id,
                                                      buffer_id)

    def add_slice(self, prefix, n):
        prefix_int = int(prefix[0])
        prefix_str = "{}.0.0.0/8".format(prefix_int)
        log("sending slice for LPM {}...".format(prefix_str), "red")
        rule_handle = self.controller.table_add("respondBgp", "slice",
                                                [prefix_str],
                                                [str(n)])  # slice in bits
        self.slice_rules[prefix_int] = {
            'handle': rule_handle,
            'expiration': int(time.time()) + _RULE_EXPIRATION_SEC
        }

    def process_digest(self, msg, num_samples):
        log("digest incoming!")
        digest = []
        offset = 32
        for _ in range(num_samples):
            if msg[offset] == 0:
                mac0, mac1, ingress_port = struct.unpack(
                    ">LHH", msg[offset + 1:offset + 9])
                mac_addr = hex((mac0 << 16) + mac1)
                log("learn {} on {}".format(mac_addr, ingress_port))
                self.controller.table_add("source", "NoAction",
                                          [str(mac_addr)], [])
                self.controller.table_add("dest", "forward", [str(mac_addr)],
                                          [str(ingress_port)])
                offset += 9
            elif msg[offset] == 1:
                expected, got, prefix = struct.unpack(
                    ">LLc", msg[offset + 1:offset + 10])
                log("received seq update!")
                gap = got - expected
                log("gap = {}, prefix = {}".format(gap, int(prefix[0])))
                if not int(prefix[0]) in self.slice_rules:
                    self.add_slice(prefix, gap)
                else:
                    log("slice rule for this prefix already exists.")
                offset += 10
            elif msg[offset] == 2:
                prefix = msg[offset + 1]
                if not prefix in self.allocated_counters:
                    log('allocating prefix {} to counter {}'.format(
                        prefix, self.last_free_counter))
                    pfx_str = "{}.0.0.0/8".format(prefix)
                    self.controller.table_add("seqprefix", "set_counter_idx",
                                              [pfx_str],
                                              [str(self.last_free_counter)])
                    self.last_free_counter += 1
                    self.allocated_counters.append(prefix)
                offset += 5
            else:
                log('unknown digest type {}'.format(msg[offset]))
        return digest

    # cleans up expired rules every 10 seconds and doesn't ever return.
    def do_expired_rule_cleanup(self):
        while self.running:
            expired_rules = [
                rule for rule in self.slice_rules.keys()
                if self.slice_rules[rule]['expiration'] < int(time.time())
            ]
            for r in expired_rules:
                log("deleting expired rule for prefix={}.0.0.0/8".format(r))
                self.controller.table_delete("respondBgp",
                                             self.slice_rules[r]['handle'])
                del self.slice_rules[r]
            time.sleep(10)

    def await_notifications(self):
        sub = nnpy.Socket(nnpy.AF_SP, nnpy.SUB)
        sock = self.controller.client.bm_mgmt_get_info().notifications_socket
        log("socket = {}".format(sock))
        sub.connect(sock)
        sub.setsockopt(nnpy.SUB, nnpy.SUB_SUBSCRIBE, '')
        log("connected to socket.")

        while True:
            self.on_notify(sub.recv())
示例#5
0
class Controller(object):
    def __init__(self):
        self.controller = SimpleSwitchAPI(_THRIFT_PORT)
        self.controller.reset_state()
        self.seq_dict = {}
        self.slice_rules = {}
        self.last_free_counter = 0
        self.allocated_counters = []
        self.running = True

        # Remove the CPU port from multicast
        ports = [
            port for port in self.controller.client.bm_dev_mgr_show_ports()
            if port.port_num != 255
        ]
        mc_grp_id = 1
        rid = 0
        for port in ports:
            other_ports = ports[:]  # clone the port
            del (other_ports[other_ports.index(port)])
            self.controller.mc_mgrp_create(mc_grp_id)
            handle = self.controller.mc_node_create(
                rid, [p.port_num for p in other_ports])
            self.controller.mc_node_associate(mc_grp_id, handle)
            self.controller.table_add("multicast", "set_mcast_grp",
                                      [str(port.port_num)], [str(mc_grp_id)])
            rid += 1
            mc_grp_id += 1

    def on_notify(self, msg):
        topic, device_id, ctx_id, list_id, buffer_id, num = struct.unpack(
            "<iQiiQi", msg[:32])
        self.process_digest(msg, num)
        # Acknowledge
        self.controller.client.bm_learning_ack_buffer(ctx_id, list_id,
                                                      buffer_id)

    def add_slice(self, prefix, n):
        prefix_str = "{}.0.0.0/8".format(prefix)
        log("sending slice for LPM {}...".format(prefix_str), "red")
        rule_handle = self.controller.table_add("respondBgp", "slice",
                                                [prefix_str],
                                                [str(n)])  # slice in bits
        self.slice_rules[int(prefix)] = {
            'handle': rule_handle,
            'expiration': int(time.time()) + _RULE_EXPIRATION_SEC
        }

    def process_digest(self, msg, num_samples):
        log("digest incoming!")
        digest = []
        offset = 32
        for _ in range(num_samples):
            if msg[offset] == 0:
                mac0, mac1, ingress_port = struct.unpack(
                    ">LHH", msg[offset + 1:offset + 9])
                mac_addr = hex((mac0 << 16) + mac1)
                log("learn {} on {}".format(mac_addr, ingress_port))
                self.controller.table_add("source", "NoAction",
                                          [str(mac_addr)], [])
                self.controller.table_add("dest", "forward", [str(mac_addr)],
                                          [str(ingress_port)])
                offset += 9
            elif msg[offset] == 1:
                expected, got, prefix = struct.unpack(
                    ">LLc", msg[offset + 1:offset + 10])
                log("received seq update!")
                gap = got - expected
                log("gap = {}, prefix = {}".format(gap, int(prefix[0])))
                # Check for existing quarantined packets with this gap value.
                r = int(prefix[0])
                if (gap, str(r)) in self.quarantined:
                    log(
                        "Received response for prefix {}, blocking AS {}.".
                        format(r, gap), "red")
                    del self.quarantined[(gap, str(r))]
                    # Delete slice rule.
                    self.controller.table_delete("respondBgp",
                                                 self.slice_rules[r]['handle'])
                    del self.slice_rules[r]
                else:
                    log(
                        "Could not find any quarantined for AS {} (prefix {}.0.0.0/8)."
                        .format(gap, r), "yellow")

                offset += 10
            elif msg[offset] == 2:
                prefix = msg[offset + 1]
                if not prefix in self.allocated_counters:
                    log('allocating prefix {} to counter {}'.format(
                        prefix, self.last_free_counter))
                    pfx_str = "{}.0.0.0/8".format(prefix)
                    self.controller.table_add("seqprefix", "set_counter_idx",
                                              [pfx_str],
                                              [str(self.last_free_counter)])
                    self.last_free_counter += 1
                    self.allocated_counters.append(prefix)
                offset += 5
            else:
                log('unknown digest type {}'.format(msg[offset]))
        return digest

    # cleans up expired rules every 10 seconds and doesn't ever return.
    def do_expired_rule_cleanup(self):
        while self.running:
            expired_rules = [
                rule for rule in self.slice_rules.keys()
                if self.slice_rules[rule]['expiration'] < int(time.time())
            ]
            for r in expired_rules:
                log("deleting expired rule for prefix={}.0.0.0/8".format(r))
                self.controller.table_delete("respondBgp",
                                             self.slice_rules[r]['handle'])
                del self.slice_rules[r]

                qkey = [(q, k) for (q, k) in self.quarantined.keys() if k == r]
                if len(qkey) == 0:
                    log(
                        "couldn't find a quarantine key matching this prefix...",
                        "yellow")
                    continue
                self.allowed.append(qkey[0])
            time.sleep(10)

    def await_notifications(self):
        sub = nnpy.Socket(nnpy.AF_SP, nnpy.SUB)
        sock = self.controller.client.bm_mgmt_get_info().notifications_socket
        log("socket = {}".format(sock))
        sub.connect(sock)
        sub.setsockopt(nnpy.SUB, nnpy.SUB_SUBSCRIBE, '')
        log("connected to socket.")

        while True:
            self.on_notify(sub.recv())

    def cpu_port_handler(self, packet, psock):
        eth = Ether(packet)
        if not self.running:
            raise Exception('stop')

        if not eth.haslayer(bgp.BGPUpdate):
            # Release the packet.
            psock.send(packet)
            return

        #log("Get BGP update packet: {}".format(eth.summary()), "yellow")

        update = eth[bgp.BGPUpdate]

        # Flag to determine whether this packet should be stopped.
        quarantine = False
        drop = False
        quarantine_key = None

        while True:
            # Check if this path_attr ends at a different AS compared to
            # known for this prefix, and if so, quarantine this BGP update packet.

            p = [
                path_attr for path_attr in update.path_attr
                if path_attr.type_code == AS_PATH
            ]
            if len(p) > 0:
                s = [
                    path.segment_value for path in p[0].attribute.segments
                    if path.segment_type == AS_SEQUENCE
                ]
                if len(s) > 0:
                    # Get the destination AS in each sequence. Assert that they are all the same.
                    dest = [seq[-1] for seq in s]
                    assert (all(d == dest[0] for d in dest))
                    if dest[0] in self.blocked_as:
                        log(
                            "Dropping packet announcing blocked AS {}.".format(
                                dest[0]), "red")
                        drop = True
                        break
                    if dest[0] != 0:
                        for dest_prefix in [
                                nlri.prefix for nlri in update.nlri
                        ]:
                            #print("Announcement for prefix {}:".format(dest_prefix))
                            #print(s)
                            if dest_prefix.split('.')[0] == 9:
                                continue  # TODO: this skips the internal network. Should be fixed with CIDR below.
                            if (dest[0], dest_prefix.split('.')[0]
                                ) in self.quarantined:
                                drop = True  # Ignore this packet that's advertising a quarantined route.
                            elif dest_prefix in self.routing_table and self.routing_table[
                                    dest_prefix] != dest[0]:
                                log(
                                    "Delay AS {} announcing prefix {}.".format(
                                        dest[0], dest_prefix), "red")

                                # Ideally, we should look through all of the updates and notify all the
                                # ASes being targeted in a single update. For now though, we only notify
                                # the first, and similarly unblock the AS when this is done.

                                # Hardcode the prefix to /8. This needs modification on the P4 side to support
                                # proper CIDR.
                                quarantine_key = (dest[0],
                                                  dest_prefix.split('.')[0])
                                if not quarantine_key in self.allowed:
                                    quarantine = True
                                break
                            else:
                                log("Route {}: {}".format(dest_prefix, s[0]))
                                self.routing_table[dest_prefix] = dest[0]

            if not update.haslayer(
                    bgp.BGPHeader) or not update[bgp.BGPHeader].haslayer(
                        bgp.BGPUpdate):
                break
            else:
                # next update
                update = update[bgp.BGPHeader][bgp.BGPUpdate]

        if drop:
            pass  # blackhole
        elif not quarantine:
            psock.send(packet)
        elif not quarantine_key in self.quarantined:
            self.quarantined[quarantine_key] = int(
                time.time()) + _QUARANTINE_EXPIRATION_SEC
            (gap, prefix) = quarantine_key
            self.add_slice(prefix, gap)

    def cpu_port_sniffer(self):
        self.routing_table = {}  # prefix -> AS
        self.quarantined = {}  # (AS, prefix) -> quarantine expire time
        self.blocked_as = []
        self.allowed = []  # (AS, prefix)
        psock = psocket.SocketHndl(timeout=999999, iface_name=_CPU_PORT_VETH)
        while self.running:
            for raw in psock:
                self.cpu_port_handler(raw, psock)