184 lines
6.5 KiB
Python
184 lines
6.5 KiB
Python
# Copyright (c) 2021 China Unicom Cloud Data Co.,Ltd.
|
|
# Copyright (c) 2019 - 2020 China Telecom Corporation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import math
|
|
import struct
|
|
|
|
import netaddr
|
|
from neutron_lib.api import converters
|
|
from os_ken.lib import addrconv
|
|
from os_ken.lib.packet import dhcp
|
|
from os_ken.lib.packet import dhcp6
|
|
from os_ken.lib.packet import ethernet
|
|
from os_ken.lib.packet import ipv4
|
|
from os_ken.lib.packet import ipv6
|
|
from os_ken.lib.packet import packet
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
|
|
import base_oskenapp
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
IPV4_STR = "v4"
|
|
IPV6_STR = "v6"
|
|
|
|
|
|
class DHCPResponderBase(base_oskenapp.BaseNeutronAgentOSKenApp):
|
|
|
|
def __init__(self, agent_api, ext_api, version=IPV4_STR, *args, **kwargs):
|
|
super(DHCPResponderBase, self).__init__(*args, **kwargs)
|
|
self.agent_api = agent_api
|
|
self.int_br = self.agent_api.request_int_br()
|
|
self.ext_api = ext_api
|
|
self.version = version
|
|
self.name = "DHCP%sResponder" % version
|
|
|
|
self.hw_addr = converters.convert_to_sanitized_mac_address(
|
|
cfg.CONF.base_mac)
|
|
self.register_packet_in_handler(self._packet_in_handler)
|
|
|
|
def _packet_in_handler(self, event):
|
|
msg = event.msg
|
|
datapath = msg.datapath
|
|
ofproto = datapath.ofproto
|
|
|
|
if msg.reason != ofproto.OFPR_ACTION:
|
|
LOG.debug("DHCP Controller only handle the packet which "
|
|
"match the rules and the action is send to the "
|
|
"controller.")
|
|
return
|
|
|
|
of_in_port = msg.match['in_port']
|
|
LOG.info("DHCP Controller packet in OF port: %s", of_in_port)
|
|
pkt = packet.Packet(data=msg.data)
|
|
|
|
LOG.debug('DHCP Controller packet received: '
|
|
'buffer_id=%x total_len=%d reason=ACTION '
|
|
'table_id=%d cookie=%d match=%s pkt=%s',
|
|
msg.buffer_id, msg.total_len,
|
|
msg.table_id, msg.cookie, msg.match,
|
|
pkt)
|
|
|
|
if self.version == IPV4_STR:
|
|
ip_protocol = ipv4.ipv4
|
|
dhcp_protocol = dhcp.dhcp
|
|
else:
|
|
ip_protocol = ipv6.ipv6
|
|
dhcp_protocol = dhcp6.dhcp6
|
|
ip_header = pkt.get_protocol(ip_protocol)
|
|
if not ip_header:
|
|
LOG.warning("DHCP Controller received packet "
|
|
"is not an IP%s packet",
|
|
self.version)
|
|
return
|
|
|
|
dhcp_pkt = pkt.get_protocol(dhcp_protocol)
|
|
if not dhcp_pkt:
|
|
LOG.warning("DHCP Controller received packet "
|
|
"is not a DHCP%s packet",
|
|
self.version)
|
|
return
|
|
|
|
eth_pkt = pkt.get_protocol(ethernet.ethernet)
|
|
port_id = self.get_port_id_from_br(of_in_port, eth_pkt.src)
|
|
LOG.debug("DHCP Controller received DHCP%s packet's neutron port "
|
|
"id: %s", self.version, port_id)
|
|
|
|
port_info = self.ext_api.get_port_info(port_id)
|
|
LOG.debug("DHCP Controller received DHCP%s packet's neutron port "
|
|
"info: %s", self.version, port_info)
|
|
if not port_info:
|
|
return
|
|
|
|
self.handle_dhcp(datapath, of_in_port, pkt, port_info)
|
|
|
|
def get_bin_dns(self, dns_nameservers):
|
|
if self.version == IPV4_STR:
|
|
text_protocol = addrconv.ipv4
|
|
else:
|
|
text_protocol = addrconv.ipv6
|
|
|
|
dns_bin = b''
|
|
for dns in dns_nameservers:
|
|
dns_bin += text_protocol.text_to_bin(dns['address'])
|
|
return dns_bin
|
|
|
|
def get_bin_route(self, destination, nexthop):
|
|
if self.version == IPV4_STR:
|
|
text_protocol = addrconv.ipv4
|
|
else:
|
|
text_protocol = addrconv.ipv6
|
|
|
|
bin_route = b''
|
|
net = netaddr.IPNetwork(str(destination))
|
|
dest = str(net.ip)
|
|
mask = net.prefixlen
|
|
bin_route += struct.pack('B', mask)
|
|
bin_addr = text_protocol.text_to_bin(dest)
|
|
dest_len = int(math.ceil(mask / 8.0))
|
|
bin_route += bin_addr[:dest_len]
|
|
bin_route += text_protocol.text_to_bin(nexthop)
|
|
return bin_route
|
|
|
|
def get_port_id_from_br(self, ofport, vif_mac):
|
|
vifs = self.int_br.get_vif_ports()
|
|
for vif in vifs:
|
|
if vif.ofport == ofport and vif.vif_mac == vif_mac:
|
|
return vif.vif_id
|
|
|
|
def get_port_ip(self, port_info, ip_version):
|
|
fixed_ips = port_info['fixed_ips']
|
|
for ip in fixed_ips:
|
|
ipaddr = netaddr.IPNetwork(ip['ip_address'])
|
|
# For the first IP only, secondary IPs will not be returned.
|
|
if ipaddr.version == ip_version:
|
|
return ip
|
|
|
|
def get_dhcp_options(self, port_info, is_ack):
|
|
raise NotImplementedError()
|
|
|
|
def get_ret_packet(self, packet_in, port_info, is_ack=False):
|
|
raise NotImplementedError()
|
|
|
|
def get_state(self, pkt_dhcp):
|
|
raise NotImplementedError()
|
|
|
|
def handle_dhcp(self, datapath, ofport, pkt, port_info):
|
|
raise NotImplementedError()
|
|
|
|
def packet_out(self, datapath, ofport, pkt):
|
|
if not pkt:
|
|
LOG.debug("DHCP Controller no packet assembled for DHCP%s.",
|
|
self.version)
|
|
return
|
|
ofproto = datapath.ofproto
|
|
parser = datapath.ofproto_parser
|
|
pkt.serialize()
|
|
LOG.debug("DHCP Controller packet assembled for DHCP%s %s",
|
|
self.version, (pkt,))
|
|
data = pkt.data
|
|
actions = [parser.OFPActionOutput(port=ofport)]
|
|
out = parser.OFPPacketOut(datapath=datapath,
|
|
buffer_id=ofproto.OFP_NO_BUFFER,
|
|
in_port=ofproto.OFPP_CONTROLLER,
|
|
actions=actions,
|
|
data=data)
|
|
LOG.info("DHCP Controller assembled DHCP%s packet out to OF port %s",
|
|
self.version, ofport)
|
|
datapath.send_msg(out)
|