| """ |
| Mock backend for testing purposes |
| """ |
| |
| import logging |
| import os |
| import sys |
| from threading import Thread |
| |
| from hexdump import hexdump |
| from scapy.all import Ether, IP, UDP, Dot1Q, sendp, sniff |
| |
| # VERY IMPORTANT: |
| # Without the below hack, scapy will not properly receive VLAN |
| # header (see http://stackoverflow.com/questions/18994242/why-isnt-scapy-capturing-vlan-tag-information). |
| # |
| from scapy.all import conf, ETH_P_ALL |
| import pcap |
| conf.use_pcap = True |
| import scapy.arch.pcapdnet |
| assert conf.L2listen.__name__=='L2pcapListenSocket' |
| |
| sys.path.insert(1, os.path.join(sys.path[0], '..')) |
| |
| import loxi.of13 as ofp |
| from ofagent.utils import mac_str_to_tuple |
| |
| |
| class MockBackend(object): |
| |
| mfr_desc = "Ciena Corp." |
| hw_desc = "mock" |
| |
| def __init__(self, store, in_out_iface=None, in_out_stag=None): |
| self.store = store |
| self.add_some_ports() |
| self.in_out_iface = in_out_iface |
| self.in_out_stag = in_out_stag |
| self.agent = None |
| self.in_out_receiver = None |
| |
| def set_agent(self, agent): |
| self.agent = agent |
| if self.in_out_iface is not None: |
| self.in_out_receiver = InOutReceiver(self.in_out_iface, agent, self.in_out_stag) |
| self.in_out_receiver.start() |
| |
| def stop(self): |
| if self.in_out_receiver is not None: |
| self.in_out_receiver.stop() |
| |
| def get_serial_num(self): |
| return "DFG-4567-RTYU-789" |
| |
| def get_dp_desc(self): |
| return "mock device" |
| |
| def add_some_ports(self): |
| cap = ofp.OFPPF_1GB_FD | ofp.OFPPF_FIBER |
| for pno, mac, nam, cur, adv, sup, spe in ( |
| ( 1, '00:00:00:00:00:01', 'onu1', cap, cap, cap, ofp.OFPPF_1GB_FD), |
| ( 2, '00:00:00:00:00:02', 'onu2', cap, cap, cap, ofp.OFPPF_1GB_FD), |
| (129, '00:00:00:00:00:81', 'olt', cap, cap, cap, ofp.OFPPF_1GB_FD) |
| ): |
| port = ofp.common.port_desc(pno, mac_str_to_tuple(mac), nam, |
| curr=cur, advertised=adv, supported=sup, |
| curr_speed=spe, max_speed=spe) |
| self.store.port_add(port) |
| |
| def packet_out(self, in_port, out_port, data): |
| in_port = "CONTROLLER" if in_port == ofp.OFPP_CONTROLLER else in_port |
| print "PACKET OUT (%s => %s): " % (in_port, out_port) |
| hexdump(data) |
| |
| if self.in_out_iface is not None: |
| |
| try: |
| # disect the packet |
| pkt = Ether(data) |
| |
| # remove payload from Ether frame |
| payload = pkt.payload |
| payload_type = pkt.type |
| pkt.remove_payload() |
| |
| # insert Dot1Q shim with vlan_id = out_port |
| |
| if self.in_out_stag is None: |
| ## WARNING -- This was changed from 0x88a8 to 0x8100 when |
| ## testing with the Intel XL710 quad 10GE boards. The |
| ## XL710 does not support the TPID for the STAG. |
| ## |
| ## Long term, it should be changed back to 0x88a8! |
| ## |
| pkt.type = 0x8100 |
| new_pkt = pkt / Dot1Q(vlan=out_port, type=payload_type) / payload |
| |
| else: |
| pkt.type = 0x8100 |
| new_pkt = ( |
| pkt / |
| Dot1Q(vlan=self.in_out_stag, type=0x8100) / |
| Dot1Q(vlan=out_port, type=payload_type) / |
| payload) |
| |
| # send out the packet |
| sendp(new_pkt, iface=self.in_out_iface) |
| |
| except Exception, e: |
| logging.exception("Could not parse packet-out data as scapy.Ether:\n") |
| logging.error(hexdump(data, 'return')) |
| |
| |
| class InOutReceiver(Thread): |
| |
| def __init__(self, iface, agent, in_out_stag=None): |
| Thread.__init__(self) |
| self.iface = iface |
| self.finished = False |
| self.agent = agent |
| self.in_out_stag = in_out_stag |
| |
| def run(self): |
| # TODO this loop could be reconciled with the ofp Connection to become a |
| # single select loop. |
| self.sock = s = conf.L2listen( |
| type=ETH_P_ALL, |
| iface=self.iface, |
| filter='inbound' |
| ) |
| while not self.finished: |
| try: |
| sniffed = sniff(1, iface=self.iface, timeout=1, opened_socket=s) |
| print 'Sniffer received %d packet(s)' % len(sniffed) |
| for pkt in sniffed: |
| self.forward_packet(pkt) |
| |
| except Exception, e: |
| logging.error("scapy.sniff error: %s" % e) |
| |
| def stop(self): |
| """ |
| Signal the thread to exit and wait for it |
| """ |
| assert not self.finished |
| logging.debug("Stop sniffing on in-out channel") |
| self.finished = True |
| self.sock.close() |
| self.join() |
| |
| def forward_packet(self, pkt): |
| print "Received packet:" |
| hexdump(str(pkt)) |
| pkt.show() |
| |
| try: |
| assert isinstance(pkt, Ether) |
| assert isinstance(pkt.getlayer(1), Dot1Q) |
| dot1q = pkt.getlayer(1) |
| assert isinstance(dot1q, Dot1Q) |
| |
| if self.in_out_stag is None: |
| payload = dot1q.payload |
| payload_type = dot1q.type |
| pkt.remove_payload() |
| |
| pkt.type = payload_type |
| new_pkt = pkt / payload |
| in_port = dot1q.vlan |
| |
| else: |
| if dot1q.vlan != self.in_out_stag: |
| print 'Dropping packet because outer tag %d does not match %d' % ( |
| dot1q.vlan, self.in_out_stag) |
| return |
| dot1q_inner = dot1q.getlayer(1) |
| assert isinstance(dot1q_inner, Dot1Q) |
| payload = dot1q_inner.payload |
| payload_type = dot1q_inner.type |
| pkt.remove_payload() |
| |
| pkt.type = payload_type |
| new_pkt = pkt / payload |
| in_port = dot1q_inner.vlan |
| |
| if self.agent is not None: |
| self.agent.send_packet_in(str(new_pkt), in_port=in_port) |
| print 'new packet forwarded to controller (with in_port=%d):' % in_port |
| new_pkt.show() |
| |
| except Exception, e: |
| logging.exception("Unexpected packet format received by InOutReceiver: %s" % e) |
| logging.error(hexdump(str(pkt), 'return')) |
| |
| |