Skip to content

Instantly share code, notes, and snippets.

@aishusreeni
Forked from smihir/router.py
Created March 14, 2018 20:14
Show Gist options
  • Select an option

  • Save aishusreeni/4c62734a020255d7e46d1d892323e799 to your computer and use it in GitHub Desktop.

Select an option

Save aishusreeni/4c62734a020255d7e46d1d892323e799 to your computer and use it in GitHub Desktop.

Revisions

  1. @smihir smihir created this gist Dec 21, 2015.
    288 changes: 288 additions & 0 deletions router.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,288 @@
    #!/usr/bin/env python3

    '''
    Basic IPv4 router (static routing) in Python.
    '''

    import sys
    import os
    import time
    from switchyard.lib.packet import *
    from switchyard.lib.address import *
    from switchyard.lib.common import *

    class Router(object):

    _forwarding_table_file = "forwarding_table.txt"

    def __init__(self, net):
    self.net = net
    self.last_to = 0
    # list of dictionaries
    self.forwarding_table = list()
    #dict with ip address as key
    self.arp_table = dict()
    self.arp_waitlist = dict()
    self.queue = list()
    self.create_forwarding_table()

    def create_forwarding_table(self):
    with open(self._forwarding_table_file) as f:
    for line in f:
    linearray = line.strip('\n').split(' ')
    if len(linearray) != 4:
    log_fatal("incorrect format")
    continue
    fwtable_entry = dict()
    fwtable_entry['netaddr'] = linearray[0]
    fwtable_entry['netmask'] = linearray[1]
    fwtable_entry['nexthop'] = linearray[2]
    fwtable_entry['intfname'] = linearray[3]
    fwtable_entry['prefixlen'] = IPv4Network(fwtable_entry['netaddr'] + '/' + fwtable_entry['netmask']).prefixlen
    self.forwarding_table.append(fwtable_entry)
    for i in self.net.interfaces():
    fwtable_entry = dict()
    # should be ip & netmask
    fwtable_entry['netaddr'] = str(IPv4Address(int(IPv4Address(i.ipaddr)) & int(IPv4Address(i.netmask))))
    fwtable_entry['netmask'] = str(i.netmask)
    #fwtable_entry['nexthop'] = str(i.ipaddr) # fix this as well, should be NULL
    fwtable_entry['intfname'] = i.name
    fwtable_entry['prefixlen'] = IPv4Network(fwtable_entry['netaddr'] + '/' + fwtable_entry['netmask']).prefixlen
    self.forwarding_table.append(fwtable_entry)
    self.forwarding_table.sort(key = lambda x:x['prefixlen'], reverse = True)
    log_debug(self.forwarding_table)

    def get_tx_dev(self, dstip):
    dstaddr = IPv4Address(dstip)
    for e in self.forwarding_table:
    prefixnet = IPv4Network(str(e['netaddr']) + '/' + str(e['prefixlen']))
    #print("prefixnet {}, dstip {}".format(prefixnet, dstip))
    if dstaddr in prefixnet:
    return e['intfname']
    return None

    def get_next_hop(self, dstip):
    dstaddr = IPv4Address(dstip)
    for e in self.forwarding_table:
    prefixnet = IPv4Network(str(e['netaddr']) + '/' + str(e['prefixlen']))
    #print("prefixnet {}, dstip {}".format(prefixnet, dstip))
    if dstaddr in prefixnet:
    if 'nexthop' in e:
    return e['nexthop']
    else:
    return dstip
    return None

    def get_dst_mac(self, ip):
    if ip in self.arp_table:
    return self.arp_table[ip]
    return None

    def add_arp_entry(self, ip, mac):
    self.arp_table[ip] = mac

    def process_queue_for_mac(self, ip, mac):
    log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
    log_debug("queue: {}".format(str(self.queue)))
    delqueue = list()
    for e in self.queue:
    if e['ip'] == ip:
    #send
    tx_pkt = self.transform_ipv4_pkt(e['outdev'], e['pkt'], mac)
    log_debug("Send packet({}): {}".format(e['outdev'], str(tx_pkt)))
    self.net.send_packet(e['outdev'], tx_pkt)
    #delete.from queue
    #self.queue.remove(e)
    delqueue.append(e)
    log_debug("queue: {}".format(str(self.queue)))
    for i in delqueue:
    self.queue.remove(i)

    #delete from the set
    print(self.arp_waitlist)
    self.arp_waitlist.pop(ip, 0)

    def create_arp_waitlist(self, ip):
    if not ip in self.arp_waitlist:
    self.arp_waitlist[ip] = 0

    def queue_pkt(self, pkt, out_dev, next_hop):
    node = dict()
    node['ip'] = next_hop
    node['pkt'] = pkt
    node['outdev'] = out_dev
    self.queue.append(node)

    def transform_ipv4_pkt(self, tx_dev, pkt, dst_mac):
    pkt[0].src = self.net.interface_by_name(tx_dev).ethaddr
    pkt[0].dst = dst_mac
    return pkt

    def process_pending_packets(self):
    t = time.time()
    if t < self.last_to + 1:
    return
    self.last_to = t
    log_debug("{} process_pending_packets".format(str(t)))
    log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
    log_debug("queue: {}".format(str(self.queue)))

    poplist = list()

    for key, val in self.arp_waitlist.items():
    self.arp_waitlist[key] += 1
    if self.arp_waitlist[key] >= 5:
    poplist.append(key)
    delqueue = list()
    for e in self.queue:
    if key == e['ip']:
    log_debug("{} removing entry from queue {}".format(str(t), str(e)))
    #self.queue.remove(e)
    delqueue.append(e)
    for i in delqueue:
    self.queue.remove(i)
    else:
    devname = None
    # send arp request
    for e in self.queue:
    if key == e['ip']:
    devname = e['outdev']
    break
    if devname != None:
    tx_dev = self.net.interface_by_name(devname)
    tx_pkt = create_ip_arp_request(tx_dev.ethaddr, tx_dev.ipaddr, e['ip'])
    log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
    self.net.send_packet(devname, tx_pkt)
    else:
    log_debug("process_pending_packets: outdev is None!")

    for ip in poplist:
    self.arp_waitlist.pop(ip, 0)
    log_debug("arp_waitlist: {}".format(str(self.arp_waitlist)))
    log_debug("queue: {}".format(str(self.queue)))

    def router_main(self):
    '''
    Main method for router; we stay in a loop in this method, receiving
    packets until the end of time.
    '''
    while True:
    gotpkt = True
    try:
    dev,pkt = self.net.recv_packet(timeout=1.0)
    except NoPackets:
    self.process_pending_packets()
    log_debug("No packets available in recv_packet")
    gotpkt = False
    #if no ARP response for 1 sec, retry ARP for 5 times then timeout(i.e drop current packet)
    except Shutdown:
    log_debug("Got shutdown signal")
    break

    if gotpkt:
    log_debug("Got a packet: {}".format(str(pkt)))
    arp = pkt.get_header(Arp)
    if arp and arp.operation == ArpOperation.Request:
    # check if it is for us
    try:
    intf = self.net.interface_by_ipaddr(arp.targetprotoaddr)
    #if we are here it means ARP resquest is for us
    tx_pkt = create_ip_arp_reply(intf.ethaddr, arp.senderhwaddr, arp.targetprotoaddr, arp.senderprotoaddr)
    log_debug("Send packet({}): {}".format(dev, str(tx_pkt)))
    self.net.send_packet(dev, tx_pkt)
    except SwitchyException:
    log_debug("Rx Arp not for us packet({}): {}".format(dev, str(tx_pkt)))
    finally:
    self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
    self.process_pending_packets()
    continue

    if arp and arp.operation == ArpOperation.Reply:
    try:
    intf = self.net.interface_by_ipaddr(arp.targetprotoaddr)
    #if we are here it means ARP response is for us
    '''
    self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
    self.process_queue_for_mac(str(arp.senderprotoaddr), str(arp.senderhwaddr))
    self.process_pending_packets()
    continue
    '''
    except SwitchyException:
    log_debug("Arp reply not for us, ip({})".format(arp.targetprotoaddr))
    finally:
    self.add_arp_entry(str(arp.senderprotoaddr), str(arp.senderhwaddr))
    self.process_queue_for_mac(str(arp.senderprotoaddr), str(arp.senderhwaddr))
    self.process_pending_packets()
    continue

    self.process_pending_packets()

    ipv4 = pkt.get_header(IPv4)
    if ipv4:
    pkt[1].ttl -= 1
    log_debug("ipv4 dst {} ip({})".format(dev, ipv4.dst))
    try:
    #if for self drop
    intf = self.net.interface_by_ipaddr(ipv4.dst)
    if intf != None:
    log_debug("packet for self, drop ({}): {}".format(dev, str(pkt)))
    continue
    except SwitchyException:
    #check in forwarding table for match with longest prefix
    tx_dev = self.get_tx_dev(ipv4.dst)
    if (tx_dev == None):
    #if not found drop
    log_debug("no tx_dev found, drop ({})".format(str(pkt)))
    continue

    log_debug("tx_dev {} found, process packet({})".format(tx_dev, str(pkt)))
    next_hop = self.get_next_hop(ipv4.dst)
    if (next_hop == None):
    next_hop = ipv4.dst

    #if found, check if the arp entry for next hop is present in ARP table
    dst_mac = self.get_dst_mac(str(next_hop))
    log_debug("Next Hop {} Dst mac".format(str(next_hop), str(dst_mac)))

    #if entry reconstruct ethernet header and send
    if (dst_mac != None):
    log_debug("Entry found in ARP table {} process packet({})".format(dst_mac, str(pkt)))
    tx_pkt = self.transform_ipv4_pkt(tx_dev, pkt, dst_mac)
    log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
    self.net.send_packet(tx_dev, tx_pkt)

    #if no entry, then check in per interface queue if ARP request is sent
    else:
    log_debug("Entry not found in ARP table, process packet({})".format(str(pkt)))
    log_debug("Arp table {}".format(str(self.arp_table)))
    if not str(ipv4.dst) in self.arp_waitlist:
    log_debug("Entry not found in ARP waitlist, create entry and add to q({})".format(str(self.queue)))
    self.create_arp_waitlist(str(next_hop))
    self.queue_pkt(pkt, tx_dev, str(next_hop))
    log_debug("q({})".format(str(self.queue)))

    src_mac = self.net.interface_by_name(tx_dev).ethaddr
    src_ip = self.net.interface_by_name(tx_dev).ipaddr
    tx_pkt = create_ip_arp_request(src_mac, src_ip, next_hop)
    log_debug("Send packet({}): {}".format(tx_dev, str(tx_pkt)))
    self.net.send_packet(tx_dev, tx_pkt, str(next_hop))
    else:
    log_debug("Entry found in ARP waitlist, create entry and add to q({})".format(str(self.queue)))
    self.queue_pkt(pkt, tx_dev, str(next_hop))
    log_debug("q({})".format(str(self.queue)))

    #if sent put in queue
    #if not sent create a new queue and send arp
    #if arp response reconstruct ethernet header and send
    continue



    def switchy_main(net):
    '''
    Main entry point for router. Just create Router
    object and get it going.
    '''
    r = Router(net)
    r.router_main()
    net.shutdown()