Exemplo n.º 1
0
    def _respond_dhcp(self, datapath, port, pool,
                      pkt_ethernet, pkt_vlan, pkt_ip, pkt_udp, pkt_dhcp):
        # DHCP message type code
        options = dict()
        for option in pkt_dhcp.options.option_list:
            options[option.tag] = option.value
        src = pkt_dhcp.chaddr

        # RESPONSE MSG
        pkt = packet.Packet()
        pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,
                                           dst=pkt_ethernet.src,
                                           src='00:00:00:00:00:00'))
        if pkt_vlan:
            pkt.add_protocol(vlan.vlan(cfi=pkt_vlan.cfi,
                                       ethertype=pkt_vlan.ethertype,
                                       pcp=pkt_vlan.pcp,
                                       vid=pkt_vlan.vid))
        pkt.add_protocol(ipv4.ipv4(src=pkt_ip.dst, dst=pkt_ip.src, proto=in_proto.IPPROTO_UDP))
        pkt.add_protocol(udp.udp(src_port=pkt_udp.dst_port, dst_port=pkt_udp.src_port))

        # DISCOVER MSG
        dhcp_msg_type = ord(options[dhcp.DHCP_MESSAGE_TYPE_OPT])
        if dhcp_msg_type == dhcp.DHCP_DISCOVER:
            msg_type = dhcp.DHCP_OFFER
            if src in self.leases:
                offer = self.leases[src]
                del self.leases[src]
                self.offers[src] = offer
            else:
                offer = self.offers.get(src)
                if offer is None:
                    if len(pool) == 0:
                        LOG.error("Out of IP addresses")
                        msg_type = dhcp.DHCP_NAK

                    offer = pool[0]
                    if dhcp.DHCP_REQUESTED_IP_ADDR_OPT in options:
                        wanted_ip = IPAddr(addrconv.ipv4.bin_to_text(options[dhcp.DHCP_REQUESTED_IP_ADDR_OPT].value))
                        if wanted_ip in pool:
                            offer = wanted_ip
                    pool.remove(offer)
                    self.offers[src] = offer
            wanted_opts = list()
            if dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT in options:
                fmt = "s" * len(options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                wanted_opt_set = struct.unpack(fmt, options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                for i in wanted_opt_set:
                    wanted_opts.append(ord(i))
            option_list = list()
            option_list.append(dhcp.option(dhcp.DHCP_MESSAGE_TYPE_OPT, chr(msg_type), length=1))
            if msg_type is not dhcp.DHCP_NAK:
                if dhcp.DHCP_SUBNET_MASK_OPT in wanted_opts:
                    option_list.append(dhcp.option(dhcp.DHCP_SUBNET_MASK_OPT,
                                                   addrconv.ipv4.text_to_bin(self.subnet.toStr()),
                                                   length=4))
                if dhcp.DHCP_GATEWAY_ADDR_OPT in wanted_opts and self.router_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_GATEWAY_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.router_addr.toStr()),
                                                   length=4))
                if dhcp.DHCP_DNS_SERVER_ADDR_OPT in wanted_opts and self.dns_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_DNS_SERVER_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.dns_addr.toStr()),
                                                   length=4))
                option_list.append(dhcp.option(dhcp.DHCP_IP_ADDR_LEASE_TIME_OPT,
                                               chr(self.lease_time),
                                               length=4))
                option_list.append(dhcp.option(dhcp.DHCP_RENEWAL_TIME_OPT, chr(self.lease_time / 2), length=4))
                option_list.append(dhcp.option(dhcp.DHCP_REBINDING_TIME_OPT, chr(self.lease_time * 7 / 8), length=4))
            resp_options = dhcp.options(option_list=option_list)
            pkt.add_protocol(dhcp.dhcp(op=self._MSG_TYPE_BOOT_REPLY, chaddr=pkt_dhcp.chaddr, options=resp_options,
                                       xid=pkt_dhcp.xid, ciaddr=pkt_dhcp.ciaddr, yiaddr=offer.toStr(),
                                       siaddr=self.ip_addr.toStr(), giaddr='0.0.0.0', sname='', boot_file=''))

        # REQUEST MSG
        if dhcp_msg_type == dhcp.DHCP_REQUEST:
            msg_type = dhcp.DHCP_ACK
            if dhcp.DHCP_REQUESTED_IP_ADDR_OPT not in options:
                return
            wanted_ip = IPAddr(addrconv.ipv4.bin_to_text(options[dhcp.DHCP_REQUESTED_IP_ADDR_OPT]))
            got_ip = None
            if src in self.leases:
                if wanted_ip != self.leases[src]:
                    pool.append(self.leases[src])
                    del self.leases[src]
                else:
                    got_ip = self.leases[src]
            if got_ip is None:
                if src in self.offers:
                    if wanted_ip != self.offers[src]:
                        pool.append(self.offers[src])
                        del self.offers[src]
                    else:
                        got_ip = self.offers[src]
            if got_ip is None:
                if wanted_ip in pool:
                    pool.remove(wanted_ip)
                    got_ip = wanted_ip
            if got_ip is None:
                LOG.warn("%s asked for un-offered %s", src, wanted_ip.toStr())
                msg_type = dhcp.DHCP_NAK
            wanted_opts = list()
            if dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT in options:
                fmt = "s" * len(options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                wanted_opt_set = struct.unpack(fmt, options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                for i in wanted_opt_set:
                    wanted_opts.append(ord(i))
            # DHCP options tag code
            option_list = list()
            option_list.append(dhcp.option(dhcp.DHCP_MESSAGE_TYPE_OPT, chr(msg_type), length=1))
            if msg_type is not dhcp.DHCP_NAK:
                if dhcp.DHCP_SUBNET_MASK_OPT in wanted_opts:
                    option_list.append(dhcp.option(dhcp.DHCP_SUBNET_MASK_OPT,
                                                   addrconv.ipv4.text_to_bin(self.subnet.toStr()),
                                                   length=4))
                if dhcp.DHCP_GATEWAY_ADDR_OPT in wanted_opts and self.router_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_GATEWAY_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.router_addr.toStr()),
                                                   length=4))
                if dhcp.DHCP_DNS_SERVER_ADDR_OPT in wanted_opts and self.dns_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_DNS_SERVER_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.dns_addr.toStr()),
                                                   length=4))
                option_list.append(dhcp.option(dhcp.DHCP_IP_ADDR_LEASE_TIME_OPT,
                                               chr(self.lease_time),
                                               length=4))
                option_list.append(dhcp.option(dhcp.DHCP_RENEWAL_TIME_OPT, chr(self.lease_time / 2), length=4))
                option_list.append(dhcp.option(dhcp.DHCP_REBINDING_TIME_OPT, chr(self.lease_time * 7 / 8), length=4))
            resp_options = dhcp.options(option_list=option_list)
            pkt.add_protocol(dhcp.dhcp(op=self._MSG_TYPE_BOOT_REPLY, chaddr=pkt_dhcp.chaddr, options=resp_options,
                                       xid=pkt_dhcp.xid, ciaddr=pkt_dhcp.ciaddr, yiaddr=wanted_ip.toStr(),
                                       siaddr=self.ip_addr.toStr(), giaddr='0.0.0.0', sname='', boot_file=''))

        # RELEASE MSG
        if dhcp_msg_type == dhcp.DHCP_RELEASE:
            if self.leases.get(src).toStr() != pkt_dhcp.ciaddr:
                LOG.warn("%s tried to release unleased %s" % (src, pkt_dhcp.ciaddr))
                return
            del self.leases[src]
            pool.append(pkt_dhcp.ciaddr)
            LOG.info("%s released %s" % (src, pkt_dhcp.ciaddr))

        self._send_dhcp_reply(datapath, port, pkt)
        return True
Exemplo n.º 2
0
class Dhcpd(app_manager.RyuApp):
    _MSG_TYPE_BOOT_REPLY = 2
    _MSG_TYPE_BOOT_REQ = 1
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, ip_address="192.168.0.254", router_address=(), dns_address=(), pool=None, subnet=None,
                 install_flow=True):
        super(Dhcpd, self).__init__()

        def fix_addr(addr, backup):
            if addr is None:
                return None
            if addr is ():
                return IPAddr(backup)
            return IPAddr(addr)

        self._install_flow = install_flow

        self.ip_addr = IPAddr(ip_address)
        self.router_addr = fix_addr(router_address, ip_address)
        self.dns_addr = fix_addr(dns_address, self.router_addr)

        if pool is None:
            self.pool = [IPAddr("192.168.0." + str(x)) for x in range(100, 199)]
            self.subnet = IPAddr(subnet or "255.255.255.0")
        else:
            self.pool = pool
            self.subnet = subnet
            if hasattr(pool, 'subnet_mask'):
                self.subnet = pool.subnet_mask
            if self.subnet is None:
                raise RuntimeError("You must specify a subnet mask or use a pool with a subnet hint")

        self.lease_time = 180
        # TODO: Actually make them expire :)

        self.offers = {}  # Eth -> IP we offered
        self.leases = {}  # Eth -> IP we leased

        if self.ip_addr in self.pool:
            LOG.debug("Removing my own IP (%s) from address pool", self.ip_addr)
            self.pool.remove(self.ip_addr)

            # core.openflow.addListeners(self)

    def _get_pool(self):
        return self.pool

    def add_dhcp_flow(self, datapath):
        ofproto = datapath.ofproto
        match = datapath.ofproto_parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
                                                 ip_proto=inet.IPPROTO_UDP,
                                                 udp_src=68,
                                                 udp_dst=67)
        actions = [datapath.ofproto_parser.OFPActionOutput(datapath.ofproto_parser.OFPP_CONTROLLER)]
        instructions = [datapath.ofproto_parser.OFPInstructionActions(
            ofproto.OFPIT_APPLY_ACTIONS, actions)]

        mod = datapath.ofproto_parser.OFPFlowMod(
            datapath=datapath, cookie=0, cookie_mask=0, table_id=0,
            command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
            priority=1, buffer_id=ofproto.OFP_NO_BUFFER,
            flags=0, match=match, instructions=instructions)

        datapath.send_msg(mod)

    @log_helpers.log_method_call
    def _send_dhcp_reply(self, datapath, port, pkt):
        ofp = datapath.ofproto
        ofpp = datapath.ofproto_parser
        pkt.serialize()
        data = pkt.data
        actions = [ofpp.OFPActionOutput(port=port)]
        out = ofpp.OFPPacketOut(datapath=datapath,
                                buffer_id=ofp.OFP_NO_BUFFER,
                                in_port=ofp.OFPP_CONTROLLER,
                                actions=actions,
                                data=data)
        datapath.send_msg(out)

    @handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        """Check a packet-in message.

           Build and output an dhcp reply if a packet-in message is
           an dhcp packet.
        """
        msg = ev.msg
        LOG.debug("packet-in msg %s", msg)
        datapath = msg.datapath
        port = msg.match['in_port']
        # NOTE: Ryu packet library can raise various exceptions on a corrupted packet.
        try:
            pkt = packet.Packet(msg.data)
        except Exception as e:
            LOG.debug("Unparsable packet: got exception %s", e)
            return
        LOG.debug("packet-in dpid %(dpid)s in_port %(port)s pkt %(pkt)s",
                  {'dpid': dpid_lib.dpid_to_str(datapath.id),
                   'port': port, 'pkt': pkt})

        pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
        if not pkt_ethernet:
            LOG.debug("drop non-ethernet packet")
            return
        pkt_vlan = pkt.get_protocol(vlan.vlan)
        pkt_ip = pkt.get_protocol(ipv4.ipv4)
        if pkt_ip is None:
            return
        pkt_udp = pkt.get_protocol(udp.udp)
        if pkt_udp is None:
            return
        pkt_dhcp = pkt.get_protocol(dhcp.dhcp)
        if pkt_dhcp is None:
            LOG.debug("drop non-dhcp packet")
            return
        pool = self._get_pool()
        if pool is None:
            return
        self._respond_dhcp(datapath, port, pool, pkt_ethernet, pkt_vlan, pkt_ip, pkt_udp, pkt_dhcp)

    def _respond_dhcp(self, datapath, port, pool,
                      pkt_ethernet, pkt_vlan, pkt_ip, pkt_udp, pkt_dhcp):
        # DHCP message type code
        options = dict()
        for option in pkt_dhcp.options.option_list:
            options[option.tag] = option.value
        src = pkt_dhcp.chaddr

        # RESPONSE MSG
        pkt = packet.Packet()
        pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,
                                           dst=pkt_ethernet.src,
                                           src='00:00:00:00:00:00'))
        if pkt_vlan:
            pkt.add_protocol(vlan.vlan(cfi=pkt_vlan.cfi,
                                       ethertype=pkt_vlan.ethertype,
                                       pcp=pkt_vlan.pcp,
                                       vid=pkt_vlan.vid))
        pkt.add_protocol(ipv4.ipv4(src=pkt_ip.dst, dst=pkt_ip.src, proto=in_proto.IPPROTO_UDP))
        pkt.add_protocol(udp.udp(src_port=pkt_udp.dst_port, dst_port=pkt_udp.src_port))

        # DISCOVER MSG
        dhcp_msg_type = ord(options[dhcp.DHCP_MESSAGE_TYPE_OPT])
        if dhcp_msg_type == dhcp.DHCP_DISCOVER:
            msg_type = dhcp.DHCP_OFFER
            if src in self.leases:
                offer = self.leases[src]
                del self.leases[src]
                self.offers[src] = offer
            else:
                offer = self.offers.get(src)
                if offer is None:
                    if len(pool) == 0:
                        LOG.error("Out of IP addresses")
                        msg_type = dhcp.DHCP_NAK

                    offer = pool[0]
                    if dhcp.DHCP_REQUESTED_IP_ADDR_OPT in options:
                        wanted_ip = IPAddr(addrconv.ipv4.bin_to_text(options[dhcp.DHCP_REQUESTED_IP_ADDR_OPT].value))
                        if wanted_ip in pool:
                            offer = wanted_ip
                    pool.remove(offer)
                    self.offers[src] = offer
            wanted_opts = list()
            if dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT in options:
                fmt = "s" * len(options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                wanted_opt_set = struct.unpack(fmt, options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                for i in wanted_opt_set:
                    wanted_opts.append(ord(i))
            option_list = list()
            option_list.append(dhcp.option(dhcp.DHCP_MESSAGE_TYPE_OPT, chr(msg_type), length=1))
            if msg_type is not dhcp.DHCP_NAK:
                if dhcp.DHCP_SUBNET_MASK_OPT in wanted_opts:
                    option_list.append(dhcp.option(dhcp.DHCP_SUBNET_MASK_OPT,
                                                   addrconv.ipv4.text_to_bin(self.subnet.toStr()),
                                                   length=4))
                if dhcp.DHCP_GATEWAY_ADDR_OPT in wanted_opts and self.router_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_GATEWAY_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.router_addr.toStr()),
                                                   length=4))
                if dhcp.DHCP_DNS_SERVER_ADDR_OPT in wanted_opts and self.dns_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_DNS_SERVER_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.dns_addr.toStr()),
                                                   length=4))
                option_list.append(dhcp.option(dhcp.DHCP_IP_ADDR_LEASE_TIME_OPT,
                                               chr(self.lease_time),
                                               length=4))
                option_list.append(dhcp.option(dhcp.DHCP_RENEWAL_TIME_OPT, chr(self.lease_time / 2), length=4))
                option_list.append(dhcp.option(dhcp.DHCP_REBINDING_TIME_OPT, chr(self.lease_time * 7 / 8), length=4))
            resp_options = dhcp.options(option_list=option_list)
            pkt.add_protocol(dhcp.dhcp(op=self._MSG_TYPE_BOOT_REPLY, chaddr=pkt_dhcp.chaddr, options=resp_options,
                                       xid=pkt_dhcp.xid, ciaddr=pkt_dhcp.ciaddr, yiaddr=offer.toStr(),
                                       siaddr=self.ip_addr.toStr(), giaddr='0.0.0.0', sname='', boot_file=''))

        # REQUEST MSG
        if dhcp_msg_type == dhcp.DHCP_REQUEST:
            msg_type = dhcp.DHCP_ACK
            if dhcp.DHCP_REQUESTED_IP_ADDR_OPT not in options:
                return
            wanted_ip = IPAddr(addrconv.ipv4.bin_to_text(options[dhcp.DHCP_REQUESTED_IP_ADDR_OPT]))
            got_ip = None
            if src in self.leases:
                if wanted_ip != self.leases[src]:
                    pool.append(self.leases[src])
                    del self.leases[src]
                else:
                    got_ip = self.leases[src]
            if got_ip is None:
                if src in self.offers:
                    if wanted_ip != self.offers[src]:
                        pool.append(self.offers[src])
                        del self.offers[src]
                    else:
                        got_ip = self.offers[src]
            if got_ip is None:
                if wanted_ip in pool:
                    pool.remove(wanted_ip)
                    got_ip = wanted_ip
            if got_ip is None:
                LOG.warn("%s asked for un-offered %s", src, wanted_ip.toStr())
                msg_type = dhcp.DHCP_NAK
            wanted_opts = list()
            if dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT in options:
                fmt = "s" * len(options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                wanted_opt_set = struct.unpack(fmt, options[dhcp.DHCP_PARAMETER_REQUEST_LIST_OPT])
                for i in wanted_opt_set:
                    wanted_opts.append(ord(i))
            # DHCP options tag code
            option_list = list()
            option_list.append(dhcp.option(dhcp.DHCP_MESSAGE_TYPE_OPT, chr(msg_type), length=1))
            if msg_type is not dhcp.DHCP_NAK:
                if dhcp.DHCP_SUBNET_MASK_OPT in wanted_opts:
                    option_list.append(dhcp.option(dhcp.DHCP_SUBNET_MASK_OPT,
                                                   addrconv.ipv4.text_to_bin(self.subnet.toStr()),
                                                   length=4))
                if dhcp.DHCP_GATEWAY_ADDR_OPT in wanted_opts and self.router_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_GATEWAY_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.router_addr.toStr()),
                                                   length=4))
                if dhcp.DHCP_DNS_SERVER_ADDR_OPT in wanted_opts and self.dns_addr is not None:
                    option_list.append(dhcp.option(dhcp.DHCP_DNS_SERVER_ADDR_OPT,
                                                   addrconv.ipv4.text_to_bin(self.dns_addr.toStr()),
                                                   length=4))
                option_list.append(dhcp.option(dhcp.DHCP_IP_ADDR_LEASE_TIME_OPT,
                                               chr(self.lease_time),
                                               length=4))
                option_list.append(dhcp.option(dhcp.DHCP_RENEWAL_TIME_OPT, chr(self.lease_time / 2), length=4))
                option_list.append(dhcp.option(dhcp.DHCP_REBINDING_TIME_OPT, chr(self.lease_time * 7 / 8), length=4))
            resp_options = dhcp.options(option_list=option_list)
            pkt.add_protocol(dhcp.dhcp(op=self._MSG_TYPE_BOOT_REPLY, chaddr=pkt_dhcp.chaddr, options=resp_options,
                                       xid=pkt_dhcp.xid, ciaddr=pkt_dhcp.ciaddr, yiaddr=wanted_ip.toStr(),
                                       siaddr=self.ip_addr.toStr(), giaddr='0.0.0.0', sname='', boot_file=''))

        # RELEASE MSG
        if dhcp_msg_type == dhcp.DHCP_RELEASE:
            if self.leases.get(src).toStr() != pkt_dhcp.ciaddr:
                LOG.warn("%s tried to release unleased %s" % (src, pkt_dhcp.ciaddr))
                return
            del self.leases[src]
            pool.append(pkt_dhcp.ciaddr)
            LOG.info("%s released %s" % (src, pkt_dhcp.ciaddr))

        self._send_dhcp_reply(datapath, port, pkt)
        return True