├── .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()
--------------------------------------------------------------------------------