Files
louiscklaw 866bfd3b42 update,
2025-01-31 19:51:47 +08:00

409 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 doesnt, 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()