From af63e5f51954fe1f080c1804940affce9a5d13cb Mon Sep 17 00:00:00 2001 From: Takeshi Date: Thu, 16 Jul 2015 19:24:33 +0800 Subject: [PATCH] add host discovery functions Signed-off-by: Takeshi Signed-off-by: FUJITA Tomonori --- ryu/topology/api.py | 8 +++ ryu/topology/event.py | 23 +++++++ ryu/topology/switches.py | 139 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 3 deletions(-) diff --git a/ryu/topology/api.py b/ryu/topology/api.py index 7485a8e6..cd72b84b 100644 --- a/ryu/topology/api.py +++ b/ryu/topology/api.py @@ -35,4 +35,12 @@ def get_all_link(app): return get_link(app) +def get_host(app, dpid=None): + rep = app.send_request(event.EventHostRequest(dpid)) + return rep.hosts + + +def get_all_host(app): + return get_host(app) + app_manager.require_app('ryu.topology.switches', api_style=True) diff --git a/ryu/topology/event.py b/ryu/topology/event.py index bd87ab00..83881c00 100644 --- a/ryu/topology/event.py +++ b/ryu/topology/event.py @@ -126,3 +126,26 @@ class EventLinkReply(event.EventReplyBase): def __str__(self): return 'EventLinkReply' % \ (self.dst, self.dpid, len(self.links)) + + +class EventHostRequest(event.EventRequestBase): + # if dpid is None, replay all hosts + def __init__(self, dpid=None): + super(EventHostRequest, self).__init__() + self.dst = 'switches' + self.dpid = dpid + + def __str__(self): + return 'EventHostRequest' % \ + (self.src, self.dpid) + + +class EventHostReply(event.EventReplyBase): + def __init__(self, dst, dpid, hosts): + super(EventHostReply, self).__init__(dst) + self.dpid = dpid + self.hosts = hosts + + def __str__(self): + return 'EventHostReply' % \ + (self.dst, self.dpid, len(self.hosts)) diff --git a/ryu/topology/switches.py b/ryu/topology/switches.py index ba956d15..31b4b0c8 100644 --- a/ryu/topology/switches.py +++ b/ryu/topology/switches.py @@ -29,10 +29,12 @@ from ryu.lib import addrconv, hub from ryu.lib.mac import DONTCARE_STR from ryu.lib.dpid import dpid_to_str, str_to_dpid from ryu.lib.port_no import port_no_to_str -from ryu.lib.packet import packet, ethernet, lldp +from ryu.lib.packet import packet, ethernet +from ryu.lib.packet import lldp, ether_types +from ryu.lib.packet import arp, ipv4, ipv6 from ryu.ofproto.ether import ETH_TYPE_LLDP -from ryu.ofproto import ofproto_v1_0 from ryu.ofproto import nx_match +from ryu.ofproto import ofproto_v1_0 from ryu.ofproto import ofproto_v1_2 from ryu.ofproto import ofproto_v1_3 from ryu.ofproto import ofproto_v1_4 @@ -158,6 +160,68 @@ class Link(object): return 'Link: %s to %s' % (self.src, self.dst) +class Host(object): + # This is data class passed by EventHostXXX + def __init__(self, mac, port): + super(Host, self).__init__() + self.port = port + self.mac = mac + self.ipv4 = [] + self.ipv6 = [] + + def to_dict(self): + d = {'mac': self.mac, + 'ipv4': self.ipv4, + 'ipv6': self.ipv6, + 'port': self.port.to_dict()} + return d + + def __eq__(self, host): + return self.mac == host.mac and self.port == host.port + + def __str__(self): + msg = 'Host Host class + def __init__(self): + super(HostState, self).__init__() + + def add(self, host): + mac = host.mac + self.setdefault(mac, host) + + def update_ip(self, host, ip_v4=None, ip_v6=None): + mac = host.mac + host = None + if mac in self: + host = self[mac] + + if not host: + return + + if ip_v4 != None and ip_v4 not in host.ipv4: + host.ipv4.append(ip_v4) + + if ip_v6 != None and ip_v6 not in host.ipv6: + host.ipv6.append(ip_v6) + + def get_by_dpid(self, dpid): + result = [] + + for mac in self: + host = self[mac] + if host.port.dpid == dpid: + result.append(host) + + return result + + class PortState(dict): # dict: int port_no -> OFPPort port # OFPPort is defined in ryu.ofproto.ofproto_v1_X_parser @@ -451,6 +515,7 @@ class Switches(app_manager.RyuApp): self.port_state = {} # datapath_id => ports self.ports = PortDataState() # Port class -> PortData class self.links = LinkState() # Link class -> timestamp + self.hosts = HostState() # mac address -> Host class list self.is_active = True self.link_discovery = self.CONF.observe_links @@ -518,6 +583,13 @@ class Switches(app_manager.RyuApp): self.send_event_to_observers(event.EventLinkDelete(rev_link)) self.ports.move_front(dst) + def _is_edge_port(self, port): + for link in self.links: + if port == link.src or port == link.dst: + return False + + return True + @set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER]) def state_change_handler(self, ev): @@ -680,7 +752,7 @@ class Switches(app_manager.RyuApp): dp.ofproto.OFP_VERSION) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) - def packet_in_handler(self, ev): + def lldp_packet_in_handler(self, ev): if not self.link_discovery: return @@ -738,6 +810,54 @@ class Switches(app_manager.RyuApp): if self.explicit_drop: self._drop_packet(msg) + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def host_discovery_packet_in_handler(self, ev): + msg = ev.msg + pkt = packet.Packet(msg.data) + eth = pkt.get_protocols(ethernet.ethernet)[0] + + # ignore lldp packet + if eth.ethertype == ETH_TYPE_LLDP: + return + + datapath = msg.datapath + dpid = datapath.id + port_no = -1 + + if msg.datapath.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION: + port_no = msg.in_port + else: + port_no = msg.match['in_port'] + + port = self._get_port(dpid, port_no) + + # can't find this port(ex: logic port) + if not port: + return + # ignore switch-to-switch port + if not self._is_edge_port(port): + return + + host_mac = eth.src + host = Host(host_mac, port) + + # arp packet, update both location and ip + if eth.ethertype == ether_types.ETH_TYPE_ARP: + self.hosts.add(host) + arp_pkt = pkt.get_protocols(arp.arp)[0] + self.hosts.update_ip(host, ip_v4=arp_pkt.src_ip) + + # ipv4 packet, update ip only + elif eth.ethertype == ether_types.ETH_TYPE_IP: + ipv4_pkt = pkt.get_protocols(ipv4.ipv4)[0] + self.hosts.update_ip(host, ip_v4=ipv4_pkt.src) + + # ipv6 packet, update ip only + elif eth.ethertype == ether_types.ETH_TYPE_IPV6: + # TODO: need to handle NDP + ipv6_pkt = pkt.get_protocols(ipv6.ipv6)[0] + self.hosts.update_ip(host, ip_v6=ipv6_pkt.src) + def send_lldp_packet(self, port): try: port_data = self.ports.lldp_sent(port) @@ -862,3 +982,16 @@ class Switches(app_manager.RyuApp): links = [link for link in self.links if link.src.dpid == dpid] rep = event.EventLinkReply(req.src, dpid, links) self.reply_to_request(req, rep) + + @set_ev_cls(event.EventHostRequest) + def host_request_handler(self, req): + dpid = req.dpid + hosts = [] + if dpid is None: + for mac in self.hosts: + hosts.append(self.hosts[mac]) + else: + hosts = self.hosts.get_by_dpid(dpid) + + rep = event.EventHostReply(req.src, dpid, hosts) + self.reply_to_request(req, rep)