Example #1
0
 def waitinner():
     ports = self.managed_ports.get((vhost, datapathid))
     if ports is None:
         for m in callAPI(self.apiroutine, 'openflowmanager',
                          'waitconnection', {
                              'datapathid': datapathid,
                              'vhost': vhost,
                              'timeout': timeout
                          }):
             yield m
         c = self.apiroutine.retvalue
         ports = self.managed_ports.get((vhost, datapathid))
         if ports is None:
             yield (OpenflowPortSynchronized.createMatcher(c), )
         ports = self.managed_ports.get((vhost, datapathid))
         if ports is None:
             raise ConnectionResetException(
                 'Datapath %016x is not connected' % datapathid)
     for p in ports.values():
         if p.name == name:
             self.apiroutine.retvalue = p
             return
     yield (OpenflowAsyncMessageEvent.createMatcher(
         of13.OFPT_PORT_STATUS,
         datapathid,
         0,
         _ismatch=lambda x: x.message.desc.name == name), )
     self.apiroutine.retvalue = self.apiroutine.event.message.desc
Example #2
0
    def _icmp_packetin_handler(self):
        conn = self._connection
        ofdef = self._connection.openflowdef
        l3input = self.parent._gettableindex("l3input",self._connection.protocol.vhost)
        
        transactid = uint16.create(os.urandom(2)) 

        def send_packet_out(portid,packet):
            for m in self.execute_commands(conn,
                        [
                            ofdef.ofp_packet_out(
                                buffer_id = ofdef.OFP_NO_BUFFER,
                                in_port = ofdef.OFPP_CONTROLLER,
                                actions = [
                                    ofdef.ofp_action_output(port = portid,
                                                            max_len = ofdef.OFPCML_NO_BUFFER
                                                            )
                                ],
                                data = packet._tobytes()
                            )
                        ]):
                yield m

        icmp_packetin_matcher = OpenflowAsyncMessageEvent.createMatcher(ofdef.OFPT_PACKET_IN,None,None,l3input,2,
                                                            self._connection,self._connection.connmark)

        while True:
            yield (icmp_packetin_matcher,)
            msg = self.event.message
            inport = ofdef.ofp_port_no.create(ofdef.get_oxm(msg.match.oxm_fields,ofdef.OXM_OF_IN_PORT))

            # it must be icmp packet ...
            icmp_packet = ethernet_l7.create(msg.data)
            
            transactid = (transactid + 1) & 0xffff
            reply_packet = ip4_packet_l7((ip4_payload,ip4_icmp_payload),
                                         (icmp_bestparse, icmp_echo),
                                        dl_src = icmp_packet.dl_dst,
                                        dl_dst = icmp_packet.dl_src,
                                        ip_src = icmp_packet.ip_dst,
                                        ip_dst = icmp_packet.ip_src,
                                        frag_off = icmp_packet.frag_off,
                                        ttl = 128,
                                        identifier = transactid,
                                        icmp_type = ICMP_ECHOREPLY,
                                        icmp_code = icmp_packet.icmp_code,
                                        icmp_id = icmp_packet.icmp_id,
                                        icmp_seq = icmp_packet.icmp_seq,
                                        data = icmp_packet.data
                                        )
           
            self.subroutine(send_packet_out(inport,reply_packet))
Example #3
0
    def _icmp_packetin_handler(self):
        conn = self._connection
        ofdef = self._connection.openflowdef
        l3input = self.parent._gettableindex("l3input",self._connection.protocol.vhost)
        
        transactid = uint16.create(os.urandom(2)) 

        def send_packet_out(portid,packet):
            for m in self.execute_commands(conn,
                        [
                            ofdef.ofp_packet_out(
                                buffer_id = ofdef.OFP_NO_BUFFER,
                                in_port = ofdef.OFPP_CONTROLLER,
                                actions = [
                                    ofdef.ofp_action_output(port = portid,
                                                            max_len = ofdef.OFPCML_NO_BUFFER
                                                            )
                                ],
                                data = packet._tobytes()
                            )
                        ]):
                yield m

        icmp_packetin_matcher = OpenflowAsyncMessageEvent.createMatcher(ofdef.OFPT_PACKET_IN,None,None,l3input,2,
                                                            self._connection,self._connection.connmark)

        while True:
            yield (icmp_packetin_matcher,)
            msg = self.event.message
            inport = ofdef.ofp_port_no.create(ofdef.get_oxm(msg.match.oxm_fields,ofdef.OXM_OF_IN_PORT))

            # it must be icmp packet ...
            icmp_packet = ethernet_l7.create(msg.data)
            
            transactid = (transactid + 1) & 0xffff
            reply_packet = ip4_packet_l7((ip4_payload,ip4_icmp_payload),
                                         (icmp_bestparse, icmp_echo),
                                        dl_src = icmp_packet.dl_dst,
                                        dl_dst = icmp_packet.dl_src,
                                        ip_src = icmp_packet.ip_dst,
                                        ip_dst = icmp_packet.ip_src,
                                        frag_off = icmp_packet.frag_off,
                                        ttl = 128,
                                        identifier = transactid,
                                        icmp_type = ICMP_ECHOREPLY,
                                        icmp_code = icmp_packet.icmp_code,
                                        icmp_id = icmp_packet.icmp_id,
                                        icmp_seq = icmp_packet.icmp_seq,
                                        data = icmp_packet.data
                                        )
           
            self.subroutine(send_packet_out(inport,reply_packet))
Example #4
0
 def waitinner():
     ports = self.managed_ports.get((vhost, datapathid))
     if ports is None:
         for m in callAPI(self.apiroutine, 'openflowmanager', 'waitconnection', {'datapathid': datapathid, 'vhost':vhost, 'timeout': timeout}):
             yield m
         c = self.apiroutine.retvalue
         ports = self.managed_ports.get((vhost, datapathid))
         if ports is None:
             yield (OpenflowPortSynchronized.createMatcher(c),)
         ports = self.managed_ports.get((vhost, datapathid))
         if ports is None:
             raise ConnectionResetException('Datapath %016x is not connected' % datapathid)
     if portno not in ports:
         yield (OpenflowAsyncMessageEvent.createMatcher(of13.OFPT_PORT_STATUS, datapathid, 0, _ismatch = lambda x: x.message.desc.port_no == portno),)
         self.apiroutine.retvalue = self.apiroutine.event.message.desc
     else:
         self.apiroutine.retvalue = ports[portno]
Example #5
0
    def _dhcp_handler(self):
        conn = self._connection
        ofdef = self._connection.openflowdef
        l3 = self._parent._gettableindex('l3input',
                                         self._connection.protocol.vhost)
        dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher(
            ofdef.OFPT_PACKET_IN, None, None, l3, 1, self._connection,
            self._connection.connmark)
        required_tags = [
            d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER,
            d.OPTION_NETMASK, d.OPTION_ROUTER, d.OPTION_DNSSERVER,
            d.OPTION_BROADCAST, d.OPTION_MTU, d.OPTION_LEASE_TIME, d.OPTION_T1,
            d.OPTION_T2
        ]
        server_mac = mac_addr(self._parent.servermac)
        trans_id = uint16.create(os.urandom(2))

        def set_options(payload,
                        option_dict,
                        provide_options,
                        message_type,
                        remove_lease=False):
            message_type_opt = d.dhcp_option_message_type(value=message_type)
            if d.OPTION_REQUESTED_OPTIONS in option_dict:
                reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value)
                send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value
                             if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \
                            + [t for t in required_tags
                               if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \
                            + [t for t in provide_options
                               if t not in reqs and t not in required_tags]
            else:
                send_tags = [t for t in required_tags
                             if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \
                            + [t for t in set(provide_options.keys()).difference(required_tags)]
            d.build_options(
                payload, [
                    message_type_opt
                    if t == d.OPTION_MESSAGE_TYPE else provide_options[t]
                    for t in send_tags
                    if not remove_lease or (t != d.OPTION_LEASE_TIME and t !=
                                            d.OPTION_T1 and t != OPTION_T2)
                ],
                max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400),
                    576) if d.OPTION_MAX_MESSAGE_SIZE in option_dict else 576)

        def send_packet(pid, packet):
            for m in self.execute_commands(conn, [
                    ofdef.ofp_packet_out(
                        buffer_id=ofdef.OFP_NO_BUFFER,
                        in_port=ofdef.OFPP_CONTROLLER,
                        actions=[
                            ofdef.ofp_action_output(
                                port=pid, max_len=ofdef.OFPCML_NO_BUFFER)
                        ],
                        data=packet._tobytes())
            ]):
                yield m

        while True:
            yield (dhcp_packet_matcher, )
            msg = self.event.message
            try:
                in_port = ofdef.ofp_port_no.create(
                    ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT))
                if in_port not in self._dhcpentries:
                    continue
                port_mac, port_ip, server_ip, provide_options = self._dhcpentries[
                    in_port]
                l7_packet = ethernet_l7.create(msg.data)
                dhcp_packet = d.dhcp_payload.create(l7_packet.data)
                if dhcp_packet.op != d.BOOTREQUEST or \
                        dhcp_packet.hlen != 6 or dhcp_packet.htype != 1 or \
                        dhcp_packet.magic_cookie != d.BOOTP_MAGIC_COOKIE or \
                        dhcp_packet.giaddr != 0:
                    raise ValueError('Unsupported DHCP packet')
                options = d.reassemble_options(dhcp_packet)
                option_dict = dict((o.tag, o) for o in options)
                if d.OPTION_MESSAGE_TYPE not in option_dict:
                    raise ValueError('Message type not found')
                message_type = option_dict[d.OPTION_MESSAGE_TYPE].value
                is_nak = False
                if message_type == d.DHCPDISCOVER:
                    if dhcp_packet.chaddr[:6].ljust(
                            6, b'\x00') != mac_addr.tobytes(port_mac):
                        # Ignore this packet
                        continue
                    dhcp_reply = d.dhcp_payload(
                        op=d.BOOTREPLY,
                        htype=1,
                        hlen=6,
                        hops=0,
                        xid=dhcp_packet.xid,
                        secs=0,
                        flags=dhcp_packet.flags,
                        ciaddr=0,
                        yiaddr=port_ip,
                        siaddr=0,
                        giaddr=dhcp_packet.giaddr,
                        chaddr=dhcp_packet.chaddr,
                        magic_cookie=d.BOOTP_MAGIC_COOKIE)
                    set_options(dhcp_reply, option_dict, provide_options,
                                d.DHCPOFFER)
                elif message_type == d.DHCPREQUEST:
                    if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[
                            d.OPTION_SERVER_IDENTIFIER].value != server_ip:
                        # Ignore packets to wrong address
                        continue
                    if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \
                            or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \
                            or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip):
                        dhcp_reply = d.dhcp_payload(
                            op=d.BOOTREPLY,
                            htype=1,
                            hlen=6,
                            hops=0,
                            xid=dhcp_packet.xid,
                            secs=0,
                            flags=dhcp_packet.flags,
                            ciaddr=0,
                            yiaddr=0,
                            siaddr=0,
                            giaddr=dhcp_packet.giaddr,
                            chaddr=dhcp_packet.chaddr,
                            magic_cookie=d.BOOTP_MAGIC_COOKIE)
                        d.build_options(dhcp_reply, [
                            d.dhcp_option_message_type(value=d.DHCPNAK),
                            d.dhcp_option_address(
                                tag=d.OPTION_SERVER_IDENTIFIER,
                                value=server_ip)
                        ], 576, 0)
                        is_nak = True
                    else:
                        dhcp_reply = d.dhcp_payload(
                            op=d.BOOTREPLY,
                            htype=1,
                            hlen=6,
                            hops=0,
                            xid=dhcp_packet.xid,
                            secs=0,
                            flags=dhcp_packet.flags,
                            ciaddr=dhcp_packet.ciaddr,
                            yiaddr=port_ip,
                            siaddr=0,
                            giaddr=dhcp_packet.giaddr,
                            chaddr=dhcp_packet.chaddr,
                            magic_cookie=d.BOOTP_MAGIC_COOKIE)
                        set_options(dhcp_reply, option_dict, provide_options,
                                    d.DHCPACK)
                elif message_type == d.DHCPDECLINE:
                    self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\
                                         ' Connection = %r(%016x), port = %d.',
                                         self._connection, self._connection.openflow_datapathid)
                elif message_type == d.DHCPRELEASE:
                    # Safe to ignore
                    continue
                elif message_type == d.DHCPINFORM:
                    dhcp_reply = d.dhcp_payload(
                        op=d.BOOTREPLY,
                        htype=1,
                        hlen=6,
                        hops=0,
                        xid=dhcp_packet.xid,
                        secs=0,
                        flags=dhcp_packet.flags,
                        ciaddr=dhcp_packet.ciaddr,
                        yiaddr=0,
                        siaddr=0,
                        giaddr=dhcp_packet.giaddr,
                        chaddr=dhcp_packet.chaddr,
                        magic_cookie=d.BOOTP_MAGIC_COOKIE)
                    set_options(dhcp_reply, option_dict, provide_options,
                                d.DHCPACK, True)
                trans_id = (trans_id + 1) & 0xffff
                if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak:
                    dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
                    ip_dst = 0xffffffff
                else:
                    dl_dst = l7_packet.dl_src
                    ip_dst = port_ip
                reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload),
                                             dl_src=server_mac,
                                             dl_dst=dl_dst,
                                             identifier=trans_id,
                                             ttl=128,
                                             ip_src=server_ip,
                                             ip_dst=ip_dst,
                                             sport=67,
                                             dport=68,
                                             data=dhcp_reply._tobytes())
                self.subroutine(send_packet(in_port, reply_packet), True)
            except Exception:
                self._logger.info('Invalid DHCP packet received: %r',
                                  msg.data,
                                  exc_info=True)
Example #6
0
 def _manage_ports(self):
     try:
         self.apiroutine.subroutine(self._get_existing_ports())
         conn_update = ModuleNotification.createMatcher('openflowmanager', 'update')
         port_status = OpenflowAsyncMessageEvent.createMatcher(of13.OFPT_PORT_STATUS, None, 0)
         while True:
             yield (conn_update, port_status)
             if self.apiroutine.matcher is port_status:
                 e = self.apiroutine.event
                 m = e.message
                 c = e.connection
                 if (c.protocol.vhost, c.openflow_datapathid) in self.managed_ports:
                     if m.reason == c.openflowdef.OFPPR_ADD:
                         # A new port is added
                         self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] = m.desc
                         self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update',
                                                                      datapathid = c.openflow_datapathid,
                                                                      connection = c,
                                                                      vhost = c.protocol.vhost,
                                                                      add = [m.desc], remove = [],
                                                                      reason = 'add'))
                     elif m.reason == c.openflowdef.OFPPR_DELETE:
                         try:
                             del self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no]
                             self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update',
                                                                          datapathid = c.openflow_datapathid,
                                                                          connection = c,
                                                                          vhost = c.protocol.vhost,
                                                                          add = [], remove = [m.desc],
                                                                          reason = 'delete'))
                         except KeyError:
                             pass
                     elif m.reason == c.openflowdef.OFPPR_MODIFY:
                         try:
                             self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'modified',
                                                                          datapathid = c.openflow_datapathid,
                                                                          connection = c,
                                                                          vhost = c.protocol.vhost,
                                                                          old = self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no],
                                                                          new = m.desc,
                                                                          reason = 'modified'))
                         except KeyError:
                             self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update',
                                                                          datapathid = c.openflow_datapathid,
                                                                          connection = c,
                                                                          vhost = c.protocol.vhost,
                                                                          add = [m.desc], remove = [],
                                                                          reason = 'add'))
                         self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)][m.desc.port_no] = m.desc
             else:
                 e = self.apiroutine.event
                 for c in e.remove:
                     if c.openflow_auxiliaryid == 0 and (c.protocol.vhost, c.openflow_datapathid) in self.managed_ports:
                         self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'update',
                                              datapathid = c.openflow_datapathid,
                                              connection = c,
                                              vhost = c.protocol.vhost,
                                              add = [], remove = list(self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)].values()),
                                              reason = 'disconnected'))
                         del self.managed_ports[(c.protocol.vhost, c.openflow_datapathid)]
                 for c in e.add:
                     if c.openflow_auxiliaryid == 0:
                         self.apiroutine.subroutine(self._get_ports(c, c.protocol, True, True))
     finally:
         self.scheduler.emergesend(ModuleNotification(self.getServiceName(), 'unsynchronized'))
Example #7
0
 def _manage_ports(self):
     try:
         self.apiroutine.subroutine(self._get_existing_ports())
         conn_update = ModuleNotification.createMatcher(
             'openflowmanager', 'update')
         port_status = OpenflowAsyncMessageEvent.createMatcher(
             of13.OFPT_PORT_STATUS, None, 0)
         while True:
             yield (conn_update, port_status)
             if self.apiroutine.matcher is port_status:
                 e = self.apiroutine.event
                 m = e.message
                 c = e.connection
                 if (c.protocol.vhost,
                         c.openflow_datapathid) in self.managed_ports:
                     if m.reason == c.openflowdef.OFPPR_ADD:
                         # A new port is added
                         self.managed_ports[(c.protocol.vhost,
                                             c.openflow_datapathid
                                             )][m.desc.port_no] = m.desc
                         self.scheduler.emergesend(
                             ModuleNotification(
                                 self.getServiceName(),
                                 'update',
                                 datapathid=c.openflow_datapathid,
                                 connection=c,
                                 vhost=c.protocol.vhost,
                                 add=[m.desc],
                                 remove=[],
                                 reason='add'))
                     elif m.reason == c.openflowdef.OFPPR_DELETE:
                         try:
                             del self.managed_ports[(
                                 c.protocol.vhost,
                                 c.openflow_datapathid)][m.desc.port_no]
                             self.scheduler.emergesend(
                                 ModuleNotification(
                                     self.getServiceName(),
                                     'update',
                                     datapathid=c.openflow_datapathid,
                                     connection=c,
                                     vhost=c.protocol.vhost,
                                     add=[],
                                     remove=[m.desc],
                                     reason='delete'))
                         except KeyError:
                             pass
                     elif m.reason == c.openflowdef.OFPPR_MODIFY:
                         try:
                             self.scheduler.emergesend(
                                 ModuleNotification(
                                     self.getServiceName(),
                                     'modified',
                                     datapathid=c.openflow_datapathid,
                                     connection=c,
                                     vhost=c.protocol.vhost,
                                     old=self.managed_ports[(
                                         c.protocol.vhost,
                                         c.openflow_datapathid
                                     )][m.desc.port_no],
                                     new=m.desc,
                                     reason='modified'))
                         except KeyError:
                             self.scheduler.emergesend(
                                 ModuleNotification(
                                     self.getServiceName(),
                                     'update',
                                     datapathid=c.openflow_datapathid,
                                     connection=c,
                                     vhost=c.protocol.vhost,
                                     add=[m.desc],
                                     remove=[],
                                     reason='add'))
                         self.managed_ports[(c.protocol.vhost,
                                             c.openflow_datapathid
                                             )][m.desc.port_no] = m.desc
             else:
                 e = self.apiroutine.event
                 for c in e.remove:
                     if c.openflow_auxiliaryid == 0 and (
                             c.protocol.vhost,
                             c.openflow_datapathid) in self.managed_ports:
                         self.scheduler.emergesend(
                             ModuleNotification(
                                 self.getServiceName(),
                                 'update',
                                 datapathid=c.openflow_datapathid,
                                 connection=c,
                                 vhost=c.protocol.vhost,
                                 add=[],
                                 remove=list(self.managed_ports[(
                                     c.protocol.vhost,
                                     c.openflow_datapathid)].values()),
                                 reason='disconnected'))
                         del self.managed_ports[(c.protocol.vhost,
                                                 c.openflow_datapathid)]
                 for c in e.add:
                     if c.openflow_auxiliaryid == 0:
                         self.apiroutine.subroutine(
                             self._get_ports(c, c.protocol, True, True))
     finally:
         self.scheduler.emergesend(
             ModuleNotification(self.getServiceName(), 'unsynchronized'))
Example #8
0
    async def _dhcp_handler(self):
        """
        Mini DHCP server, respond DHCP packets from OpenFlow
        """
        conn = self._connection
        ofdef = self._connection.openflowdef
        l3 = self._parent._gettableindex('l3input',
                                         self._connection.protocol.vhost)
        dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher(
            ofdef.OFPT_PACKET_IN, None, None, l3, 1, self._connection,
            self._connection.connmark)
        # These tags are important options. They are sent first to make sure the client
        # correctly receive these options.
        required_tags = [
            d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER,
            d.OPTION_NETMASK, d.OPTION_ROUTER, d.OPTION_DNSSERVER,
            d.OPTION_BROADCAST, d.OPTION_MTU, d.OPTION_LEASE_TIME, d.OPTION_T1,
            d.OPTION_T2
        ]
        server_mac = mac_addr(self._parent.servermac)
        # IP fragment identifier
        trans_id = uint16.create(os.urandom(2))

        def set_options(payload,
                        option_dict,
                        provide_options,
                        message_type,
                        remove_lease=False):
            """
            Set DHCP options to output payload regarding the incoming request
            
            :param payload: output DHCP payload
            
            :param option_dict: incoming DHCP options in request
            
            :param provide_options: all DHCP options that are ready to sent to the client
            
            :param message_type: output DHCP message type
            
            :param remove_lease: remove all leases options. DHCPINFORM cannot contain
                                 leases options. See https://tools.ietf.org/html/rfc2131#section-3.4
            """
            message_type_opt = d.dhcp_option_message_type(value=message_type)
            if d.OPTION_REQUESTED_OPTIONS in option_dict:
                # First requested options, then required options, then others
                reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value)
                send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value
                             if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \
                            + [t for t in required_tags
                               if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \
                            + [t for t in provide_options
                               if t not in reqs and t not in required_tags]
            else:
                # Required options, then others
                send_tags = [t for t in required_tags
                             if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \
                            + [t for t in set(provide_options.keys()).difference(required_tags)]
            # If the client has sent an option for max message size, use it; or use the RFC required 576 bytes
            not_finished = d.build_options(
                payload, [
                    message_type_opt
                    if t == d.OPTION_MESSAGE_TYPE else provide_options[t]
                    for t in send_tags
                    if not remove_lease or (t != d.OPTION_LEASE_TIME and t !=
                                            d.OPTION_T1 and t != OPTION_T2)
                ],
                max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400),
                    576) if d.OPTION_MAX_MESSAGE_SIZE in option_dict else 576)

        async def send_packet(pid, packet):
            """
            Send DHCP packet to specified port
            """
            await self.execute_commands(conn, [
                ofdef.ofp_packet_out(buffer_id=ofdef.OFP_NO_BUFFER,
                                     in_port=ofdef.OFPP_CONTROLLER,
                                     actions=[
                                         ofdef.ofp_action_output(
                                             port=pid,
                                             max_len=ofdef.OFPCML_NO_BUFFER)
                                     ],
                                     data=packet._tobytes())
            ])

        while True:
            ev = await dhcp_packet_matcher
            msg = ev.message
            try:
                in_port = ofdef.ofp_port_no.create(
                    ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT))
                if in_port not in self._dhcpentries:
                    # Not a DHCP-enabled port
                    continue
                port_mac, port_ip, server_ip, provide_options = self._dhcpentries[
                    in_port]
                # Fragmented DHCP packets are not supported - this is allowed according
                # to RFC: server and clients are only needed to support at least 576 bytes
                # DHCP messages.
                l7_packet = ethernet_l7.create(msg.data)
                dhcp_packet = d.dhcp_payload.create(l7_packet.data)
                # We only process a DHCP request directly sent from the logical port
                if (dhcp_packet.op != d.BOOTREQUEST  # A DHCP server packet
                        or dhcp_packet.hlen != 6 or dhcp_packet.htype !=
                        1  # Hardware address not ethernet (48-bit)
                        or dhcp_packet.magic_cookie !=
                        d.BOOTP_MAGIC_COOKIE  # Magic cookie not match
                        or dhcp_packet.giaddr != 0):  # A relayed DHCP message
                    raise ValueError('Unsupported DHCP packet')
                # Reassemble DHCP options
                options = d.reassemble_options(dhcp_packet)
                option_dict = dict((o.tag, o) for o in options)
                if d.OPTION_MESSAGE_TYPE not in option_dict:
                    raise ValueError('Message type not found')
                message_type = option_dict[d.OPTION_MESSAGE_TYPE].value
                is_nak = False
                if message_type == d.DHCPDISCOVER:
                    # A DHCPDISCOVER should get a DHCPOFFER response
                    if dhcp_packet.chaddr[:6].ljust(
                            6, b'\x00') != mac_addr.tobytes(port_mac):
                        # MAC address not match, ignore this packet
                        continue
                    dhcp_reply = d.dhcp_payload(
                        op=d.BOOTREPLY,
                        htype=1,
                        hlen=6,
                        hops=0,
                        xid=dhcp_packet.xid,
                        secs=0,
                        flags=dhcp_packet.flags,
                        ciaddr=0,
                        yiaddr=port_ip,
                        siaddr=0,
                        giaddr=dhcp_packet.giaddr,
                        chaddr=dhcp_packet.chaddr,
                        magic_cookie=d.BOOTP_MAGIC_COOKIE)
                    set_options(dhcp_reply, option_dict, provide_options,
                                d.DHCPOFFER)
                elif message_type == d.DHCPREQUEST:
                    # A DHCPREQUEST should get a DHCPACK reply
                    if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[
                            d.OPTION_SERVER_IDENTIFIER].value != server_ip:
                        # Ignore packets to wrong address
                        continue
                    if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \
                            or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \
                            or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip):
                        # Requested MAC or IP not matched, send a NACK
                        dhcp_reply = d.dhcp_payload(
                            op=d.BOOTREPLY,
                            htype=1,
                            hlen=6,
                            hops=0,
                            xid=dhcp_packet.xid,
                            secs=0,
                            flags=dhcp_packet.flags,
                            ciaddr=0,
                            yiaddr=0,
                            siaddr=0,
                            giaddr=dhcp_packet.giaddr,
                            chaddr=dhcp_packet.chaddr,
                            magic_cookie=d.BOOTP_MAGIC_COOKIE)
                        # Do not send more options in NACK
                        d.build_options(dhcp_reply, [
                            d.dhcp_option_message_type(value=d.DHCPNAK),
                            d.dhcp_option_address(
                                tag=d.OPTION_SERVER_IDENTIFIER,
                                value=server_ip)
                        ], 576, 0)
                        is_nak = True
                    else:
                        dhcp_reply = d.dhcp_payload(
                            op=d.BOOTREPLY,
                            htype=1,
                            hlen=6,
                            hops=0,
                            xid=dhcp_packet.xid,
                            secs=0,
                            flags=dhcp_packet.flags,
                            ciaddr=dhcp_packet.ciaddr,
                            yiaddr=port_ip,
                            siaddr=0,
                            giaddr=dhcp_packet.giaddr,
                            chaddr=dhcp_packet.chaddr,
                            magic_cookie=d.BOOTP_MAGIC_COOKIE)
                        set_options(dhcp_reply, option_dict, provide_options,
                                    d.DHCPACK)
                elif message_type == d.DHCPDECLINE:
                    # Address already in use?
                    self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\
                                         ' Connection = %r(%016x), port = %d.',
                                         self._connection, self._connection.openflow_datapathid)
                elif message_type == d.DHCPRELEASE:
                    # Safe to ignore, we do not use a dynamic IP address pool
                    continue
                elif message_type == d.DHCPINFORM:
                    # Client setup IP addresses itself, but requesting more information
                    # DHCPINFORM reply cannot have lease options, and yiaddr = 0
                    dhcp_reply = d.dhcp_payload(
                        op=d.BOOTREPLY,
                        htype=1,
                        hlen=6,
                        hops=0,
                        xid=dhcp_packet.xid,
                        secs=0,
                        flags=dhcp_packet.flags,
                        ciaddr=dhcp_packet.ciaddr,
                        yiaddr=0,
                        siaddr=0,
                        giaddr=dhcp_packet.giaddr,
                        chaddr=dhcp_packet.chaddr,
                        magic_cookie=d.BOOTP_MAGIC_COOKIE)
                    set_options(dhcp_reply, option_dict, provide_options,
                                d.DHCPACK, True)
                trans_id = (trans_id + 1) & 0xffff
                if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak:
                    # client request broadcast, or DHCPNAK
                    # RFC requires that DHCPNAK uses broadcast
                    dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
                    ip_dst = 0xffffffff
                else:
                    dl_dst = l7_packet.dl_src
                    ip_dst = port_ip
                reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload),
                                             dl_src=server_mac,
                                             dl_dst=dl_dst,
                                             identifier=trans_id,
                                             ttl=128,
                                             ip_src=server_ip,
                                             ip_dst=ip_dst,
                                             sport=67,
                                             dport=68,
                                             data=dhcp_reply._tobytes())
                # Send packet to the incoming port
                self.subroutine(send_packet(in_port, reply_packet), True)
            except Exception:
                self._logger.info('Invalid DHCP packet received: %r',
                                  msg.data,
                                  exc_info=True)
Example #9
0
 def _dhcp_handler(self):
     conn = self._connection
     ofdef = self._connection.openflowdef
     l3 = self._parent._gettableindex('l3input', self._connection.protocol.vhost)
     dhcp_packet_matcher = OpenflowAsyncMessageEvent.createMatcher(ofdef.OFPT_PACKET_IN, None, None, l3, 1,
                                                           self._connection, self._connection.connmark)
     required_tags = [d.OPTION_MESSAGE_TYPE, d.OPTION_SERVER_IDENTIFIER,
                      d.OPTION_NETMASK, d.OPTION_ROUTER,
                      d.OPTION_DNSSERVER, d.OPTION_BROADCAST, d.OPTION_MTU,
                      d.OPTION_LEASE_TIME, d.OPTION_T1, d.OPTION_T2]
     server_mac = mac_addr(self._parent.servermac)
     trans_id = uint16.create(os.urandom(2))
     def set_options(payload, option_dict, provide_options, message_type, remove_lease = False):
         message_type_opt = d.dhcp_option_message_type(value = message_type)
         if d.OPTION_REQUESTED_OPTIONS in option_dict:
             reqs = set(option_dict[d.OPTION_REQUESTED_OPTIONS].value)
             send_tags = [t for t in option_dict[d.OPTION_REQUESTED_OPTIONS].value
                          if t == d.OPTION_MESSAGE_TYPE or t in provide_options] \
                         + [t for t in required_tags
                            if (t in provide_options or t == d.OPTION_MESSAGE_TYPE) and t not in reqs] \
                         + [t for t in provide_options
                            if t not in reqs and t not in required_tags]
         else:
             send_tags = [t for t in required_tags
                          if t in provide_options or t == d.OPTION_MESSAGE_TYPE] \
                         + [t for t in set(provide_options.keys()).difference(required_tags)]
         d.build_options(payload, [message_type_opt if t == d.OPTION_MESSAGE_TYPE
                                   else provide_options[t] for t in send_tags
                                   if not remove_lease or (t != d.OPTION_LEASE_TIME and t != d.OPTION_T1 and t != OPTION_T2)],
                         max(min(option_dict[d.OPTION_MAX_MESSAGE_SIZE].value, 1400), 576)
                         if d.OPTION_MAX_MESSAGE_SIZE in option_dict
                         else 576)
     def send_packet(pid, packet):
         for m in self.execute_commands(conn,
                         [ofdef.ofp_packet_out(
                                 buffer_id = ofdef.OFP_NO_BUFFER,
                                 in_port = ofdef.OFPP_CONTROLLER,
                                 actions = [ofdef.ofp_action_output(port = pid,
                                                                    max_len = ofdef.OFPCML_NO_BUFFER)],
                                 data = packet._tobytes()
                             )]):
             yield m
     while True:
         yield (dhcp_packet_matcher,)
         msg = self.event.message
         try:
             in_port = ofdef.ofp_port_no.create(ofdef.get_oxm(msg.match.oxm_fields, ofdef.OXM_OF_IN_PORT))
             if in_port not in self._dhcpentries:
                 continue
             port_mac, port_ip, server_ip, provide_options = self._dhcpentries[in_port]
             l7_packet = ethernet_l7.create(msg.data)
             dhcp_packet = d.dhcp_payload.create(l7_packet.data)
             if dhcp_packet.op != d.BOOTREQUEST or \
                     dhcp_packet.hlen != 6 or dhcp_packet.htype != 1 or \
                     dhcp_packet.magic_cookie != d.BOOTP_MAGIC_COOKIE or \
                     dhcp_packet.giaddr != 0:
                 raise ValueError('Unsupported DHCP packet')
             options = d.reassemble_options(dhcp_packet)
             option_dict = dict((o.tag, o) for o in options)
             if d.OPTION_MESSAGE_TYPE not in option_dict:
                 raise ValueError('Message type not found')
             message_type = option_dict[d.OPTION_MESSAGE_TYPE].value
             is_nak = False
             if message_type == d.DHCPDISCOVER:
                 if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac):
                     # Ignore this packet
                     continue
                 dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY,
                                             htype = 1,
                                             hlen = 6,
                                             hops = 0,
                                             xid = dhcp_packet.xid,
                                             secs = 0,
                                             flags = dhcp_packet.flags,
                                             ciaddr = 0,
                                             yiaddr = port_ip,
                                             siaddr = 0,
                                             giaddr = dhcp_packet.giaddr,
                                             chaddr = dhcp_packet.chaddr,
                                             magic_cookie = d.BOOTP_MAGIC_COOKIE
                                             )
                 set_options(dhcp_reply, option_dict, provide_options, d.DHCPOFFER)
             elif message_type == d.DHCPREQUEST:
                 if d.OPTION_SERVER_IDENTIFIER in option_dict and option_dict[d.OPTION_SERVER_IDENTIFIER].value != server_ip:
                     # Ignore packets to wrong address
                     continue
                 if dhcp_packet.chaddr[:6].ljust(6, b'\x00') != mac_addr.tobytes(port_mac) \
                         or (d.OPTION_REQUESTED_IP in option_dict and option_dict[d.OPTION_REQUESTED_IP].value != port_ip) \
                         or (dhcp_packet.ciaddr != 0 and dhcp_packet.ciaddr != port_ip):
                     dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY,
                                                 htype = 1,
                                                 hlen = 6,
                                                 hops = 0,
                                                 xid = dhcp_packet.xid,
                                                 secs = 0,
                                                 flags = dhcp_packet.flags,
                                                 ciaddr = 0,
                                                 yiaddr = 0,
                                                 siaddr = 0,
                                                 giaddr = dhcp_packet.giaddr,
                                                 chaddr = dhcp_packet.chaddr,
                                                 magic_cookie = d.BOOTP_MAGIC_COOKIE
                                                 )
                     d.build_options(dhcp_reply, [d.dhcp_option_message_type(value = d.DHCPNAK),
                                                  d.dhcp_option_address(tag = d.OPTION_SERVER_IDENTIFIER,
                                                                        value = server_ip)], 576, 0)
                     is_nak = True
                 else:
                     dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY,
                                                 htype = 1,
                                                 hlen = 6,
                                                 hops = 0,
                                                 xid = dhcp_packet.xid,
                                                 secs = 0,
                                                 flags = dhcp_packet.flags,
                                                 ciaddr = dhcp_packet.ciaddr,
                                                 yiaddr = port_ip,
                                                 siaddr = 0,
                                                 giaddr = dhcp_packet.giaddr,
                                                 chaddr = dhcp_packet.chaddr,
                                                 magic_cookie = d.BOOTP_MAGIC_COOKIE
                                                 )
                     set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK)
             elif message_type == d.DHCPDECLINE:
                 self._logger.warning('DHCP client reports DHCPDECLINE, there should be some problems.'\
                                      ' Connection = %r(%016x), port = %d.',
                                      self._connection, self._connection.openflow_datapathid)
             elif message_type == d.DHCPRELEASE:
                 # Safe to ignore
                 continue
             elif message_type == d.DHCPINFORM:
                 dhcp_reply = d.dhcp_payload(op = d.BOOTREPLY,
                                             htype = 1,
                                             hlen = 6,
                                             hops = 0,
                                             xid = dhcp_packet.xid,
                                             secs = 0,
                                             flags = dhcp_packet.flags,
                                             ciaddr = dhcp_packet.ciaddr,
                                             yiaddr = 0,
                                             siaddr = 0,
                                             giaddr = dhcp_packet.giaddr,
                                             chaddr = dhcp_packet.chaddr,
                                             magic_cookie = d.BOOTP_MAGIC_COOKIE
                                             )
                 set_options(dhcp_reply, option_dict, provide_options, d.DHCPACK, True)
             trans_id = (trans_id + 1) & 0xffff
             if (dhcp_packet.flags & d.DHCPFLAG_BROADCAST) or is_nak:
                 dl_dst = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
                 ip_dst = 0xffffffff
             else:
                 dl_dst = l7_packet.dl_src
                 ip_dst = port_ip
             reply_packet = ip4_packet_l7((ip4_payload, ip4_udp_payload),
                                        dl_src = server_mac,
                                        dl_dst = dl_dst,
                                        identifier = trans_id,
                                        ttl = 128,
                                        ip_src = server_ip,
                                        ip_dst = ip_dst,
                                        sport = 67,
                                        dport = 68,
                                        data = dhcp_reply._tobytes()
                                        )
             self.subroutine(send_packet(in_port, reply_packet), True)
         except Exception:
             self._logger.info('Invalid DHCP packet received: %r', msg.data, exc_info = True)