|
|
@@ -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() |