409 lines
18 KiB
Python
409 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
|
||
# -*- coding: UTF-8 -*-
|
||
|
||
######
|
||
import argparse
|
||
import socket
|
||
import os
|
||
import sys
|
||
import struct
|
||
import time
|
||
|
||
|
||
def setupArgumentParser() -> argparse.Namespace:
|
||
parser = argparse.ArgumentParser(
|
||
description='A collection of Network Applications developed for SCC.203.')
|
||
parser.set_defaults(func=ICMPPing, hostname='lancaster.ac.uk')
|
||
subparsers = parser.add_subparsers(help='sub-command help')
|
||
|
||
parser_p = subparsers.add_parser(
|
||
'ping', aliases=['p'], help='run ping')
|
||
parser_p.add_argument('hostname', type=str,
|
||
help='host to ping towards')
|
||
parser_p.add_argument('count', nargs='?', type=int,
|
||
help='number of times to ping the host before stopping')
|
||
parser_p.add_argument('timeout', nargs='?',
|
||
type=int,
|
||
help='maximum timeout before considering request lost')
|
||
parser_p.set_defaults(func=ICMPPing)
|
||
|
||
parser_t = subparsers.add_parser('traceroute', aliases=['t'],
|
||
help='run traceroute')
|
||
parser_t.add_argument('hostname', type=str,
|
||
help='host to traceroute towards')
|
||
parser_t.add_argument('timeout', nargs='?', type=int,
|
||
help='maximum timeout before considering request lost')
|
||
parser_t.add_argument('protocol', nargs='?', type=str,
|
||
help='protocol to send request with (UDP/ICMP)')
|
||
parser_t.set_defaults(func=Traceroute)
|
||
|
||
parser_w = subparsers.add_parser(
|
||
'web', aliases=['w'], help='run web server')
|
||
parser_w.set_defaults(port=8080)
|
||
parser_w.add_argument('port', type=int, nargs='?',
|
||
help='port number to start web server listening on')
|
||
parser_w.set_defaults(func=WebServer)
|
||
|
||
parser_x = subparsers.add_parser(
|
||
'proxy', aliases=['x'], help='run proxy')
|
||
parser_x.set_defaults(port=8000)
|
||
parser_x.add_argument('port', type=int, nargs='?',
|
||
help='port number to start web server listening on')
|
||
parser_x.set_defaults(func=Proxy)
|
||
|
||
args = parser.parse_args()
|
||
return args
|
||
|
||
|
||
class NetworkApplication:
|
||
|
||
def checksum(self, dataToChecksum: str) -> str:
|
||
csum = 0
|
||
countTo = (len(dataToChecksum) // 2) * 2
|
||
count = 0
|
||
|
||
while count < countTo:
|
||
thisVal = dataToChecksum[count+1] * 256 + dataToChecksum[count]
|
||
csum = csum + thisVal
|
||
csum = csum & 0xffffffff
|
||
count = count + 2
|
||
|
||
if countTo < len(dataToChecksum):
|
||
csum = csum + dataToChecksum[len(dataToChecksum) - 1]
|
||
csum = csum & 0xffffffff
|
||
|
||
csum = (csum >> 16) + (csum & 0xffff)
|
||
csum = csum + (csum >> 16)
|
||
answer = ~csum
|
||
answer = answer & 0xffff
|
||
answer = answer >> 8 | (answer << 8 & 0xff00)
|
||
answer = socket.htons(answer)
|
||
return answer
|
||
|
||
def printOneResult(self, destinationAddress: str, packetLength: int, time: float, ttl: int, destinationHostname=''):
|
||
|
||
if destinationHostname:
|
||
print("%d bytes from %s (%s):ttl=%d time=%.2f ms" % (
|
||
packetLength, destinationHostname, destinationAddress, ttl, time))
|
||
else:
|
||
print("%d bytes from %s: ttl=%dtime=%.2f ms" %
|
||
(packetLength, destinationAddress, ttl, time))
|
||
|
||
def printAdditionalDetails(self, packetLoss=0.0, minimumDelay=0.0, averageDelay=0.0, maximumDelay=0.0):
|
||
print("%.2f%% packet loss" % (packetLoss))
|
||
if minimumDelay > 0 and averageDelay > 0 and maximumDelay > 0:
|
||
print("rtt min/avg/max = %.2f/%.2f/%.2fms" %
|
||
(minimumDelay, averageDelay, maximumDelay))
|
||
|
||
|
||
class ICMPPing(NetworkApplication):
|
||
|
||
def receiveOnePing(self, icmpSocket, destinationAddress, ID, timeout):
|
||
|
||
# 1. Wait for the socket to receive a reply. #2. Once received, record time of receipt, otherwise, handle a timeout
|
||
try:
|
||
timeRecieved = time.time()
|
||
information, address = icmpSocket.recvfrom(1024)
|
||
timeSent = information.split()[2]
|
||
|
||
# 3. Compare the time of receipt to time of sending, producing the total network delay
|
||
timeSent= self.sendOnePing(icmpSocket, destinationAddress, 111)
|
||
totalNetworkDelay = (timeRecieved*1000) - timeSent
|
||
|
||
# 4. Unpack the packet header for useful information, including the ID
|
||
icmpType, icmpCode, icmpChecksum, icmpPacketID, icmpSeqNumber = struct.unpack("bbHHh", icmpHeader)
|
||
|
||
# 5. Check that the ID matches between the request and reply AND THEN 6. Return total network delay
|
||
if(icmpPacketID == self.ID):
|
||
return totalNetworkDelay
|
||
|
||
else:
|
||
return 0
|
||
|
||
except timeout: # No response received, print the timeout message
|
||
print("Request timed out")
|
||
|
||
|
||
|
||
def sendOnePing(self, icmpSocket, destinationAddress, ID):
|
||
# 1. Build ICMP header
|
||
icmpHeader=struct.pack("bbHHh", 8, 0, 0, ID, 1)
|
||
|
||
# 2. Checksum ICMP packet using given function
|
||
icmpChecksum = self.checksum(icmpHeader)
|
||
|
||
# 3. Insert checksum into packet
|
||
icmpHeader = struct.pack("bbHHh", 8, 0, icmpChecksum, ID, 1)
|
||
|
||
# 4. Send packet using socket- double check this //run with wireshark
|
||
icmpSocket.sendto(icmpHeader, (destinationAddress, 1))
|
||
|
||
# 5. Record time of sending
|
||
timeSent=time.time()
|
||
return timeSent
|
||
|
||
def doOnePing(self, destinationAddress, timeout):
|
||
# 1. Create ICMP socket
|
||
# Translate an Internet protocol name (for example, 'icmp') to a constant suitable for passing as the (optional) third argument to the socket() function.
|
||
icmp_proto = socket.getprotobyname("icmp") #debugging
|
||
icmpSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp_proto)
|
||
#icmpSocket = socket.socket(socket.AF_INET,socket.SOCK_RAW, socket.IPPROTO_ICMP)
|
||
|
||
# 2. Call sendOnePing function
|
||
timeSent = self.sendOnePing(icmpSocket, destinationAddress, 111)
|
||
|
||
# 3. Call receiveOnePing function
|
||
networkDelay = self.receiveOnePing(icmpSocket, destinationAddress, 111, 1000, timeSent)
|
||
|
||
# 4. Close ICMP socket
|
||
icmpSocket.close()
|
||
|
||
# 5. Return total network delay
|
||
return networkDelay
|
||
|
||
def __init__(self, args):
|
||
print('Ping to: %s...' % (args.hostname))
|
||
# 1. Look up hostname, resolving it to an IP address
|
||
ipAddress = socket.gethostbyname(args.hostname)
|
||
|
||
# 2. Call doOnePing function approximately every second
|
||
while True:
|
||
time.sleep(1)
|
||
debuggingTimeout = args.timeout
|
||
print("testing:", ipAddress, debuggingTimeout)
|
||
returnedDelay = self.doOnePing(ipAddress, debuggingTimeout)
|
||
# 3. Print out the returned delay (and other relevant details) using the printOneResult method
|
||
self.printOneResult(ipAddress, 50, returnedDelay, 150)
|
||
#Example use of printOneResult - complete as appropriate
|
||
# 4. Continue this process until stopped - did this through the while True
|
||
|
||
|
||
class Traceroute(NetworkApplication):
|
||
|
||
def __init__(self, args):
|
||
#
|
||
# Please ensure you print each result using the printOneResult method!
|
||
print('Traceroute to: %s...' % (args.hostname))
|
||
# 1. Look up hostname, resolving it to an IP address
|
||
ipAddress= socket.gethostbyname(args.hostname)
|
||
numberofNodes= 0 # create variable and initialise
|
||
# 2. Call PingOneNode function approximately every second
|
||
while True:
|
||
time.sleep(1)
|
||
#nodalDelay = self.pingOneNode(ipAddress, args.timeout, 1)
|
||
|
||
nodalDelay = self.pingOneNode()
|
||
self.printOneResult(ipAddress, 50, nodalDelay[1]*1000, 150)
|
||
numberofNodes = numberofNodes + 1 # increments number of nodes
|
||
|
||
# 4. Continue this process until stopped - until ICMP = 0
|
||
if self.ICMP_CODE == 0:
|
||
break
|
||
# 3. Print out the returned delay (and other relevant details) using the printOneResult method
|
||
# check this don't think its right
|
||
self.printOneResult(ipAddress, 50, nodalDelay[1]*1000, 150)
|
||
|
||
def pingOneNode(self):
|
||
# 1. Create ICMP socket
|
||
icmp_proto = socket.getprotobyname("icmp") #debugging
|
||
icmpSocket= socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp_proto)
|
||
# 2. Call sendNodePing function
|
||
timeSent= self.sendNodePing(icmpSocket, self.ipAddress, 111)
|
||
# 3. Call recieveNodePing function
|
||
networkDelay= self.recieveNodePing(icmpSocket, self.ipAddress, 111, 1000, timeSent)
|
||
# 4. Close ICMP socket
|
||
icmpSocket.close()
|
||
# 5. Return total network delay- add up all the nodes
|
||
x = 0
|
||
for x in self.numberOfNodes:
|
||
totalDelay = (networkDelay[x] + networkDelay[x + 1])
|
||
x = x + 1
|
||
if x == self.numberOfNodes:
|
||
break
|
||
return totalDelay
|
||
|
||
def sendNodePing(icmpSocket):
|
||
# 1. Build ICMP header
|
||
icmpHeader= struct.pack("bbHHh", 8, 0, 0, ID, 1)
|
||
# 2. Checksum ICMP packet using given function
|
||
icmpChecksum= self.checksum(icmpHeader)
|
||
# 3. Insert checksum into packet
|
||
packetHeader= struct.pack("bbHHh", 8, 0, icmpChecksum, ID, 1)
|
||
packet= packetHeader
|
||
# 4. Send packet using socket
|
||
# double check this //run with wireshark
|
||
icmpSocket.sendto(packet, (self.icmpAddress, 1))
|
||
# 5. Record time of sending
|
||
sentTime= time.time()
|
||
return sentTime
|
||
|
||
def recieveNodePing(icmpSocket):
|
||
# 1. Wait for the socket to receive a reply- TTL = 0
|
||
sentTime= time.time()
|
||
## Set the TTL for messages to 1 so they do not go past the local network segment
|
||
#TTL = socket.recvmessage()
|
||
|
||
TTL = struct.pack('b', 1)
|
||
icmpSocket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, TTL)
|
||
# 2. Once received, record time of receipt, otherwise, handle a timeout
|
||
try: # TTL == 0
|
||
timeRecieved = time.time()
|
||
# 3. Compare the time of receipt to time of sending, producing the total network delay- did when calculated RTT?
|
||
totalNetworkDelay = (timeRecieved * 1000) - sentTime
|
||
# 4. Unpack the packet header for useful information, including the ID
|
||
icmpType, icmpCode, icmpChecksum, icmpPacketID, icmpSeqNumber= struct.unpack("bbHHh", icmpHeader)
|
||
# 5. Check that the ID matches between the request and reply and # 6. Return total network delay
|
||
if(icmpPacketID == self.ID):
|
||
return totalNetworkDelay
|
||
else:
|
||
return 0
|
||
|
||
except TTL != 0: #if nothing is recieved, handle a timeout
|
||
print("TTL is 0 - socket has not recieved a reply")
|
||
return None
|
||
|
||
|
||
|
||
class WebServer(NetworkApplication):
|
||
|
||
def handleRequest(tcpSocket):
|
||
# 1. Receive request message from the client on connection (tcp?) socket
|
||
tcpSocket = serverSocket.accept() # acceptrequest
|
||
bufferSize = tcpSocket.CMSG_SPACE(4) # IPv4 address is 4 bytes in length - calculates the size of the buffer that should be allocated for receiving the ancillary data.
|
||
#recieve message in buffer size allocated
|
||
requestMessage = tcpSocket.recvmsg(bufferSize[0, [0]])
|
||
# 2. Extract the path of the requested object from the message (second part of the HTTP header)
|
||
file = requestMessage.unpack_from(bufferSize) # returns a tuple
|
||
# 3. Read the corresponding file from disk
|
||
socket.sendfile(file)
|
||
# 4. Store in temporary buffer
|
||
tempBuffer = socket.makefile( mode = 'r', buffering =None, encoding=None, errors=None, newline=None)
|
||
tempFile = struct.pack_into(format, self.tempBuffer, 0, file)
|
||
# 5. Send the correct HTTP response error
|
||
httpResponseError= ("HTTP/1.1 404 Not Found\r\n")
|
||
tcpSocket.sendmsg(httpResponseError)
|
||
# 6. Send the content of the file to the socket
|
||
tcpSocket.recvmsg(bufferSize[0, 0])
|
||
# 7. Close the connection socket
|
||
tcpSocket.close()
|
||
pass
|
||
|
||
def __init__(self, args):
|
||
print('Web Server starting on port: %i...' % (args.port))
|
||
# 1. Create server socket
|
||
serverSocket= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
print("creating server socket")
|
||
# 2. Bind the server socket to server address and server port
|
||
#serverSocket.bind((socket.gethostname(), 80))
|
||
serverSocket.bind((sys.argv[1],80))
|
||
print("binding socket")
|
||
# 3. Continuously listen for connections to server socket
|
||
serverSocket.listen(5)
|
||
# 4. When a connection is accepted, call handleRequest function, passing new connection socket (see https://docs.python.org/3/library/socket.html#socket.socket.accept)
|
||
newSocket= socket.accept()
|
||
while True:
|
||
handleRequest(newSocket)
|
||
print("calling handleRequest")
|
||
# 5. Close server socket
|
||
serverSocket.close()
|
||
|
||
|
||
class Proxy(NetworkApplication):
|
||
|
||
def __init__(self, args):
|
||
print('Web Proxy starting on port: %i...' % (args.port))
|
||
|
||
#if __name__ == "__main__":
|
||
# args=setupArgumentParser()
|
||
# args.func(args)
|
||
|
||
#1. create server socket and listen - connectionless socket: used to establish a TCP connection with the HTTP server
|
||
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
#2. Bind the server socket to server address and server port
|
||
serverSocket.bind((socket.gethostname(), 80))
|
||
#serverSocket.bind(('', args.port))
|
||
#serverSocket.bind((sys.argv[1],80))
|
||
print("binding socket")
|
||
serverSocket.listen(5)
|
||
#3. create proxy
|
||
proxySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
proxySocket.bind((socket.gethostname(), args.port))
|
||
# become a server socket
|
||
proxySocket.listen(5)
|
||
#4. Continuously listen for connections to server socket and proxy
|
||
#5. When a connection is accepted, call handleRequest function, passing new connection socket (?)
|
||
while 1:
|
||
connectionSocket, addr = serverSocket.accept() # accept TCP connection from client
|
||
with serverSocket.accept()[0] as connectionSocket: #pass new connection socket
|
||
print("recieved connection from ", addr)
|
||
handleRequest(proxySocket)
|
||
print("calling handleRequest")
|
||
# 5. Close server socket?
|
||
serverSocket.close()
|
||
|
||
|
||
def handleRequest(connectionSocket):
|
||
#1. Receive request message from the client on connection socket
|
||
# IPv4 address is 4 bytes in length
|
||
bufferSize = connectionSocket.CMSG_SPACE(4)
|
||
requestMessage = connectionSocket.recvmsg(bufferSize[0, [0]])
|
||
#2. forward to proxy
|
||
proxySocket.recvmsg(requestMessage)
|
||
#3. proxy extracts the path of the requested object from the message (second part of the HTTP header)
|
||
file = requestMessage.unpack_from( format, buffer, offset = 1) # returns a tuple
|
||
filename= requestMessage.split()[1]
|
||
#4. Read the corresponding file from disk: proxy server checks to see if object is stored locally
|
||
try:
|
||
fileOpen = open(filename[1:], "r") # open file in text mode
|
||
outputdata = fileOpen.readlines()
|
||
isObjectLocal == True
|
||
# 1. if it does, the proxy server returns the object within a HTTP response message to the client browser
|
||
httpResponse= ("GET /" + file + " HTTP/1.1\r\n\r\n")
|
||
# 3. Read the corresponding file from disk
|
||
socket.sendfile(object, offset = 0, count =None)
|
||
#send via HTTP response message to client Browser
|
||
|
||
except IOError:
|
||
if isObjectLocal == False:
|
||
# 2. if it doesn’t, the proxy server opens a TCP connection to the origin server??
|
||
originIP = serverSocket.gethostbyname(args.hostname)
|
||
proxySocket.connect(originIP, port)
|
||
# proxySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
# bind the socket to a public host, and a well-known port
|
||
# proxySocket.bind((socket.gethostname(), 80))
|
||
#sends HTTP request for object
|
||
httpRequest= ("GET /" + file + " HTTP/1.1\r\n\r\n")
|
||
proxySocket.send(httpRequest.encode())
|
||
#origin server recieves request
|
||
connectionSocket.recvmessage(httpRequest.encode())
|
||
|
||
#5. Store in temporary buffer
|
||
hostn = filename.split('/')[0].replace("www.","",1)
|
||
connectionSocket.connect((hostn,80))
|
||
# Create a temporary file on this socket
|
||
tempObject = proxySocket.makefile('r', 0)
|
||
tempObject.write("GET "+"http://" + filename + " HTTP/1.0\n\n")
|
||
|
||
#6. Send the correct HTTP response error
|
||
httpRequest= ("GET /" + file + " HTTP/1.1\r\n\r\n")
|
||
connectionSocket.send(httpRequest.encode())
|
||
#connctionSocket.send("HTTP/1.1 200 OK\r\n\r\n")
|
||
print("Request message sent")
|
||
#7. send content to webserver
|
||
object = connectionSocket.send(bufferSize[0, 0])
|
||
serverSocket.recvmsg(object)
|
||
#8. Send the content of the file to the socket
|
||
|
||
#9. Close the connection socket
|
||
connectionSocket.close()
|
||
|
||
|
||
|
||
if __name__ == "__main__":
|
||
args= setupArgumentParser()
|
||
args.func(args)
|
||
|
||
def main():
|
||
print("running")
|
||
NetworkApplication() |