# Abgabe von: # Jutta Seth, Janek Schoffit, Eva Hoegner # Problem 1.3 (IP Router for IPv4) from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet from ryu.lib.packet import ethernet from ryu.lib.packet import ether_types from ryu.lib.packet import ipv4 from ryu.lib.packet import icmp from ryu.lib.packet.arp import arp from ryu.lib.packet.packet import Packet # For IP address subnet matching import ipaddress class L3Switch(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] IP_ADDR = ["10.0.2.254", "10.0.2.252", "10.0.1.14", "10.0.3.62", "10.0.4.2"] MAC_ADDR = ["52:00:00:00:00:01", "52:00:00:00:00:02"] def __init__(self, *args, **kwargs): super(L3Switch, self).__init__(*args, **kwargs) self.mac_to_port = {} self.ip_to_mac = {} @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser # install table-miss flow entry # # We specify NO BUFFER to max_len of the output action due to # OVS bug. At this moment, if we specify a lesser number, e.g., # 128, OVS will send Packet-In with invalid buffer_id and # truncated packet data. In that case, we cannot output packets # correctly. The bug has been fixed in OVS v2.1.0. match = parser.OFPMatch() actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0, match, actions) def add_flow(self, datapath, priority, match, actions, buffer_id=None): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] if buffer_id: mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id, priority=priority, match=match, instructions=inst) else: mod = parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst) datapath.send_msg(mod) def send_packet(self, datapath, port, pkt, buffer_id=None): ofproto = datapath.ofproto parser = datapath.ofproto_parser pkt.serialize() self.logger.info("packet-out %s" % (pkt,)) data = pkt.data actions = [parser.OFPActionOutput(port=port)] if buffer_id: out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=data) else: out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=data) datapath.send_msg(out) def do_arp(self, datapath, packet, frame, inPort): dpid = datapath.id ofproto = datapath.ofproto parser = datapath.ofproto_parser macaddr = self.MAC_ADDR[dpid-1] arpPacket = packet.get_protocol(arp) if arpPacket.opcode == 1 : # arp request arp_dstIp = arpPacket.dst_ip self.logger.info('received ARP Request %s => %s (port%d, ip: %s)'%(frame.src, frame.dst, inPort, arp_dstIp)) if arp_dstIp in self.IP_ADDR: # this switch was requested opcode = 2 srcMAC = macaddr srcIP = arp_dstIp dstMAC = frame.src dstIP = arpPacket.src_ip outPort = inPort # learn mac 2 port mapping self.mac_to_port[dpid][dstMAC] = inPort # learn ip 2 mac mapping self.ip_to_mac[dpid][dstIP] = dstMAC self.logger.info("send ARP reply %s => %s (port%d)" %(srcMAC, dstMAC, outPort)) else: if arpPacket.dst_ip in self.ip_to_mac[dpid]: # optimization: the switch already knows the mapping and can answer the request opcode = 2 srcMAC = self.ip_to_mac[dpid][arpPacket.dst_ip] srcIP = arpPacket.dst_ip dstMAC = frame.src dstIP = arpPacket.src_ip outPort = self.mac_to_port[dpid][dstMAC] self.logger.info("optimization: answer ARP request %s => %s (port%d)" %(srcMAC, dstMAC, outPort)) else: # forward arp request opcode = 1 srcMAC = frame.src srcIP = arpPacket.src_ip dstMAC = frame.dst dstIP = arpPacket.dst_ip outPort = ofproto.OFPP_FLOOD # learn mac 2 port mapping self.mac_to_port[dpid][srcMAC] = inPort # learn ip 2 mac mapping self.ip_to_mac[dpid][srcIP] = srcMAC self.logger.info("froward ARP request %s => %s (port%d)" %(srcMAC, dstMAC, outPort)) elif arpPacket.opcode == 2 : self.logger.info(arpPacket) if arpPacket.dst_mac in self.MAC_ADDR: # learn mac 2 port mapping self.mac_to_port[dpid][arpPacket.src_mac] = inPort # learn ip 2 mac mapping self.ip_to_mac[dpid][arpPacket.src_ip] = arpPacket.src_mac return else: opcode = 2 #arp reply # forward arp reply srcMAC = frame.src srcIP = arpPacket.src_ip dstMAC = frame.dst dstIP = arpPacket.dst_ip outPort = self.mac_to_port[dpid][dstMAC] # learn mac 2 port mapping self.mac_to_port[dpid][srcMAC] = inPort # learn ip 2 mac mapping self.ip_to_mac[dpid][srcIP] = srcMAC self.logger.debug('forward ARP reply %s => %s (port%d)'%(frame.src ,frame.dst, inPort)) self.send_arp(datapath, opcode, srcMAC, srcIP, dstMAC, dstIP, outPort) def send_arp(self, datapath, opcode, srcMac, srcIp, dstMac, dstIp, outPort): if opcode == 1: targetMac = "FF:FF:FF:FF:FF:FF" targetIp = dstIp elif opcode == 2: targetMac = dstMac targetIp = dstIp e = ethernet.ethernet(dstMac, srcMac, ether_types.ETH_TYPE_ARP) a = arp(1, 0x0800, 6, 4, opcode, srcMac, srcIp, targetMac, targetIp) p = Packet() p.add_protocol(e) p.add_protocol(a) self.send_packet(datapath, outPort, p) def do_icmp(self, datapath, port, pkt_ethernet, pkt_ipv4, pkt_icmp): self.logger.info(pkt_icmp.type) self.logger.info(icmp.ICMP_ECHO_REQUEST) if pkt_icmp.type != icmp.ICMP_ECHO_REQUEST: return pkt = packet.Packet() pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype, dst=pkt_ethernet.src, src=self.MAC_ADDR[datapath.id-1])) pkt.add_protocol(ipv4.ipv4(dst=pkt_ipv4.src, src=pkt_ipv4.dst, proto=pkt_ipv4.proto)) pkt.add_protocol(icmp.icmp(type_=icmp.ICMP_ECHO_REPLY, code=icmp.ICMP_ECHO_REPLY_CODE, csum=0, data=pkt_icmp.data)) self.logger.info("do icmp: %s" %(pkt,)) self.logger.info("port: %s" %(port)) self.send_packet(datapath, port, pkt) def do_l2_switch(self, datapath, dpid, packet, frame, in_port, buffer_id=None): ofproto = datapath.ofproto parser = datapath.ofproto_parser if frame.dst in self.mac_to_port[dpid]: out_port = self.mac_to_port[dpid][frame.dst] else: out_port = ofproto.OFPP_FLOOD actions = [parser.OFPActionOutput(out_port)] # install a flow to avoid packet_in next time if out_port != ofproto.OFPP_FLOOD: match = parser.OFPMatch(in_port=in_port, eth_dst=frame.dst) # verify if we have a valid buffer_id, if yes avoid to send both # flow_mod & packet_out if buffer_id != ofproto.OFP_NO_BUFFER: self.add_flow(datapath, 1, match, actions, buffer_id) return else: self.add_flow(datapath, 1, match, actions) data = None if buffer_id == ofproto.OFP_NO_BUFFER: data = packet.data out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=in_port, actions=actions, data=data) datapath.send_msg(out) def do_l3_switch(self, datapath, dpid, packet, frame, ipv4_pkt, in_port, buffer_id=None): ofproto = datapath.ofproto parser = datapath.ofproto_parser if ipv4_pkt.dst in self.ip_to_mac[dpid]: # Get mac and port of the given destination dstMac = self.ip_to_mac[dpid][ipv4_pkt.dst] out_port = self.mac_to_port[dpid][dstMac] else: # This flow is for the second switch if the address is in range 10.0.0.0-10.0.3.255 # The ipaddress module assumes an IP adress as a unicode string (python 2.7 default for a string is ascii encoding) if datapath.id == 2 and ipaddress.ip_address(unicode(ipv4_pkt.dst)) in ipaddress.ip_network(unicode("10.0.0.0/22")): actions = [parser.OFPActionDecNwTtl(), #decrease TTL count parser.OFPActionSetField(eth_src=self.MAC_ADDR[1]), # set own mac as source parser.OFPActionSetField(eth_dst=self.MAC_ADDR[0]), # set dst switch parser.OFPActionOutput(2)] match = parser.OFPMatch(ipv4_dst=("10.0.0.0", "255.255.252.0"), eth_dst=self.MAC_ADDR[1], eth_type=0x0800) self.add_flow(datapath, 1, match, actions) data = None if buffer_id == ofproto.OFP_NO_BUFFER: data = packet.data out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=in_port, actions=actions, data=data) datapath.send_msg(out) return # Add a flow for the first switch if the ipadress matches network 4. if datapath.id == 1 and ipaddress.ip_address(unicode(ipv4_pkt.dst)) in ipaddress.ip_network(unicode("10.0.4.0/30")): actions = [parser.OFPActionDecNwTtl(), #decrease TTL count parser.OFPActionSetField(eth_src=self.MAC_ADDR[0]), # set own mac as source parser.OFPActionSetField(eth_dst=self.MAC_ADDR[1]), # set dst switch parser.OFPActionOutput(1)] match = parser.OFPMatch(ipv4_dst=("10.0.4.0", "255.255.255.252"), eth_dst=self.MAC_ADDR[0], eth_type=0x0800) self.add_flow(datapath, 1, match, actions) data = None if buffer_id == ofproto.OFP_NO_BUFFER: data = packet.data out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=in_port, actions=actions, data=data) datapath.send_msg(out) return # Send an arp request and define mac and port # so we're able to apply the following actions # on the packet. out_port = ofproto.OFPP_FLOOD dstMac = "FF:FF:FF:FF:FF:FF" self.send_arp(datapath, 1, self.MAC_ADDR[datapath.id-1], ipv4_pkt.src, dstMac, ipv4_pkt.dst, out_port) # Actions are applied in the same order as defined in the list. # OFPActionOutput terminates the chain. So this call should be the last action. actions = [parser.OFPActionDecNwTtl(), #decrease TTL count parser.OFPActionSetField(eth_src=self.MAC_ADDR[datapath.id-1]), # set own mac as source parser.OFPActionSetField(eth_dst=dstMac), # set dst from arp request parser.OFPActionOutput(out_port)] # forward packet through out_port # Only install a flow if we#re in the first case (specific mac and out_port) if ipv4_pkt.dst in self.ip_to_mac[dpid]: # install a flow to avoid packet_in next time match = parser.OFPMatch(in_port=in_port, ipv4_dst=ipv4_pkt.dst, eth_type=0x0800) #onlz match ipv4 packets # verify if we have a valid buffer_id, if yes avoid to send both # flow_mod & packet_out if buffer_id != ofproto.OFP_NO_BUFFER: self.add_flow(datapath, 1, match, actions, buffer_id) return else: self.add_flow(datapath, 1, match, actions) data = None if buffer_id == ofproto.OFP_NO_BUFFER: data = packet.data out = parser.OFPPacketOut(datapath=datapath, buffer_id=buffer_id, in_port=in_port, actions=actions, data=data) datapath.send_msg(out) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): # If you hit this you might want to increase # the "miss_send_length" of your switch if ev.msg.msg_len < ev.msg.total_len: self.logger.debug("packet truncated: only %s of %s bytes", ev.msg.msg_len, ev.msg.total_len) msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] if eth.ethertype == ether_types.ETH_TYPE_LLDP: # ignore lldp packet return dst = eth.dst src = eth.src dpid = datapath.id self.mac_to_port.setdefault(dpid, {}) self.ip_to_mac.setdefault(dpid, {}) self.logger.info("packet in dpid: %s, src: %s, dest: %s, in_port: %s", dpid, src, dst, in_port) #self.logger.info(pkt) #learn mac to port mapping if src not in self.mac_to_port[dpid]: self.mac_to_port[dpid][src] = in_port if eth.ethertype == ether_types.ETH_TYPE_ARP and src not in self.MAC_ADDR: self.do_arp(datapath, pkt, eth, in_port) return if eth.ethertype == ether_types.ETH_TYPE_IP: ipv4_pkt = pkt.get_protocol(ipv4.ipv4) self.logger.info("IP src: %s, dst: %s", ipv4_pkt.src, ipv4_pkt.dst) #check if packet is for this switch if ipv4_pkt.dst in self.IP_ADDR: icmp_pkt = pkt.get_protocol(icmp.icmp) if icmp_pkt: self.do_icmp(datapath, in_port, eth, ipv4_pkt, icmp_pkt) else: # forward the package into a different subnet self.do_l3_switch(datapath, dpid, pkt, eth, ipv4_pkt, in_port, msg.buffer_id) # packet is not for this switch, so do l2 switching if dst != self.MAC_ADDR[dpid-1]: self.do_l2_switch(datapath, dpid, pkt, eth, in_port, msg.buffer_id) return return