├── .gitignore ├── README.md ├── gbn ├── client.py ├── data │ ├── client_push.txt │ └── server_push.txt ├── server.py └── util.py ├── proxy ├── cache.py ├── filter.json ├── main.py └── proxy.py ├── report ├── report1.doc ├── report2.doc ├── report3.doc └── report4.doc └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Pycharm config 94 | .idea 95 | 96 | # Myconfig 97 | proxy/cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《计算机网络》实验 2 | 3 | > 本项目为哈尔滨工业大学《计算机网络》实验的代码。使用Python语言进行编写。 4 | > ~~软件学院将Python作为主要工作语言的人并不是很多,希望你我能将这份信仰坚持下去。~~ 5 | > ~~人生苦短,我用Python!~~ 6 | 7 | 请在使用前用pip安装包依赖。 8 | 9 | (sudo) pip install -r requirements.txt 10 | 11 | ## 目录 12 | - [代理服务器](#代理服务器) 13 | - [可靠传输协议的实现](#可靠传输协议的实现) 14 | - [使用Wireshark分析网络协议](#使用Wireshark分析网络协议) 15 | - [简单网络组件与配置](#简单网络组件与配置) 16 | 17 | ## 代理服务器 18 | 19 | ### 基本功能 20 | 21 | - 代理服务器的基本功能 22 | - ~~缓存机制的实现~~(待完善) 23 | - 屏蔽特定用户、特定网站以及钓鱼功能 24 | 25 | ### 项目说明 26 | 27 | 本项目在`proxy`文件夹下。 28 | 运行`main.py`脚本打开代理服务器,默认端口为`5000`。其中`filter.json`为屏蔽&钓鱼功能的配置文件。`cache`目录为缓存的数据。 29 | 30 | ## 可靠传输协议的实现 31 | 32 | ### 基本功能 33 | 34 | - GBN协议的实现 35 | - 实现数据的双向传输 36 | - SR协议的实现 37 | 38 | ### 项目说明 39 | 40 | 本项目在`gbn`文件夹下。 41 | 其中`util.py`脚本为gbn协议和sr协议的基本实现。而`client.py`和`server.py`则为服务器和客户端的测试脚本。 42 | 基于20%的数据丢包率进行测试,采用双向传输,C->S和S->C分别使用GBN协议和SR协议进行测试。 43 | 44 | ### 改进空间 45 | 46 | 目前双向通信的实现一共使用了四个端口,实际上双向通信两个`socket`各自只需要一个端口即可。 47 | 48 | ## 使用Wireshark分析网络协议 49 | 50 | ### 基本功能 51 | 52 | - 使用Wireshark分析HTTP、TCP、IP协议 53 | - 使用Wireshark分析DNS、UDP、ARP协议 54 | 55 | ### 项目说明 56 | 57 | 在Ubuntu下可直接使用以下指令进行安装 58 | 59 | sudo apt-get install wireshark 60 | 61 | 在运行`wireshark`时,一般用户没有抓包的权限。 62 | 官方推荐使用`wireshark`用户组的用户进行抓包,不建议使用`superuser`。 63 | 如果仅为实验,方便起见可以使用超级管理员权限。 64 | 65 | sudo wireshark 66 | 67 | ## 简单网络组件与配置 68 | 69 | ### 基本功能 70 | 71 | 按照实验指导书所示的网路拓扑,选择实体设备并进行网络连接。 72 | 73 | ### 项目说明 74 | 75 | 细节可参照实验指导书,仅保留实验报告。 76 | 77 | -------------------------------------------------------------------------------- /gbn/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import socket 3 | import thread 4 | import server 5 | 6 | from util import * 7 | 8 | 9 | def new_client_socket(client_port, protocol): 10 | # 设置网络连接为ipv4, 传输层协议为tcp 11 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 12 | # 传输完成后立即回收该端口 13 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 14 | # 任意ip均可以访问 15 | s.bind(('', client_port)) 16 | 17 | p = protocol(s) 18 | p.pull_data() 19 | 20 | 21 | if __name__ == '__main__': 22 | 23 | # 接收方开启多线程 24 | thread.start_new_thread(server.new_server_socket, (SERVER_PORT_EXTRA, CLIENT_PORT_EXTRA, 'data/client_push.txt', Gbn)) 25 | 26 | # server.new_server_socket(SERVER_PORT_EXTRA, CLIENT_PORT_EXTRA, 'data/client_push.txt', Gbn) 27 | new_client_socket(CLIENT_PORT, Sr) 28 | 29 | -------------------------------------------------------------------------------- /gbn/data/client_push.txt: -------------------------------------------------------------------------------- 1 | x 2 | a 3 | f 4 | e 5 | g 6 | h 7 | e 8 | e 9 | r 10 | g 11 | g 12 | h 13 | w 14 | -------------------------------------------------------------------------------- /gbn/data/server_push.txt: -------------------------------------------------------------------------------- 1 | A 2 | F 3 | F 4 | T 5 | W 6 | Y 7 | R 8 | B 9 | B 10 | J 11 | D 12 | W 13 | Q 14 | D 15 | T 16 | Y 17 | Q -------------------------------------------------------------------------------- /gbn/server.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import socket 3 | import thread 4 | import client 5 | from util import * 6 | 7 | 8 | def new_server_socket(server_port, client_port, path, protocol): 9 | # 设置网络连接为ipv4, 传输层协议为udp 10 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 11 | # 传输完成后立即回收该端口 12 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 13 | # 任意ip均可以访问 14 | s.bind(('', server_port)) 15 | 16 | p = protocol(s) 17 | p.push_data(path, client_port) 18 | 19 | 20 | if __name__ == '__main__': 21 | 22 | thread.start_new_thread(new_server_socket, (SERVER_PORT, CLIENT_PORT, 'data/server_push.txt', Sr)) 23 | 24 | # new_server_socket(SERVER_PORT, CLIENT_PORT, 'data/server_push.txt', Sr) 25 | client.new_client_socket(CLIENT_PORT_EXTRA, Gbn) 26 | 27 | -------------------------------------------------------------------------------- /gbn/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import sys 3 | import select 4 | from random import random 5 | 6 | # 设置在localhost进行测试 7 | HOST = '127.0.0.1' 8 | 9 | # 设置服务器端与客户端的端口号 10 | SERVER_PORT = 5001 11 | CLIENT_PORT = 5002 12 | 13 | # 另开端口组实现双向通信 14 | SERVER_PORT_EXTRA = 5003 15 | CLIENT_PORT_EXTRA = 5004 16 | 17 | # 单次读取的最大字节数 18 | BUFFER_SIZE = 2048 19 | 20 | # 窗口与包序号长度 21 | WINDOWS_LENGTH = 8 22 | SEQ_LENGTH = 10 23 | 24 | # 最大延迟时间 25 | MAX_TIME = 3 26 | 27 | 28 | class Data(object): 29 | 30 | def __init__(self, msg, seq=0, state=0): 31 | self.msg = msg 32 | self.state = state 33 | self.seq = str(seq % SEQ_LENGTH) 34 | 35 | def __str__(self): 36 | return self.seq + ' ' + self.msg 37 | 38 | 39 | class Gbn(object): 40 | 41 | def __init__(self, s): 42 | self.s = s 43 | 44 | def push_data(self, path, port): 45 | 46 | # 计时和包序号初始化 47 | time = 0 48 | seq = 0 49 | 50 | data_windows = [] 51 | 52 | with open(path, 'r') as f: 53 | 54 | while True: 55 | 56 | # 当超时后,将窗口内的数据更改为未发送状态 57 | if time > MAX_TIME: 58 | for data in data_windows: 59 | data.state = 0 60 | 61 | # 窗口中数据少于最大容量时,尝试添加新数据 62 | while len(data_windows) < WINDOWS_LENGTH: 63 | line = f.readline().strip() 64 | 65 | if not line: 66 | break 67 | 68 | data = Data(line, seq=seq) 69 | data_windows.append(data) 70 | seq += 1 71 | 72 | # 窗口内无数据则退出总循环 73 | if not data_windows: 74 | break 75 | 76 | # 遍历窗口内数据,如果存在未成功发送的则发送 77 | for data in data_windows: 78 | if not data.state: 79 | self.s.sendto(str(data), (HOST, port)) 80 | data.state = 1 81 | 82 | # 无阻塞socket连接监控 83 | readable, writeable, errors = select.select([self.s, ], [], [], 1) 84 | 85 | if len(readable) > 0: 86 | 87 | # 收到数据则重新计时 88 | time = 0 89 | 90 | message, address = self.s.recvfrom(BUFFER_SIZE) 91 | sys.stdout.write('ACK ' + message + '\n') 92 | 93 | for i in range(len(data_windows)): 94 | if message == data_windows[i].seq: 95 | data_windows = data_windows[i+1:] 96 | break 97 | else: 98 | # 未收到数据则计时器加一 99 | time += 1 100 | 101 | self.s.close() 102 | 103 | def pull_data(self): 104 | 105 | # 记录上一个回执的ack的值 106 | last_ack = SEQ_LENGTH - 1 107 | 108 | data_windows = [] 109 | 110 | while True: 111 | 112 | readable, writeable, errors = select.select([self.s, ], [], [], 1) 113 | 114 | if len(readable) > 0: 115 | message, address = self.s.recvfrom(BUFFER_SIZE) 116 | 117 | ack = int(message.split()[0]) 118 | 119 | # 连续接收数据则反馈当前ack 120 | if last_ack == (ack - 1) % SEQ_LENGTH: 121 | 122 | # 丢包率为0.2 123 | if random() < 0.2: 124 | continue 125 | 126 | self.s.sendto(str(ack), address) 127 | last_ack = ack 128 | 129 | # 判断数据是否重复 130 | if ack not in data_windows: 131 | data_windows.append(ack) 132 | sys.stdout.write(message + '\n') 133 | 134 | while len(data_windows) > WINDOWS_LENGTH: 135 | data_windows.pop(0) 136 | else: 137 | self.s.sendto(str(last_ack), address) 138 | 139 | self.s.close() 140 | 141 | 142 | class Sr(object): 143 | 144 | def __init__(self, s): 145 | self.s = s 146 | 147 | def push_data(self, path, port): 148 | 149 | # 计时和包序号初始化 150 | time = 0 151 | seq = 0 152 | 153 | data_windows = [] 154 | 155 | with open(path, 'r') as f: 156 | 157 | while True: 158 | 159 | # 当超时后,将窗口内第一个发送成功未确认的数据状态更改为未发送 160 | if time > MAX_TIME: 161 | for data in data_windows: 162 | if data.state == 1: 163 | data.state = 0 164 | break 165 | 166 | # 窗口中数据少于最大容量时,尝试添加新数据 167 | while len(data_windows) < WINDOWS_LENGTH: 168 | line = f.readline().strip() 169 | 170 | if not line: 171 | break 172 | 173 | data = Data(line, seq=seq) 174 | data_windows.append(data) 175 | seq += 1 176 | 177 | # 窗口内无数据则退出总循环 178 | if not data_windows: 179 | break 180 | 181 | # 遍历窗口内数据,如果存在未成功发送的则发送 182 | for data in data_windows: 183 | if not data.state: 184 | self.s.sendto(str(data), (HOST, port)) 185 | data.state = 1 186 | 187 | readable, writeable, errors = select.select([self.s, ], [], [], 1) 188 | 189 | if len(readable) > 0: 190 | 191 | # 收到数据则重新计时 192 | time = 0 193 | 194 | message, address = self.s.recvfrom(BUFFER_SIZE) 195 | sys.stdout.write('ACK ' + message + '\n') 196 | 197 | # 收到数据后更改该数据包状态为已接收 198 | for data in data_windows: 199 | if message == data.seq: 200 | data.state = 2 201 | break 202 | else: 203 | # 未收到数据则计时器加一 204 | time += 1 205 | 206 | # 当窗口中首个数据已接收时,窗口前移 207 | while data_windows[0].state == 2: 208 | data_windows.pop(0) 209 | 210 | if not data_windows: 211 | break 212 | 213 | self.s.close() 214 | 215 | def pull_data(self): 216 | 217 | # 窗口的初始序号 218 | seq = 0 219 | data_windows = {} 220 | 221 | while True: 222 | 223 | readable, writeable, errors = select.select([self.s, ], [], [], 1) 224 | 225 | if len(readable) > 0: 226 | message, address = self.s.recvfrom(BUFFER_SIZE) 227 | 228 | ack = message.split()[0] 229 | 230 | # 丢包率为0.2 231 | if random() < 0.2: 232 | continue 233 | 234 | # 返回成功接收的包序号 235 | self.s.sendto(ack, address) 236 | data_windows[ack] = message.split()[1] 237 | 238 | # 滑动窗口 239 | while str(seq) in data_windows: 240 | sys.stdout.write(str(seq) + ' ' + data_windows[str(seq)] + '\n') 241 | data_windows.pop(str(seq)) 242 | seq = (seq + 1) % SEQ_LENGTH 243 | 244 | self.s.close() 245 | 246 | -------------------------------------------------------------------------------- /proxy/cache.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import time 4 | import requests 5 | 6 | 7 | class Cache(object): 8 | 9 | def __init__(self, url): 10 | self.url = url 11 | self.name = url[:50].replace('/', '%') 12 | 13 | self.__check_exist_cache_dir() 14 | 15 | def is_cache_exist(self): 16 | ''' 17 | 检测缓存是否存在 18 | ''' 19 | if os.path.exists('./cache/' + self.name): 20 | return self.check_cache() 21 | 22 | else: 23 | with open('./cache/' + self.name, 'w') as f: 24 | return False 25 | 26 | def check_cache(self): 27 | ''' 28 | 向远程发送请求检查缓存是否可用 29 | ''' 30 | file_time = os.stat('./cache/' + self.name).st_mtime 31 | # 将文件最终修改时间转化为GMT格式的时间 32 | headers = {'If-Modified-Since': time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(file_time))} 33 | if requests.get(self.url, headers=headers).status_code == 304: 34 | return True 35 | else: 36 | with open('./cache/' + self.name, 'w') as f: 37 | return False 38 | 39 | def update_cache(self, data): 40 | ''' 41 | 进行缓存 42 | ''' 43 | with open('./cache/' + self.name, 'a') as f: 44 | f.write(data) 45 | 46 | def __check_exist_cache_dir(self): 47 | ''' 48 | 创建缓存目录 49 | ''' 50 | if not os.path.exists('./cache'): 51 | os.mkdir('./cache') 52 | 53 | if __name__ == '__main__': 54 | pass 55 | -------------------------------------------------------------------------------- /proxy/filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": [ 3 | "pediy.com" 4 | ], 5 | "ip": [ 6 | 7 | ], 8 | "fishing": { 9 | "www.fudan.edu.cn": "www.hit.edu.cn" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /proxy/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | import socket 4 | import thread 5 | from proxy import Proxy 6 | 7 | PORT = 5000 8 | THREAD = 2 9 | 10 | 11 | if __name__ == '__main__': 12 | 13 | # 设置网络连接为ipv4, 传输层协议为tcp 14 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 15 | # 传输完成后立即回收该端口 16 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 17 | # 任意ip均可以访问 18 | s.bind(('', PORT)) 19 | # 控制队列中可等待的最大链接 20 | s.listen(THREAD) 21 | 22 | while 1: 23 | # print 'Create new thread.\n' 24 | thread.start_new_thread(Proxy(s).run, ()) -------------------------------------------------------------------------------- /proxy/proxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import sys 3 | import json 4 | import socket 5 | import select 6 | from cache import Cache 7 | from urlparse import urlparse 8 | 9 | 10 | class Proxy(object): 11 | """ 12 | 支持http协议的代理服务 (暂不支持https) 13 | """ 14 | BUFFER_SIZE = 2048 15 | HTTP_METHOD = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'] 16 | HTTPS_METHOD = ['CONNECT'] 17 | 18 | def __init__(self, server_sock): 19 | self.client_sock, (self.client_ip, self.client_port) = server_sock.accept() 20 | 21 | def parse_method(self): 22 | 23 | self.request = self.client_sock.recv(self.BUFFER_SIZE) # 获取HTTP请求报文 24 | 25 | if not self.request: # 未获取报文直接返回 26 | return 27 | 28 | # print self.request 29 | lines = self.request.split('\r\n') 30 | self.method = lines[0].split()[0] # 解析method,区分协议 31 | 32 | def run(self): 33 | self.parse_method() 34 | if self.request: 35 | if self.method in self.HTTP_METHOD: 36 | self.http_request() 37 | elif self.method in self.HTTPS_METHOD: 38 | self.https_request() 39 | 40 | def http_request(self): 41 | 42 | self.parse_url_http() 43 | 44 | # 判断是否在防火墙里 45 | if not self.filter_fire_wall(): 46 | 47 | # 确认该host和port可连接 48 | try: 49 | addr_info = socket.getaddrinfo(self.host, self.port)[0] 50 | except socket.gaierror, e: 51 | print 'This site have error!!!!' 52 | print 'The host is ' + self.host 53 | print 'The port is ' + str(self.port) 54 | print 'The url is ' + self.url 55 | print 'The address information is ' + addr_info 56 | sys.exit(1) 57 | else: 58 | # 对目标服务器建立socket连接 59 | self.target_sock = socket.socket(addr_info[0], 1) 60 | self.target_sock.connect(addr_info[4]) 61 | self.target_sock.send(self.request) 62 | self.return_data() 63 | 64 | def parse_url_http(self): 65 | 66 | lines = self.request.split('\r\n') 67 | self.url = lines[0].split()[1] 68 | 69 | parse_url = urlparse(self.url) 70 | self.host = parse_url.netloc 71 | self.path = parse_url.path 72 | 73 | if ':' in self.host: # 处理url附带端口的情况 74 | self.port = self.host.split(':')[1] 75 | self.host = self.host.split(':')[0] 76 | else: 77 | self.port = 80 # http协议默认端口为80 78 | 79 | print self.method, self.url 80 | # print self.host, self.client_ip 81 | 82 | def https_request(self): 83 | pass 84 | 85 | def return_data(self): 86 | 87 | cache = Cache(url=self.url) 88 | 89 | if cache.is_cache_exist(): 90 | print 'Cache Successful!!!' 91 | 92 | inputs = [self.target_sock, self.client_sock] 93 | 94 | fish_data = self.fish() 95 | # 判断是否为钓鱼网站 96 | if fish_data: 97 | self.client_sock.send(fish_data) 98 | else: 99 | while True: 100 | # 监控可读的socket对象, 利用select实现非阻塞读取 101 | readable, writeable, errors = select.select(inputs, [], inputs, 3) 102 | 103 | # 存在错误则直接退出 104 | if errors: 105 | break 106 | 107 | last_url = '' 108 | # 建立过tcp连接后则直接交换客户端和目标服务器的数据 109 | for socket in readable: 110 | data = socket.recv(self.BUFFER_SIZE) 111 | if data: 112 | if socket is self.target_sock: 113 | self.client_sock.send(data) 114 | cache.update_cache(data) 115 | elif socket is self.client_sock: 116 | self.target_sock.send(data) 117 | else: 118 | break 119 | 120 | # 无数据传输后关闭socket连接 121 | self.client_sock.close() 122 | self.target_sock.close() 123 | 124 | def filter_fire_wall(self): 125 | # 防火墙,限制用户ip以及访问的网站 126 | 127 | with open('filter.json', 'r') as f: 128 | filter_json = json.load(f) 129 | 130 | if self.client_ip in filter_json['ip']: 131 | print 'Filter client ip %s.' % self.client_ip 132 | return True 133 | 134 | for filter_host in filter_json['host']: 135 | if self.host.endswith(filter_host): 136 | print 'Filter host %s.' % filter_host 137 | return True 138 | 139 | return False 140 | 141 | def fish(self): 142 | 143 | # 钓鱼网站 144 | with open('filter.json', 'r') as f: 145 | 146 | filter_json = json.load(f) 147 | 148 | for fish in filter_json['fishing']: 149 | if self.host == fish and self.path == '/': 150 | 151 | # 返回重定向相应 152 | return str('HTTP/1.1 302 Moved Temporarily\r\nLocation: http://' + \ 153 | filter_json['fishing'][self.host] + '\r\n\r\n') 154 | 155 | return False -------------------------------------------------------------------------------- /report/report1.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bicongwang/hit-computer_network/83b91dfca337013533913159bd5fe458795d1e91/report/report1.doc -------------------------------------------------------------------------------- /report/report2.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bicongwang/hit-computer_network/83b91dfca337013533913159bd5fe458795d1e91/report/report2.doc -------------------------------------------------------------------------------- /report/report3.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bicongwang/hit-computer_network/83b91dfca337013533913159bd5fe458795d1e91/report/report3.doc -------------------------------------------------------------------------------- /report/report4.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bicongwang/hit-computer_network/83b91dfca337013533913159bd5fe458795d1e91/report/report4.doc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.11.1 2 | --------------------------------------------------------------------------------