modified: README.md

new file:   api_add.png
	modified:   database/initdb.py
	new file:   database_add.png
	new file:   dns1.png
	new file:   dns2.png
	new file:   requirements.txt
	modified:   server/main.py
	modified:   server/serverconf.yaml
	modified:   server/xiaomiandns.py
This commit is contained in:
Smart-SangGe 2023-06-09 17:44:51 +08:00
parent 647c92c8dc
commit 6cd279e88c
10 changed files with 131 additions and 60 deletions

View File

@ -1,27 +1,57 @@
# my-tor-core # xiaomian DNS
本项目是计算机网络的课程设计项目,该项目是一个类 Tor 的网络通信协议,旨在保护用户的隐私和匿名性 本项目是计算机网络的课程设计项目,该项目是一个私有DNS的简单实现
## 项目原理 ## 项目原理
本项目目标是实现三层代理 + dns 解析自定义域名.xiaomian + 搭建匿名网站。 在server/xiaomiandns.py中实现了DNSserver和APIserver两个类。通过server/main.py启动实例化的server。配置文件在server/serverconf.yaml
server路径下包含一个目录服务器用于创建目录服务器记录加入节点。
client路径下是客户端程序客户端程序通过访问目录服务器获取当前的路由并通过随机路由算法选择代理节点。
本项目选择sqlite作为数据库存储节点信息等数据。 本项目选择sqlite作为数据库存储节点信息等数据。
## 环境依赖 ## 环境依赖
该项目依赖以下软件: 该项目依赖以下软件:
python 3.11 python 3
sqlite3
## 安装步骤 ## 安装步骤
```console
# 安装依赖
pip install -r requirements.txt
#初始化数据库
python3 database/initdb.py
```
## 使用说明 ## 使用说明
本项目中包含三种角色client, node和server。每种角色运行所需要的代码在相应的项目文件夹下面。 ```python
client 即客户端。可以通过用户端连接小面网络、创建小面网站、访问别人创建的小面网站。 python3 server/main.py
node 即代理节点。运行此程序可以将计算机加入小面网络中,代理连接流量 ```
server 即DNS服务器和小面网站目录服务器。运行此程序可以作为server接受请求。 dns默认端口为53域名api默认端口为81
需要添加数据可以通过post方法向API提交数据
usage: use POST method
/add
data: domian=xxxx&ip=xx.xx.xx.xx
/delete
data: domian=xxxx&ip=xx.xx.xx.xx
注意域名只能以xiaomian作为根域名
# 测试DNS功能
```console
# Linux
sudo apt install dnsutils
dig @DNS_SERVER -p PORT DOMAIN
# Windows
nslookup DOMAIN DNS_SERVER
```
# 测试API功能
```console
# Linux
curl -d "domain=qqqwwweee.xiaomian&ip=123.12.23.34" -X POST http://10.20.117.208:81/add -i
# Windows
Invoke-WebRequest工具一直收不到post的body不知道问题出在哪里
## 未实现的功能
目前只能把解析记录作为a记录返回还未实现添加其他解析记录。
## 许可证 ## 许可证

BIN
api_add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -1,15 +1,33 @@
import sqlite3 import sqlite3
import argparse
# 用于创建 # 用于创建
db_file = 'database/dns.db' db_file = 'database/dns.db'
if __name__ == '__main__': if __name__ == '__main__':
conn = sqlite3.connect(db_file) conn = sqlite3.connect(db_file)
cursor = conn.cursor() cursor = conn.cursor()
parser = argparse.ArgumentParser()
parser.add_argument('--add', action='store_true', help='add test data')
args = parser.parse_args()
try: try:
cursor.execute( cursor.execute(
'''CREATE TABLE xiaomiandns(domain TEXT PRIMARY KEY, ip TEXT,timestamp DATETIME)''') '''CREATE TABLE xiaomiandns(domain TEXT PRIMARY KEY, ip TEXT,timestamp DATETIME)''')
except sqlite3.OperationalError: except sqlite3.OperationalError:
print("table xiaomiandns already exists") print("table xiaomiandns already exists")
conn.commit() conn.commit()
if args.add:
test_data = [
('example.xiaomian', '192.168.1.1'),
('google.xiaomian', '8.8.8.8'),
('yahoo.xiaomian', '98.138.219.231')
]
for data in test_data:
domain, ip = data
cursor.execute("INSERT INTO xiaomiandns (domain, ip, timestamp) VALUES (?, ?, DATETIME('now'))", (domain, ip))
# 提交更改到数据库
conn.commit()
cursor.close() cursor.close()
conn.close() conn.close()

BIN
database_add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dns1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

BIN
dns2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
dnspython==2.3.0
PyYAML==6.0

View File

@ -3,7 +3,7 @@ import yaml
if __name__ == '__main__': if __name__ == '__main__':
with open('serverconf.yaml', 'r') as f: with open('server/serverconf.yaml', 'r') as f:
config = yaml.safe_load(f) config = yaml.safe_load(f)
db_file = config['database']['db_file'] db_file = config['database']['db_file']
DNS_port = config['DNS']['port'] DNS_port = config['DNS']['port']
@ -11,5 +11,10 @@ if __name__ == '__main__':
API_port = config['API']['port'] API_port = config['API']['port']
API_listen_host = config['API']['listen_host'] API_listen_host = config['API']['listen_host']
DNSServer = xiaomiandns.DNSServer(DNS_listen_host, DNS_port, db_file) # start dns server
DNSServer.run() server = xiaomiandns.DNSServer(DNS_listen_host, DNS_port, db_file)
server.run()
# start dns api server
APIserver = xiaomiandns.DNSAPI(API_listen_host, API_port, db_file)
APIserver.run()

View File

@ -1,5 +1,5 @@
database: database:
db_file : '../database/dns.db' db_file : 'database/dns.db'
DNS: DNS:
port : 53 port : 53
listen_host : "0.0.0.0" listen_host : "0.0.0.0"

View File

@ -7,12 +7,8 @@ import dns.rdatatype
import dns.flags import dns.flags
import dns.rcode import dns.rcode
import dns.rrset import dns.rrset
import time
import sqlite3 import sqlite3
import re import re
import base64
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
import yaml import yaml
@ -74,7 +70,7 @@ class DNSServer:
name = question.question[0].name name = question.question[0].name
# search domain in database # search domain in database
dbcursor.execute( dbcursor.execute(
"SELECT ip FROM xiaomiandns WHERE domain = ? AND nodetype = client", (str(name)[:-1],)) "SELECT ip FROM xiaomiandns WHERE domain = ?", (str(name)[:-1],))
result = dbcursor.fetchone() result = dbcursor.fetchone()
# Create a new RRset for the answer # Create a new RRset for the answer
@ -135,45 +131,37 @@ class DNSAPI:
data = request.split('\r\n')[-1] data = request.split('\r\n')[-1]
response = self.handle_post_request(url, data) response = self.handle_post_request(url, data)
else: else:
response = self.handle_error_request(url) response = self.handle_error_request()
return response return response
def handle_get_request(self, url): def handle_get_request(self, url):
status_code = 400
# check url start with /add reason_phrase = 'unsupport method, please use POST method'
# if re.match(r'^/add\?', url):
# status_code = self.add_data(url[5:])
# if status_code == 200:
# reason_phrase = 'Add data successful'
# else:
# reason_phrase = 'Add data unsuccessful'
# # check url start with /delete
# elif re.match(r'^/delete\?', url):
# status_code = self.delete_data(url[9:])
# if status_code == 200:
# reason_phrase = 'Delete data successful'
# else:
# reason_phrase = 'Delete data unsuccessful'
# else:
# status_code = 400
# reason_phrase = 'unsupport api'
response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase)
return response.encode("utf-8") return response.encode("utf-8")
def handle_post_request(self, url, data): def handle_post_request(self, url:str, data:str)->str:
# 处理 POST 请求data 是 POST 方法提交的数据 """处理 POST 请求
Args:
url (str): url前缀
data (str): POST 方法提交的数据
Returns:
str: http response
"""
# check url start with /add # check url start with /add
if re.match(r'^/add\?', url): if re.match(r'^/add', url):
status_code = self.add_data(data) status_code = self.add_data(data)
if status_code == 200: if status_code == 200:
reason_phrase = 'Add data successful' reason_phrase = 'Add data successful'
else: else:
reason_phrase = 'Add data unsuccessful' reason_phrase = 'Add data unsuccessful'
# check url start with /delete # check url start with /delete
elif re.match(r'^/delete\?', url): elif re.match(r'^/delete', url):
status_code = self.delete_data(data) status_code = self.delete_data(data)
if status_code == 200: if status_code == 200:
reason_phrase = 'Delete data successful' reason_phrase = 'Delete data successful'
@ -186,7 +174,7 @@ class DNSAPI:
response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase)
return response.encode("utf-8") return response.encode("utf-8")
def handle_error_request(self, request): def handle_error_request(self):
status_code = 400 status_code = 400
reason_phrase = "unsupport method" reason_phrase = "unsupport method"
headers = { headers = {
@ -194,10 +182,20 @@ class DNSAPI:
'Connection': 'close', 'Connection': 'close',
} }
response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase)
for key, value in headers.items():
response += '{}: {}\r\n'.format(key, value)
response += '\r\n'
return response.encode("utf-8") return response.encode("utf-8")
def add_data(self, data): def add_data(self, data:str)->int:
"""add data to database
Args:
data (str): domain and ip
Returns:
int: status code
"""
# parse and check validation # parse and check validation
domain, ip = self.parse_data(data) domain, ip = self.parse_data(data)
@ -208,23 +206,33 @@ class DNSAPI:
conn = sqlite3.connect(self.db_file) conn = sqlite3.connect(self.db_file)
c = conn.cursor() c = conn.cursor()
# Check if the data already exists # Check if the domain already exists
c.execute( c.execute(
"SELECT * FROM xiaomiandns WHERE domain = ? OR ip = ?", (domain, ip)) "SELECT * FROM xiaomiandns WHERE domain = ?", [domain])
existing_data = c.fetchall() existing_domain = c.fetchall()
c.close() if existing_domain:
conn.close() c.execute("UPDATE xiaomiandns SET ip = ?, timestamp = DATETIME('now') WHERE domain = ?",(ip,domain))
if existing_data:
return 400
else: else:
# Insert the new data # Insert the new data
c.execute( c.execute(
"INSERT INTO xiaomiandns (domain,ip,timestamp) VALUES (?,?,DATETIME('now'))", (domain, ip)) "INSERT INTO xiaomiandns (domain,ip,timestamp) VALUES (?,?,DATETIME('now'))", (domain, ip))
return 200 conn.commit()
c.close()
conn.close()
status_code = 200
return status_code
def delete_data(self, data:str) -> int: def delete_data(self, data:str) -> int:
"""delete record in database
Args:
data (str): domain and ip
Returns:
int: status code
"""
# parse and check validation # parse and check validation
domain, ip = self.parse_data(data) domain, ip = self.parse_data(data)
@ -232,14 +240,23 @@ class DNSAPI:
return 400 return 400
# connect db # connect db
conn = sqlite3.connect(self.db_file) conn = sqlite3.connect(self.db_file)
c = conn.cursor() c = conn.cursor()
c.execute( c.execute(
"DELETE FROM xiaomiandns WHERE domain = ? AND ip = ?", (domain, ip)) "DELETE FROM xiaomiandns WHERE domain = ? ", [domain])
deleted_rows = c.rowcount
if deleted_rows == 0:
# unmatch
status_code = 400
else:
# deleted
status_code = 200
conn.commit()
c.close() c.close()
conn.close() conn.close()
return 200
return status_code
def parse_data(self, data:str) -> str : def parse_data(self, data:str) -> str :
"""parse data form post data """parse data form post data
@ -257,7 +274,6 @@ class DNSAPI:
if domain and ip: if domain and ip:
domain = domain.group(1) domain = domain.group(1)
ip = ip.group(1) ip = ip.group(1)
return domain, ip return domain, ip
def check_data(self, domain, ip): def check_data(self, domain, ip):