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