├── .gitignore ├── README.md ├── lab2 └── cs205_lab2.py ├── lab3 ├── Echo_with_asyncio │ └── echo_ayncio.py └── HTTP_File_Browser │ ├── main.py │ ├── mime_types.py │ ├── parse_header.py │ └── responses.py ├── lab4 ├── extra │ ├── generate_mime_types.py │ └── mime.types ├── main.py ├── mime_types.py ├── parse_header.py └── responses.py ├── lab5 └── DNS_resolver.py └── lab8 ├── README.md ├── mtr.py └── traceroute.py /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Python 5 | *.pyc 6 | __pycache__ 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS305-Lab 2 | 3 | My code for some programs in CS305 lab assignments. 4 | 5 | # Credits 6 | 7 | - hguandl 8 | 9 | # LICENSE 10 | 11 | MIT 12 | -------------------------------------------------------------------------------- /lab2/cs205_lab2.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def hello_world(): 4 | print('Hello World') 5 | 6 | def find_prime(start: int, end: int) -> list: 7 | list = [] 8 | for i in range(start, end + 1): 9 | if i <= 1: 10 | continue 11 | prime = True 12 | for j in range(2, math.floor(math.sqrt(i)) + 1): 13 | if i % j == 0: 14 | prime = False 15 | break 16 | if prime: 17 | list.append(i) 18 | 19 | return list 20 | 21 | class doggy: 22 | def __init__(self, name): 23 | self.name = name; 24 | def bark(self) -> str: 25 | return self.name + ' bark' 26 | 27 | def printer_maker(key): 28 | def real_print(dict): 29 | return dict[key] 30 | return real_print 31 | 32 | if __name__ == '__main__': 33 | hello_world() 34 | 35 | s = int(input("please input the start number of the range: ")) 36 | e = int(input("please input the end number of the range: ")) 37 | r = find_prime(s, e) 38 | print("the prime between [%s,%s] is: "%(s, e)) 39 | print(r) 40 | 41 | adoggy = doggy("a doggy") 42 | print(adoggy.bark()) 43 | 44 | key = input("please input the key: ") 45 | fun = printer_maker(key) 46 | adic = {"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"} 47 | print(fun(adic)) -------------------------------------------------------------------------------- /lab3/Echo_with_asyncio/echo_ayncio.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | LISTEN_ADDR = '127.0.0.1' 4 | LISTEN_PORT = 5555 5 | 6 | 7 | async def dispatch(reader, writer): 8 | while True: 9 | data = await reader.read(2048) 10 | if data and data != b'exit\r\n': 11 | writer.write(data) 12 | print('{} sent: {}'.format(writer.get_extra_info('peername'), data)) 13 | else: 14 | break 15 | await writer.drain() 16 | writer.close() 17 | 18 | 19 | if __name__ == "__main__": 20 | loop = asyncio.get_event_loop() 21 | coro = asyncio.start_server(dispatch, LISTEN_ADDR, LISTEN_PORT, loop=loop) 22 | server = loop.run_until_complete(coro) 23 | 24 | print('Serving on {}'.format(server.sockets[0].getsockname())) 25 | try: 26 | loop.run_forever() 27 | except KeyboardInterrupt: 28 | pass 29 | -------------------------------------------------------------------------------- /lab3/HTTP_File_Browser/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import urllib.parse 4 | 5 | import parse_header 6 | from responses import AutoIndexResponse, FileResponse, NonExistResponse, InvalidMethodResponse 7 | 8 | ROOT_PATH = '.' 9 | LISTEN_ADDR = '127.0.0.1' 10 | LISTEN_PORT = 8080 11 | 12 | 13 | def range_parser(part_range) -> (int, int): 14 | if part_range is not None: 15 | parts = part_range.split('=') 16 | if parts[0] == 'bytes': 17 | range_int = parts[1].split('-') 18 | if range_int[0] != '': 19 | start = int(range_int[0]) 20 | else: 21 | start = 0 22 | if range_int[1] != '\r\n': 23 | end = int(range_int[1]) 24 | else: 25 | end = -1 26 | return start, end 27 | return None 28 | 29 | 30 | async def dispatch(reader, writer): 31 | headers_data = [] 32 | while True: 33 | data = await reader.readline() 34 | headers_data.append(data.decode()) 35 | # print(data) 36 | if data == b'\r\n' or data == b'': 37 | break 38 | 39 | client_headers = parse_header.HTTPHeader() 40 | for line in headers_data: 41 | client_headers.parse_header(line) 42 | 43 | method = client_headers.get('method') 44 | if method != 'GET' and method != 'HEAD': 45 | response = InvalidMethodResponse() 46 | writer.write(response.get_response()) 47 | 48 | else: 49 | path = urllib.parse.unquote(client_headers.get('path')) 50 | part_range = range_parser(client_headers.get('range')) 51 | 52 | real_path = ROOT_PATH + path 53 | 54 | try: 55 | if not os.path.isfile(real_path): 56 | response = AutoIndexResponse(path, real_path) 57 | response.add_entry('..') 58 | for filename in os.listdir(real_path): 59 | if filename[0:1] != '.': 60 | response.add_entry(filename) 61 | if method == 'GET': 62 | writer.write(response.get_response()) 63 | elif method == 'HEAD': 64 | writer.write(response.get_headers()) 65 | 66 | else: 67 | response = FileResponse(real_path, part_range) 68 | if method == 'GET': 69 | writer.write(response.get_response()) 70 | elif method == 'HEAD': 71 | writer.write(response.get_headers()) 72 | 73 | except FileNotFoundError: 74 | response = NonExistResponse() 75 | if method == 'GET': 76 | writer.write(response.get_response()) 77 | elif method == 'HEAD': 78 | writer.write(response.get_headers()) 79 | 80 | try: 81 | await writer.drain() 82 | except BrokenPipeError: 83 | pass 84 | writer.close() 85 | 86 | 87 | if __name__ == '__main__': 88 | loop = asyncio.get_event_loop() 89 | coro = asyncio.start_server(dispatch, LISTEN_ADDR, LISTEN_PORT, loop=loop) 90 | server = loop.run_until_complete(coro) 91 | 92 | # Serve requests until Ctrl+C is pressed 93 | print('Serving on {}'.format(server.sockets[0].getsockname())) 94 | try: 95 | loop.run_forever() 96 | except KeyboardInterrupt: 97 | pass 98 | 99 | # Close the server 100 | server.close() 101 | loop.run_until_complete(server.wait_closed()) 102 | loop.close() 103 | -------------------------------------------------------------------------------- /lab3/HTTP_File_Browser/mime_types.py: -------------------------------------------------------------------------------- 1 | mime_types = {'html': 'text/html', 'htm': 'text/html', 'shtml': 'text/html', 'css': 'text/css', 'xml': 'text/xml', 'gif': 'image/gif', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'js': 'application/javascript', 'atom': 'application/atom+xml', 'rss': 'application/rss+xml', 'mml': 'text/mathml', 'txt': 'text/plain', 'jad': 'text/vnd.sun.j2me.app-descriptor', 'wml': 'text/vnd.wap.wml', 'htc': 'text/x-component', 'png': 'image/png', 'svg': 'image/svg+xml', 'svgz': 'image/svg+xml', 'tif': 'image/tiff', 'tiff': 'image/tiff', 'wbmp': 'image/vnd.wap.wbmp', 'webp': 'image/webp', 'ico': 'image/x-icon', 'jng': 'image/x-jng', 'bmp': 'image/x-ms-bmp', 'woff': 'font/woff', 'woff2': 'font/woff2', 'jar': 'application/java-archive', 'war': 'application/java-archive', 'ear': 'application/java-archive', 'json': 'application/json', 'hqx': 'application/mac-binhex40', 'doc': 'application/msword', 'pdf': 'application/pdf', 'ps': 'application/postscript', 'eps': 'application/postscript', 'ai': 'application/postscript', 'rtf': 'application/rtf', 'm3u8': 'application/vnd.apple.mpegurl', 'kml': 'application/vnd.google-earth.kml+xml', 'kmz': 'application/vnd.google-earth.kmz', 'xls': 'application/vnd.ms-excel', 'eot': 'application/vnd.ms-fontobject', 'ppt': 'application/vnd.ms-powerpoint', 'odg': 'application/vnd.oasis.opendocument.graphics', 'odp': 'application/vnd.oasis.opendocument.presentation', 'ods': 'application/vnd.oasis.opendocument.spreadsheet', 'odt': 'application/vnd.oasis.opendocument.text', 'wmlc': 'application/vnd.wap.wmlc', '7z': 'application/x-7z-compressed', 'cco': 'application/x-cocoa', 'jardiff': 'application/x-java-archive-diff', 'jnlp': 'application/x-java-jnlp-file', 'run': 'application/x-makeself', 'pl': 'application/x-perl', 'pm': 'application/x-perl', 'prc': 'application/x-pilot', 'pdb': 'application/x-pilot', 'rar': 'application/x-rar-compressed', 'rpm': 'application/x-redhat-package-manager', 'sea': 'application/x-sea', 'swf': 'application/x-shockwave-flash', 'sit': 'application/x-stuffit', 'tcl': 'application/x-tcl', 'tk': 'application/x-tcl', 'der': 'application/x-x509-ca-cert', 'pem': 'application/x-x509-ca-cert', 'crt': 'application/x-x509-ca-cert', 'xpi': 'application/x-xpinstall', 'xhtml': 'application/xhtml+xml', 'xspf': 'application/xspf+xml', 'zip': 'application/zip', 'bin': 'application/octet-stream', 'exe': 'application/octet-stream', 'dll': 'application/octet-stream', 'deb': 'application/octet-stream', 'dmg': 'application/octet-stream', 'iso': 'application/octet-stream', 'img': 'application/octet-stream', 'msi': 'application/octet-stream', 'msp': 'application/octet-stream', 'msm': 'application/octet-stream', 'mid': 'audio/midi', 'midi': 'audio/midi', 'kar': 'audio/midi', 'mp3': 'audio/mpeg', 'ogg': 'audio/ogg', 'm4a': 'audio/x-m4a', 'ra': 'audio/x-realaudio', '3gpp': 'video/3gpp', '3gp': 'video/3gpp', 'ts': 'video/mp2t', 'mp4': 'video/mp4', 'mpeg': 'video/mpeg', 'mpg': 'video/mpeg', 'mov': 'video/quicktime', 'webm': 'video/webm', 'flv': 'video/x-flv', 'm4v': 'video/x-m4v', 'mng': 'video/x-mng', 'asx': 'video/x-ms-asf', 'asf': 'video/x-ms-asf', 'wmv': 'video/x-ms-wmv', 'avi': 'video/x-msvideo'} 2 | -------------------------------------------------------------------------------- /lab3/HTTP_File_Browser/parse_header.py: -------------------------------------------------------------------------------- 1 | keys = ('method', 'path', 'range') 2 | 3 | 4 | class HTTPHeader: 5 | def __init__(self): 6 | self.headers = {key: None for key in keys} 7 | 8 | def parse_header(self, line): 9 | fields = line.split(' ') 10 | if fields[0] == 'GET' or fields[0] == 'POST' or fields[0] == 'HEAD': 11 | self.headers['method'] = fields[0] 12 | self.headers['path'] = fields[1] 13 | if fields[0] == 'Range:': 14 | self.headers['range'] = fields[1] 15 | 16 | def get(self, key): 17 | return self.headers.get(key) 18 | -------------------------------------------------------------------------------- /lab3/HTTP_File_Browser/responses.py: -------------------------------------------------------------------------------- 1 | import html 2 | import os 3 | import urllib.parse 4 | 5 | from mime_types import mime_types 6 | 7 | 8 | class AutoIndexResponse(object): 9 | def __init__(self, path, real_path): 10 | self.headersStart = ('HTTP/1.0 200 OK\r\n' 11 | 'Content-Type:text/html; charset=utf-8\r\n' 12 | 'Server: GH-AutoIndex\r\n' 13 | 'Connection: close\r\n') 14 | self.headersEnd = '\r\n' 15 | self.path = path 16 | self.real_path = real_path 17 | title = html.escape(path) 18 | self.contentStart = ('Index of ' + title + '\r\n' 19 | '\r\n' 20 | '

Index of ' + title + '


\r\n' 21 | '
\r\n')
 22 |         self.contentEnd = ('
\r\n' 23 | '
\r\n' 24 | '\r\n') 25 | self.folders = [] 26 | self.files = [] 27 | 28 | def add_entry(self, name): 29 | if not os.path.isfile(self.real_path + name): 30 | name += '/' 31 | link = urllib.parse.quote(name) 32 | text = html.escape(name) 33 | self.folders.append(str.format('%s\r\n' % (link, text))) 34 | else: 35 | link = urllib.parse.quote(name) 36 | text = html.escape(name) 37 | self.files.append(str.format('%s\r\n' % (link, text))) 38 | 39 | def get_content(self) -> bytes: 40 | content = self.contentStart 41 | for entry in self.folders: 42 | content += entry 43 | for entry in self.files: 44 | content += entry 45 | content += self.contentEnd 46 | return content.encode() 47 | 48 | def get_headers(self) -> bytes: 49 | headers = self.headersStart 50 | headers += str.format('Content-Length: %d\r\n' % (len(self.get_content()))) 51 | headers += self.headersEnd 52 | return headers.encode() 53 | 54 | def get_response(self) -> bytes: 55 | return self.get_headers() + self.get_content() 56 | 57 | 58 | class FileResponse(object): 59 | def __init__(self, path, part_range): 60 | self.path = path 61 | self.size = os.path.getsize(path) 62 | self.start = None 63 | self.end = None 64 | 65 | if part_range is not None: 66 | self.start, self.end = part_range[0], part_range[1] 67 | if self.end < 0: 68 | self.end = self.size + self.end 69 | self.headers = ('HTTP/1.0 206 Partial Content\r\n' 70 | 'Server: GH-AutoIndex\r\n') 71 | self.headers += 'Content-Type: ' + self.__file_type() + '\r\n' 72 | self.headers += str.format('Content-Range: bytes %d-%d/%d\r\n' % 73 | (self.start, self.end, self.size)) 74 | self.headers += 'Connection: close\r\n' 75 | self.headers += 'Content-Length: ' + str(self.end - self.start + 1) + '\r\n\r\n' 76 | 77 | else: 78 | self.headers = ('HTTP/1.0 200 OK\r\n' 79 | 'Server: GH-AutoIndex\r\n') 80 | self.headers += 'Content-Type: ' + self.__file_type() + '\r\n' 81 | self.headers += 'Connection: close\r\n' 82 | self.headers += 'Content-Length: ' + str(self.size) + '\r\n' 83 | self.headers += 'Accept-Ranges: bytes\r\n\r\n' 84 | 85 | def __file_type(self) -> str: 86 | f_type = mime_types.get(self.path.split('.')[-1]) 87 | if not f_type: 88 | f_type = 'Application/octet-stream' 89 | return f_type 90 | 91 | def get_headers(self) -> bytes: 92 | return self.headers.encode() 93 | 94 | def get_content(self) -> bytes: 95 | file = open(self.path, 'rb') 96 | if self.start is not None: 97 | file.seek(self.start, 0) 98 | ret = file.read(self.end - self.start + 1) 99 | else: 100 | ret = file.read() 101 | file.close() 102 | return ret 103 | 104 | def get_response(self) -> bytes: 105 | return self.get_headers() + self.get_content() 106 | 107 | 108 | class NonExistResponse(object): 109 | def __init__(self): 110 | self.headers = ('HTTP/1.0 404 Not Found\r\n' 111 | 'Content-Type:text/html; charset=utf-8\r\n' 112 | 'Server: GH-AutoIndex\r\n' 113 | 'Connection: close\r\n' 114 | '\r\n') 115 | 116 | self.content = ('\r\n' 117 | '404 Not Found\r\n' 118 | '\r\n' 119 | '

404 Not Found

\r\n' 120 | '
GH-AutoIndex/0.1.2
\r\n' 121 | '\r\n' 122 | '\r\n') 123 | 124 | def get_headers(self) -> bytes: 125 | return self.headers.encode() 126 | 127 | def get_content(self) -> bytes: 128 | return self.content.encode() 129 | 130 | def get_response(self) -> bytes: 131 | return self.get_headers() + self.get_content() 132 | 133 | 134 | class InvalidMethodResponse(object): 135 | def __init__(self): 136 | self.headers = ('HTTP/1.0 405 Method Not Allowed\r\n' 137 | 'Content-Type:text/html; charset=utf-8\r\n' 138 | 'Server: GH-AutoIndex\r\n' 139 | 'Connection: close\r\n' 140 | '\r\n') 141 | 142 | self.content = ('\r\n' 143 | '405 Method Not Allowed\r\n' 144 | '\r\n' 145 | '

405 Method Not Allowed

\r\n' 146 | '
GH-AutoIndex/0.1.2
\r\n' 147 | '\r\n' 148 | '\r\n') 149 | 150 | def get_headers(self) -> bytes: 151 | return self.headers.encode() 152 | 153 | def get_content(self) -> bytes: 154 | return self.content.encode() 155 | 156 | def get_response(self) -> bytes: 157 | return self.get_headers() + self.get_content() -------------------------------------------------------------------------------- /lab4/extra/generate_mime_types.py: -------------------------------------------------------------------------------- 1 | types = {} 2 | 3 | with open('mime.types', 'r') as f: 4 | for line in f.readlines(): 5 | str1 = line.split(' ') 6 | while '' in str1: 7 | str1.remove('') 8 | str1 = [i.replace(';\n', '') for i in str1] 9 | if '\n' in str1[-1]: 10 | continue 11 | for i in str1[1:]: 12 | types[i] = str1[0] 13 | 14 | print(types) 15 | 16 | f.close() -------------------------------------------------------------------------------- /lab4/extra/mime.types: -------------------------------------------------------------------------------- 1 | 2 | types { 3 | text/html html htm shtml; 4 | text/css css; 5 | text/xml xml; 6 | image/gif gif; 7 | image/jpeg jpeg jpg; 8 | application/javascript js; 9 | application/atom+xml atom; 10 | application/rss+xml rss; 11 | 12 | text/mathml mml; 13 | text/plain txt; 14 | text/vnd.sun.j2me.app-descriptor jad; 15 | text/vnd.wap.wml wml; 16 | text/x-component htc; 17 | 18 | image/png png; 19 | image/svg+xml svg svgz; 20 | image/tiff tif tiff; 21 | image/vnd.wap.wbmp wbmp; 22 | image/webp webp; 23 | image/x-icon ico; 24 | image/x-jng jng; 25 | image/x-ms-bmp bmp; 26 | 27 | font/woff woff; 28 | font/woff2 woff2; 29 | 30 | application/java-archive jar war ear; 31 | application/json json; 32 | application/mac-binhex40 hqx; 33 | application/msword doc; 34 | application/pdf pdf; 35 | application/postscript ps eps ai; 36 | application/rtf rtf; 37 | application/vnd.apple.mpegurl m3u8; 38 | application/vnd.google-earth.kml+xml kml; 39 | application/vnd.google-earth.kmz kmz; 40 | application/vnd.ms-excel xls; 41 | application/vnd.ms-fontobject eot; 42 | application/vnd.ms-powerpoint ppt; 43 | application/vnd.oasis.opendocument.graphics odg; 44 | application/vnd.oasis.opendocument.presentation odp; 45 | application/vnd.oasis.opendocument.spreadsheet ods; 46 | application/vnd.oasis.opendocument.text odt; 47 | application/vnd.openxmlformats-officedocument.presentationml.presentation 48 | pptx; 49 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 50 | xlsx; 51 | application/vnd.openxmlformats-officedocument.wordprocessingml.document 52 | docx; 53 | application/vnd.wap.wmlc wmlc; 54 | application/x-7z-compressed 7z; 55 | application/x-cocoa cco; 56 | application/x-java-archive-diff jardiff; 57 | application/x-java-jnlp-file jnlp; 58 | application/x-makeself run; 59 | application/x-perl pl pm; 60 | application/x-pilot prc pdb; 61 | application/x-rar-compressed rar; 62 | application/x-redhat-package-manager rpm; 63 | application/x-sea sea; 64 | application/x-shockwave-flash swf; 65 | application/x-stuffit sit; 66 | application/x-tcl tcl tk; 67 | application/x-x509-ca-cert der pem crt; 68 | application/x-xpinstall xpi; 69 | application/xhtml+xml xhtml; 70 | application/xspf+xml xspf; 71 | application/zip zip; 72 | 73 | application/octet-stream bin exe dll; 74 | application/octet-stream deb; 75 | application/octet-stream dmg; 76 | application/octet-stream iso img; 77 | application/octet-stream msi msp msm; 78 | 79 | audio/midi mid midi kar; 80 | audio/mpeg mp3; 81 | audio/ogg ogg; 82 | audio/x-m4a m4a; 83 | audio/x-realaudio ra; 84 | 85 | video/3gpp 3gpp 3gp; 86 | video/mp2t ts; 87 | video/mp4 mp4; 88 | video/mpeg mpeg mpg; 89 | video/quicktime mov; 90 | video/webm webm; 91 | video/x-flv flv; 92 | video/x-m4v m4v; 93 | video/x-mng mng; 94 | video/x-ms-asf asx asf; 95 | video/x-ms-wmv wmv; 96 | video/x-msvideo avi; 97 | } 98 | -------------------------------------------------------------------------------- /lab4/main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import urllib.parse 4 | 5 | import parse_header 6 | from responses import AutoIndexResponse, FileResponse, NonExistResponse, InvalidMethodResponse, RedirectResponse 7 | 8 | ROOT_PATH = '.' 9 | LISTEN_ADDR = '127.0.0.1' 10 | LISTEN_PORT = 8080 11 | 12 | 13 | async def dispatch(reader, writer): 14 | headers_data = [] 15 | while True: 16 | data = await reader.readline() 17 | headers_data.append(data.decode()) 18 | # print(data) 19 | if data == b'\r\n' or data == b'': 20 | break 21 | 22 | client_headers = parse_header.HTTPHeader() 23 | for line in headers_data: 24 | client_headers.parse_header(line) 25 | 26 | method = client_headers.get('method') 27 | if method != 'GET' and method != 'HEAD': 28 | response = InvalidMethodResponse(method) 29 | writer.write(response.get_response()) 30 | 31 | else: 32 | path = urllib.parse.unquote(client_headers.get('path')) 33 | part_range = client_headers.get('range') 34 | cookie = client_headers.get('cookie') 35 | 36 | if path == '/' and cookie and cookie.get('last') and cookie.get('last') != '/': 37 | response = RedirectResponse(method, cookie.get('last')) 38 | writer.write(response.get_response()) 39 | 40 | else: 41 | real_path = ROOT_PATH + path 42 | 43 | try: 44 | if not os.path.isfile(real_path): 45 | if path[-1:] != '/': 46 | response = RedirectResponse(method, path + '/') 47 | writer.write(response.get_response()) 48 | else: 49 | response = AutoIndexResponse(method, path, real_path) 50 | response.add_entry('..') 51 | for filename in os.listdir(real_path): 52 | if filename[0:1] != '.': 53 | response.add_entry(filename) 54 | writer.write(response.get_response()) 55 | 56 | else: 57 | response = FileResponse(method, real_path, part_range) 58 | writer.write(response.get_response()) 59 | 60 | except FileNotFoundError: 61 | response = NonExistResponse(method) 62 | writer.write(response.get_response()) 63 | 64 | try: 65 | await writer.drain() 66 | except BrokenPipeError: 67 | pass 68 | writer.close() 69 | 70 | 71 | if __name__ == '__main__': 72 | loop = asyncio.get_event_loop() 73 | coro = asyncio.start_server(dispatch, LISTEN_ADDR, LISTEN_PORT, loop=loop) 74 | server = loop.run_until_complete(coro) 75 | 76 | # Serve requests until Ctrl+C is pressed 77 | print('Serving on {}'.format(server.sockets[0].getsockname())) 78 | try: 79 | loop.run_forever() 80 | except KeyboardInterrupt: 81 | pass 82 | 83 | # Close the server 84 | server.close() 85 | loop.run_until_complete(server.wait_closed()) 86 | loop.close() 87 | -------------------------------------------------------------------------------- /lab4/mime_types.py: -------------------------------------------------------------------------------- 1 | mime_types = {'html': 'text/html', 'htm': 'text/html', 'shtml': 'text/html', 'css': 'text/css', 'xml': 'text/xml', 'gif': 'image/gif', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'js': 'application/javascript', 'atom': 'application/atom+xml', 'rss': 'application/rss+xml', 'mml': 'text/mathml', 'txt': 'text/plain', 'jad': 'text/vnd.sun.j2me.app-descriptor', 'wml': 'text/vnd.wap.wml', 'htc': 'text/x-component', 'png': 'image/png', 'svg': 'image/svg+xml', 'svgz': 'image/svg+xml', 'tif': 'image/tiff', 'tiff': 'image/tiff', 'wbmp': 'image/vnd.wap.wbmp', 'webp': 'image/webp', 'ico': 'image/x-icon', 'jng': 'image/x-jng', 'bmp': 'image/x-ms-bmp', 'woff': 'font/woff', 'woff2': 'font/woff2', 'jar': 'application/java-archive', 'war': 'application/java-archive', 'ear': 'application/java-archive', 'json': 'application/json', 'hqx': 'application/mac-binhex40', 'doc': 'application/msword', 'pdf': 'application/pdf', 'ps': 'application/postscript', 'eps': 'application/postscript', 'ai': 'application/postscript', 'rtf': 'application/rtf', 'm3u8': 'application/vnd.apple.mpegurl', 'kml': 'application/vnd.google-earth.kml+xml', 'kmz': 'application/vnd.google-earth.kmz', 'xls': 'application/vnd.ms-excel', 'eot': 'application/vnd.ms-fontobject', 'ppt': 'application/vnd.ms-powerpoint', 'odg': 'application/vnd.oasis.opendocument.graphics', 'odp': 'application/vnd.oasis.opendocument.presentation', 'ods': 'application/vnd.oasis.opendocument.spreadsheet', 'odt': 'application/vnd.oasis.opendocument.text', 'wmlc': 'application/vnd.wap.wmlc', '7z': 'application/x-7z-compressed', 'cco': 'application/x-cocoa', 'jardiff': 'application/x-java-archive-diff', 'jnlp': 'application/x-java-jnlp-file', 'run': 'application/x-makeself', 'pl': 'application/x-perl', 'pm': 'application/x-perl', 'prc': 'application/x-pilot', 'pdb': 'application/x-pilot', 'rar': 'application/x-rar-compressed', 'rpm': 'application/x-redhat-package-manager', 'sea': 'application/x-sea', 'swf': 'application/x-shockwave-flash', 'sit': 'application/x-stuffit', 'tcl': 'application/x-tcl', 'tk': 'application/x-tcl', 'der': 'application/x-x509-ca-cert', 'pem': 'application/x-x509-ca-cert', 'crt': 'application/x-x509-ca-cert', 'xpi': 'application/x-xpinstall', 'xhtml': 'application/xhtml+xml', 'xspf': 'application/xspf+xml', 'zip': 'application/zip', 'bin': 'application/octet-stream', 'exe': 'application/octet-stream', 'dll': 'application/octet-stream', 'deb': 'application/octet-stream', 'dmg': 'application/octet-stream', 'iso': 'application/octet-stream', 'img': 'application/octet-stream', 'msi': 'application/octet-stream', 'msp': 'application/octet-stream', 'msm': 'application/octet-stream', 'mid': 'audio/midi', 'midi': 'audio/midi', 'kar': 'audio/midi', 'mp3': 'audio/mpeg', 'ogg': 'audio/ogg', 'm4a': 'audio/x-m4a', 'ra': 'audio/x-realaudio', '3gpp': 'video/3gpp', '3gp': 'video/3gpp', 'ts': 'video/mp2t', 'mp4': 'video/mp4', 'mpeg': 'video/mpeg', 'mpg': 'video/mpeg', 'mov': 'video/quicktime', 'webm': 'video/webm', 'flv': 'video/x-flv', 'm4v': 'video/x-m4v', 'mng': 'video/x-mng', 'asx': 'video/x-ms-asf', 'asf': 'video/x-ms-asf', 'wmv': 'video/x-ms-wmv', 'avi': 'video/x-msvideo'} 2 | -------------------------------------------------------------------------------- /lab4/parse_header.py: -------------------------------------------------------------------------------- 1 | keys = ('method', 'path', 'range', 'cookie') 2 | 3 | 4 | class HTTPHeader(object): 5 | def __init__(self): 6 | self.headers = {key: None for key in keys} 7 | 8 | def parse_header(self, line): 9 | fields = line.split(' ') 10 | if fields[0] == 'GET' or fields[0] == 'POST' or fields[0] == 'HEAD': 11 | self.headers['method'] = fields[0] 12 | self.headers['path'] = fields[1] 13 | if fields[0] == 'Range:': 14 | self.headers['range'] = HTTPRange(fields[1][:-2]).get_range() 15 | if fields[0] == 'Cookie:': 16 | self.headers['cookie'] = HTTPCookie(fields[1][:-2]) 17 | 18 | def get(self, key): 19 | return self.headers.get(key) 20 | 21 | 22 | class HTTPCookie(object): 23 | def __init__(self, string): 24 | self.__cookie = {} 25 | items = string.split(';') 26 | for i in items: 27 | entry = i.split('=') 28 | self.__cookie[entry[0]] = entry[1] 29 | 30 | def get(self, entry): 31 | if entry in self.__cookie: 32 | return self.__cookie[entry] 33 | else: 34 | return None 35 | 36 | 37 | class HTTPRange(object): 38 | def __init__(self, string): 39 | self.__start = None 40 | self.__end = None 41 | parts = string.split('=') 42 | if parts[0] == 'bytes': 43 | range_int = parts[1].split('-') 44 | if range_int[0] != '': 45 | self.__start = int(range_int[0]) 46 | else: 47 | self.__start = 0 48 | if range_int[1] != '': 49 | self.__end = int(range_int[1]) 50 | else: 51 | self.__end = -1 52 | 53 | def get_range(self) -> (int, int): 54 | if self.__start is not None and self.__end is not None: 55 | return self.__start, self.__end 56 | else: 57 | return None 58 | -------------------------------------------------------------------------------- /lab4/responses.py: -------------------------------------------------------------------------------- 1 | import html 2 | import os 3 | import urllib.parse 4 | 5 | from mime_types import mime_types 6 | 7 | _version = '1.0.0' 8 | 9 | 10 | class Response(object): 11 | def __init__(self, method, version, code, message): 12 | self.method = method 13 | self.version = version 14 | self.code = code 15 | self.message = message 16 | self.headers = {'Server': 'GH-AutoIndex/' + _version, 17 | 'Connection': 'close'} 18 | self.body = '' 19 | 20 | def get_headers(self) -> bytes: 21 | headers = str.format('HTTP/%s %s %s\r\n' % (self.version, self.code, self.message)) 22 | for key in self.headers: 23 | headers += str.format('%s: %s\r\n' % (key, self.headers[key])) 24 | headers += '\r\n' 25 | return headers.encode() 26 | 27 | def get_body(self) -> bytes: 28 | return self.body.encode() 29 | 30 | def get_response(self) -> bytes: 31 | if self.method == 'HEAD': 32 | return self.get_headers() 33 | elif self.method == 'GET': 34 | return self.get_headers() + self.get_body() 35 | else: 36 | return b'' 37 | 38 | 39 | class ErrorResponse(Response): 40 | def __init__(self, method, code, message): 41 | super().__init__(method, '1.0', code, message) 42 | self.headers['Content-Type'] = 'text/html; charset=utf-8' 43 | self.body = str.format('%s %s\r\n' 44 | '\r\n' 45 | '

%s %s

\r\n' 46 | '
GH-AutoIndex/%s
\r\n' 47 | '\r\n' % 48 | (code, message, code, message, _version)) 49 | 50 | 51 | class AutoIndexResponse(Response): 52 | def __init__(self, method, path, real_path): 53 | super().__init__(method, '1.0', '200', 'OK') 54 | self.headers['Content-Type'] = 'text/html; charset=utf-8' 55 | self.path = path 56 | self.real_path = real_path 57 | title = html.escape(path) 58 | self.contentStart = ('Index of ' + title + '\r\n' 59 | '\r\n' 60 | '

Index of ' + title + '


\r\n' 61 | '
\r\n')
 62 |         self.contentEnd = ('
\r\n' 63 | '
\r\n' 64 | '\r\n') 65 | self.folders = [] 66 | self.files = [] 67 | self.last_dir = None 68 | 69 | def add_entry(self, name): 70 | if not os.path.isfile(self.real_path + name): 71 | name += '/' 72 | link = urllib.parse.quote(name) 73 | text = html.escape(name) 74 | self.folders.append(str.format('%s\r\n' % (link, text))) 75 | self.last_dir = urllib.parse.quote(self.path) 76 | else: 77 | link = urllib.parse.quote(name) 78 | text = html.escape(name) 79 | self.files.append(str.format('%s\r\n' % (link, text))) 80 | 81 | def get_body(self) -> bytes: 82 | content = self.contentStart 83 | for entry in self.folders: 84 | content += entry 85 | for entry in self.files: 86 | content += entry 87 | content += self.contentEnd 88 | return content.encode() 89 | 90 | def get_headers(self) -> bytes: 91 | self.headers['Set-Cookie'] = str.format('last=%s; Path=/' % self.last_dir) 92 | self.headers['Content-Length'] = str.format('%d' % len(self.get_body())) 93 | return super().get_headers() 94 | 95 | 96 | class FileResponse(Response): 97 | def __init__(self, method, path, part_range): 98 | super().__init__(method, '1.1', '200', 'OK') 99 | self.path = path 100 | self.size = os.path.getsize(path) 101 | self.part_range = part_range 102 | 103 | self.headers['Content-Type'] = self.__file_type() 104 | self.headers['Accept-Ranges'] = 'bytes' 105 | 106 | if part_range is not None: 107 | self.code = '206' 108 | self.message = 'Partial Content' 109 | self.start, self.end = part_range[0], part_range[1] 110 | if self.end < 0: 111 | self.end = self.size + self.end 112 | self.headers['Content-Range'] = str.format('bytes %d-%d/%d' % 113 | (self.start, self.end, self.size)) 114 | self.headers['Content-Length'] = str(self.end - self.start + 1) 115 | 116 | else: 117 | self.headers['Content-Length'] = str(self.size) 118 | 119 | def __file_type(self) -> str: 120 | f_type = mime_types.get(self.path.split('.')[-1]) 121 | if not f_type: 122 | f_type = 'Application/octet-stream' 123 | return f_type 124 | 125 | def get_body(self) -> bytes: 126 | with open(self.path, 'rb') as file: 127 | if self.part_range is not None: 128 | file.seek(self.start, 0) 129 | return file.read(self.end - self.start + 1) 130 | else: 131 | return file.read() 132 | 133 | 134 | class NonExistResponse(ErrorResponse): 135 | def __init__(self, method): 136 | super().__init__(method, '404', 'Not Found') 137 | 138 | 139 | class InvalidMethodResponse(ErrorResponse): 140 | def __init__(self, method): 141 | super().__init__(method, '405', 'Method Not Allowed') 142 | 143 | 144 | class RedirectResponse(ErrorResponse): 145 | def __init__(self, method, path): 146 | super().__init__(method, '302', 'Found') 147 | self.headers['Location'] = path 148 | -------------------------------------------------------------------------------- /lab5/DNS_resolver.py: -------------------------------------------------------------------------------- 1 | from socket import * 2 | import time 3 | 4 | SERVER_ADDR = '0.0.0.0' 5 | SERVER_PORT = 5300 6 | 7 | # Upsteam Address, use 114 DNS 8 | UPSTREAM_ADDR = '114.114.114.114' 9 | UPSTREAM_PORT = 53 10 | 11 | # Duration to cleanup caches 12 | CACHE_TTL = 1800 13 | 14 | caches = [] 15 | 16 | 17 | # Query without EDNS 18 | class SimplifiedQuery(object): 19 | def __init__(self, data): 20 | self.raw_data = list(data) 21 | self.id = data[0:2] 22 | self._parse_question() 23 | 24 | def _parse_question(self): 25 | # Bypass Header Section 26 | data = self.raw_data[12:] 27 | # QNAME ends with 0o0 28 | end_of_qname = data.index(0o0) 29 | # QTYPE, QCLASS are the next 4 octet 30 | self.question = data[:end_of_qname + 5] 31 | # Query without EDNS 32 | self.simple_data = self.raw_data[:12 + end_of_qname + 5] 33 | # Set ARCOUNT to 0 34 | self.simple_data[11] = 0x0 35 | self.simple_data = bytes(self.simple_data) 36 | 37 | 38 | # DNS Answer Cache 39 | class DNSRecord(SimplifiedQuery): 40 | def __init__(self, _query, _response): 41 | super().__init__(_response) 42 | self.query = _query 43 | self.time = time.time() 44 | self.ttl = [] 45 | # Record Answer RR Counts 46 | self.count = int.from_bytes(bytes(self.raw_data[6:8]), byteorder='big') 47 | # Add Authority RR Counts 48 | self.count += int.from_bytes(bytes(self.raw_data[8:10]), byteorder='big') 49 | self._parse_record() 50 | 51 | # Rewrite compare method to use list.index() for searching 52 | def __eq__(self, _query: SimplifiedQuery) -> bool: 53 | return self.query.question == _query.question 54 | 55 | def _parse_record(self): 56 | # Bypass Header & Question Section 57 | pt = len(self.simple_data) 58 | rr_cnt = 0 59 | while rr_cnt < self.count: 60 | # Bypass NAME 61 | if self.raw_data[pt] & 0xc0 == 0xc0: 62 | # Pointer, 2 bytes 63 | pt += 2 64 | else: 65 | # Plain name, ends with 0x0 66 | while self.raw_data[pt] != 0x0: 67 | pt += 1 68 | pt += 1 69 | # Bypass TYPE & CLASS 70 | pt += 4 71 | # Record the position and value of TTL 72 | self.ttl.append([pt, int.from_bytes(bytes(self.raw_data[pt:(pt + 4)]), byteorder='big')]) 73 | pt += 4 74 | # Bypass RLENGTH and RDATA 75 | rlength = int.from_bytes(bytes(self.raw_data[pt:(pt + 2)]), byteorder='big') 76 | pt += 2 + rlength 77 | rr_cnt += 1 78 | 79 | def get_response(self, new_query) -> bytes: 80 | rr = self.raw_data 81 | # Use ID from new_query 82 | rr[0:2] = new_query.id 83 | 84 | # Check the TTL 85 | t_elapse = int(time.time() - self.time) 86 | for r_ttl in self.ttl: 87 | pt = r_ttl[0] 88 | new_ttl = r_ttl[1] - t_elapse 89 | if new_ttl <= 0: 90 | # Update the record from upstream 91 | self.__init__(self.query, query_upstream(self.query)) 92 | return self.get_response(new_query) 93 | rr[pt:(pt + 4)] = int.to_bytes(new_ttl, length=4, byteorder='big') 94 | 95 | return bytes(rr) 96 | 97 | def revoked(self) -> bool: 98 | t_elapse = int(time.time() - self.time) 99 | for r_ttl in self.ttl: 100 | new_ttl = r_ttl[1] - t_elapse 101 | if new_ttl <= 0: 102 | return True 103 | 104 | return False 105 | 106 | 107 | def query_upstream(_query) -> bytes: 108 | client_socket.sendto(_query.simple_data, (UPSTREAM_ADDR, UPSTREAM_PORT)) 109 | _response, server_address = client_socket.recvfrom(2048) 110 | return _response 111 | 112 | 113 | if __name__ == '__main__': 114 | t_start = time.time() 115 | server_socket = socket(AF_INET, SOCK_DGRAM) 116 | server_socket.bind((SERVER_ADDR, SERVER_PORT)) 117 | print('The server is ready at {}:{}'.format(SERVER_ADDR, SERVER_PORT)) 118 | 119 | client_socket = socket(AF_INET, SOCK_DGRAM) 120 | 121 | while True: 122 | # Cleanup outdated, unpopular cache 123 | if time.time() - t_start >= CACHE_TTL: 124 | for c in caches: 125 | if c.revoked(): 126 | caches.remove(c) 127 | 128 | message, client_address = server_socket.recvfrom(2048) 129 | query = SimplifiedQuery(message) 130 | try: 131 | cache = caches[caches.index(query)] 132 | # The query has already been cached 133 | response = cache.get_response(query) 134 | except ValueError: 135 | # No cached record matched, send query to the upstream server 136 | response = query_upstream(query) 137 | # Add the response to caches 138 | caches.append(DNSRecord(query, response)) 139 | server_socket.sendto(response, client_address) 140 | -------------------------------------------------------------------------------- /lab8/README.md: -------------------------------------------------------------------------------- 1 | # Requirements 2 | 3 | [raw_python](https://github.com/lightsing/raw_python) 4 | 5 | ## Install via PyPi 6 | ```sh 7 | $ pip install git+https://github.com/lightsing/raw_python 8 | ``` 9 | 10 | 11 | -------------------------------------------------------------------------------- /lab8/mtr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import curses 4 | import socket 5 | import time 6 | import sys 7 | 8 | import threading 9 | import math 10 | 11 | from raw_python import ICMPPacket, parse_icmp_header, parse_eth_header, parse_ip_header 12 | from traceroute import single_ping_request, catch_ping_reply 13 | 14 | # Init curses 15 | stdscr = curses.initscr() 16 | # Get height and width of the console 17 | HEIGHT, WIDTH = stdscr.getmaxyx() 18 | # Not to display cursor 19 | curses.curs_set(0) 20 | 21 | 22 | # Thread for update title time and catch quit 23 | class MenuThread(threading.Thread): 24 | def __init__(self, header, menu): 25 | super().__init__() 26 | self.header = header 27 | self.menu = menu 28 | 29 | def run(self): 30 | while True: 31 | lock.acquire() 32 | # Update current time 33 | self.header.erase() 34 | self.header.addstr(get_header()) 35 | self.header.refresh() 36 | self.menu.refresh() 37 | lock.release() 38 | # Wait for input 39 | c = self.menu.getch() 40 | # Received exit signal 41 | if c == ord('q'): 42 | global TO_EXIT 43 | TO_EXIT = True 44 | break 45 | time.sleep(1) 46 | 47 | 48 | # A node for every step of traceroute 49 | class TraceNode(object): 50 | def __init__(self, hostname): 51 | self.hostid = len(hosts) 52 | self.hostname = hostname 53 | self.cnt = 0 # Received ICMP echo 54 | self.snt = 0 # Total ICMP packets sent 55 | self.last = 0 # Last RTT 56 | self.avg = 0 # Average RTT 57 | self.best = -1 # Shorest RTT 58 | self.wrst = -1 # Longest RTT 59 | self.stdev = 0 # Standard deviation 60 | self.loss_rate = 0 # Loss rate 61 | 62 | # Keep the hostname unique in the list 63 | def __eq__(self, name: str): 64 | return self.hostname == name 65 | 66 | # Update statistics of RTT 67 | def update(self, rtt): 68 | self.snt += 1 69 | if rtt >= 0: 70 | if self.best == -1 or self.best > rtt: 71 | self.best = rtt 72 | if self.wrst == -1 or self.wrst < rtt: 73 | self.wrst = rtt 74 | self.avg = (self.avg * self.cnt + rtt) / (self.cnt + 1) 75 | self.stdev = math.sqrt((self.stdev * self.stdev * self.cnt + (self.avg - rtt) * (self.avg - rtt)) / (self.cnt + 1)) 76 | self.cnt += 1 77 | self.last = rtt 78 | self.loss_rate = (1 - self.cnt / self.snt) * 100 79 | 80 | # Format the output to a row 81 | def output(self) -> str: 82 | return '{:4.1f}%{:6d} {:6.1f}{:6.1f}{:6.1f}{:6.1f}{:6.1f}\n'.format( \ 83 | self.loss_rate, self.snt, self.last, self.avg, self.best, self.wrst, self.stdev) 84 | 85 | 86 | # Thread for ping each node 87 | class PingThread(threading.Thread): 88 | def __init__(self, tab): 89 | super().__init__() 90 | self.tab = tab 91 | 92 | def run(self): 93 | while True: 94 | if TO_EXIT: 95 | break 96 | for i in hosts: 97 | if TO_EXIT: 98 | break 99 | # Bypass unknown host 100 | if i == '???': 101 | continue 102 | # Execute ping 103 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, 64) 104 | ping_id = single_ping_request(sock, i.hostname) 105 | lock.acquire() 106 | rtt, _, _, reached = catch_ping_reply(sock, ping_id, time.time()) 107 | # Get a valid ping echo 108 | if reached and rtt is not None: 109 | i.update(rtt * 1000) 110 | else: 111 | i.update(-1) 112 | # Update & display RTT info 113 | rtt_tab.move(2 + i.hostid, 0) 114 | rtt_tab.addstr(i.output()) 115 | rtt_tab.refresh() 116 | lock.release() 117 | time.sleep(0.1) 118 | 119 | 120 | # Thread for traceroute 121 | class TraceThread(threading.Thread): 122 | def __init__(self, host_list, rtt_tab): 123 | super().__init__() 124 | self.host_list = host_list 125 | self.rtt_tab = rtt_tab 126 | 127 | def run(self): 128 | while True: 129 | if TO_EXIT: 130 | break 131 | lock.acquire() 132 | # Execute ping 133 | addr, rtt, reached = ping(len(hosts) + 1, sys.argv[1]) 134 | # Have reply 135 | if addr is not None: 136 | # A new host 137 | if not hosts.__contains__(addr): 138 | tmp = TraceNode(addr) 139 | hosts.append(tmp) 140 | # Display the new host 141 | self.host_list.addstr('{:2d}. {}\n'.format(len(hosts), addr)) 142 | tmp.update(rtt) 143 | # Display the rtt info 144 | self.rtt_tab.move(2 + tmp.hostid, 0) 145 | self.rtt_tab.addstr(tmp.output()) 146 | else: 147 | # Unknown host (no reply) 148 | hosts.append('???') 149 | self.host_list.addstr('{:2d}. {}\n'.format(len(hosts), '???')) 150 | self.host_list.refresh() 151 | self.rtt_tab.refresh() 152 | lock.release() 153 | if reached: 154 | break 155 | 156 | 157 | # Exit signal for all threads 158 | TO_EXIT = False 159 | 160 | # Create raw socket 161 | sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) 162 | 163 | # Names for each host 164 | hosts = [] 165 | 166 | # Threads 167 | threads = [] 168 | lock = threading.Lock() 169 | 170 | # Localhost name + current time 171 | def get_header() -> str: 172 | # localhost name 173 | host_str = '{} ({})'.format(socket.gethostname(), socket.gethostbyname(socket.gethostname())) 174 | # place current time from right 175 | time_str = time.asctime(time.localtime(time.time())) 176 | # use spaces to fill the middle of the line 177 | header_spaces = WIDTH - len(time_str) - len(host_str) - 1 178 | return host_str + ' ' * header_spaces + time_str 179 | 180 | def ping(num, hostname) -> (str, float): 181 | # Set TTL 182 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, num) 183 | # Setup up an ICMP packet 184 | ID = single_ping_request(sock, hostname) 185 | # Execute ping 186 | rtt, reply, icmp_reply, reached = catch_ping_reply(sock, ID, time.time()) 187 | # Fetched ICMP echo 188 | if reply: 189 | return reply['Source Address'], rtt * 1000, reached 190 | return None, None, False 191 | 192 | def main(stdscr): 193 | # Set color 194 | curses.use_default_colors() 195 | 196 | # Title: MyTraceRoute, place to the middle 197 | title = curses.newwin(1, 0, 0, WIDTH // 2 - 6) 198 | 199 | # Header: localhost name + current time 200 | header = curses.newwin(1, 0, 1, 0) 201 | 202 | # Menu: [q]uit 203 | menu = curses.newwin(1, 0, 2, 0) 204 | # Not to wait for input 205 | menu.nodelay(True) 206 | 207 | # Every host during the routing 208 | host_list = curses.newwin(0, 0, 3, 0) 209 | 210 | # Table to display RTT statistics 211 | global rtt_tab 212 | rtt_tab = curses.newwin(0, 0, 3, WIDTH - 43) 213 | 214 | # Table header for rtt_tab 215 | rtt_tab_str = ' Packets Pings\n' + \ 216 | 'Loss% Snt Last Avg Best Wrst StDev' 217 | 218 | # Contents of title 219 | title.addstr("MyTraceRoute", curses.A_BOLD) 220 | 221 | # Contents of menu 222 | menu.addstr('Keys: ') 223 | menu.addch('q', curses.A_BOLD) 224 | menu.addstr('uit') 225 | 226 | # Title of host window 227 | host_list.addstr('\n Host\n', curses.A_BOLD) 228 | rtt_tab.addstr(rtt_tab_str, curses.A_BOLD) 229 | 230 | # Refresh windows 231 | menu.refresh() 232 | title.refresh() 233 | host_list.refresh() 234 | rtt_tab.refresh() 235 | header.clear() 236 | 237 | # Add threads to the list 238 | threads.append(MenuThread(header, menu)) 239 | threads.append(TraceThread(host_list, rtt_tab)) 240 | threads.append(PingThread(rtt_tab)) 241 | 242 | # Run threads 243 | for t in threads: 244 | t.start() 245 | 246 | # Wait until all threads finish 247 | for t in threads: 248 | t.join() 249 | 250 | # Close the socket 251 | sock.close() 252 | 253 | if __name__ == '__main__': 254 | # Need an arg as hostname 255 | if len(sys.argv) != 2: 256 | curses.endwin() 257 | print('Usage: python3 mtr.py hostname') 258 | sys.exit(2) 259 | 260 | # The window is too narrow 261 | if WIDTH < 52: 262 | curses.endwin() 263 | print('The width of console must at least than 52.') 264 | sys.exit(2) 265 | else: 266 | curses.wrapper(main) 267 | -------------------------------------------------------------------------------- /lab8/traceroute.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import random 3 | import select 4 | import socket 5 | import time 6 | import sys 7 | 8 | from raw_python import ICMPPacket, parse_icmp_header, parse_eth_header, parse_ip_header 9 | 10 | def calc_rtt(time_sent): 11 | return time.time() - time_sent 12 | 13 | def single_ping_request(s, addr=None): 14 | # Random Packet Id 15 | pkt_id = random.randrange(10000, 65000) 16 | 17 | # Create ICMP Packet 18 | packet = ICMPPacket(_id=pkt_id).raw 19 | 20 | # Send ICMP Packet 21 | while packet: 22 | sent = s.sendto(packet, (addr, 1)) 23 | packet = packet[sent:] 24 | 25 | return pkt_id 26 | 27 | def catch_ping_reply(s, ID, time_sent, timeout=1): 28 | # create while loop 29 | while True: 30 | starting_time = time.time() # Record Starting Time 31 | 32 | # to handle timeout function of socket 33 | process = select.select([s], [], [], timeout) 34 | 35 | # check if timeout 36 | if not process[0]: 37 | return calc_rtt(time_sent), None, None, False 38 | 39 | # receive packet 40 | rec_packet, addr = s.recvfrom(1024) 41 | 42 | # extract icmp packet from received packet 43 | icmp = parse_icmp_header(rec_packet[20:28]) 44 | 45 | # return every icmp response 46 | return calc_rtt(time_sent), parse_ip_header(rec_packet[:20]), icmp, icmp['id'] == ID 47 | 48 | def main(): 49 | s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) 50 | 51 | # need hostname 52 | if len(sys.argv) != 2: 53 | sys.exit(2) 54 | 55 | print('traceroute to {} ({})'.format(sys.argv[1], socket.gethostbyname(sys.argv[1]))) 56 | 57 | step = 0 58 | while True: 59 | step += 1 60 | hostname = None 61 | rtt_str= '' 62 | # Set TTL 63 | s.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, step) 64 | for i in range(3): 65 | # Send ping 66 | ID = single_ping_request(s, sys.argv[1]) 67 | rtt, reply, icmp_reply, reached = catch_ping_reply(s, ID, time.time()) 68 | 69 | # Record reply 70 | if reply: 71 | hostname = reply['Source Address'] 72 | rtt_str += ' {:.2f} ms'.format(rtt * 1000) 73 | else: 74 | rtt_str += ' *' 75 | # The server sent reply 76 | if hostname is not None: 77 | print('{0:2d} {1} ({1}){2}'.format(step, hostname, rtt_str)) 78 | # The server ignored ping or lost 79 | else: 80 | print('{:2d} * * *'.format(step)) 81 | if reached: 82 | break 83 | 84 | # close socket 85 | s.close() 86 | 87 | if __name__ == '__main__': 88 | main() --------------------------------------------------------------------------------