diff --git a/README.md b/README.md index e448842..b2ed813 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,57 @@ -# my-tor-core +# xiaomian DNS -本项目是计算机网络的课程设计项目,该项目是一个类 Tor 的网络通信协议,旨在保护用户的隐私和匿名性。 +本项目是计算机网络的课程设计项目,该项目是一个私有DNS的简单实现。 ## 项目原理 -本项目目标是实现三层代理 + dns 解析自定义域名.xiaomian + 搭建匿名网站。 -server路径下包含一个目录服务器,用于创建目录服务器,记录加入节点。 -client路径下是客户端程序,客户端程序通过访问目录服务器获取当前的路由,并通过随机路由算法选择代理节点。 +在server/xiaomiandns.py中实现了DNSserver和APIserver两个类。通过server/main.py启动实例化的server。配置文件在server/serverconf.yaml 本项目选择sqlite作为数据库,存储节点信息等数据。 ## 环境依赖 - 该项目依赖以下软件: -python 3.11 -sqlite3 +python 3 ## 安装步骤 +```console +# 安装依赖 +pip install -r requirements.txt + +#初始化数据库 +python3 database/initdb.py +``` ## 使用说明 -本项目中包含三种角色,client, node和server。每种角色运行所需要的代码在相应的项目文件夹下面。 -client: 即客户端。可以通过用户端连接小面网络、创建小面网站、访问别人创建的小面网站。 -node: 即代理节点。运行此程序可以将计算机加入小面网络中,代理连接流量 -server: 即DNS服务器和小面网站目录服务器。运行此程序可以作为server接受请求。 +```python +python3 server/main.py +``` +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记录返回,还未实现添加其他解析记录。 ## 许可证 diff --git a/api_add.png b/api_add.png new file mode 100644 index 0000000..f546b35 Binary files /dev/null and b/api_add.png differ diff --git a/database/initdb.py b/database/initdb.py index 0a22853..2fe1dd7 100644 --- a/database/initdb.py +++ b/database/initdb.py @@ -1,15 +1,33 @@ import sqlite3 +import argparse # 用于创建 db_file = 'database/dns.db' if __name__ == '__main__': conn = sqlite3.connect(db_file) cursor = conn.cursor() + parser = argparse.ArgumentParser() + parser.add_argument('--add', action='store_true', help='add test data') + args = parser.parse_args() try: cursor.execute( '''CREATE TABLE xiaomiandns(domain TEXT PRIMARY KEY, ip TEXT,timestamp DATETIME)''') except sqlite3.OperationalError: print("table xiaomiandns already exists") 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() conn.close() diff --git a/database_add.png b/database_add.png new file mode 100644 index 0000000..c53d2c2 Binary files /dev/null and b/database_add.png differ diff --git a/dns1.png b/dns1.png new file mode 100644 index 0000000..cf22feb Binary files /dev/null and b/dns1.png differ diff --git a/dns2.png b/dns2.png new file mode 100644 index 0000000..22085a9 Binary files /dev/null and b/dns2.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5fbaa94 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +dnspython==2.3.0 +PyYAML==6.0 diff --git a/server/main.py b/server/main.py index cb84afe..771a8a2 100644 --- a/server/main.py +++ b/server/main.py @@ -3,7 +3,7 @@ import yaml if __name__ == '__main__': - with open('serverconf.yaml', 'r') as f: + with open('server/serverconf.yaml', 'r') as f: config = yaml.safe_load(f) db_file = config['database']['db_file'] DNS_port = config['DNS']['port'] @@ -11,5 +11,10 @@ if __name__ == '__main__': API_port = config['API']['port'] API_listen_host = config['API']['listen_host'] - DNSServer = xiaomiandns.DNSServer(DNS_listen_host, DNS_port, db_file) - DNSServer.run() + # start dns server + 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() diff --git a/server/serverconf.yaml b/server/serverconf.yaml index 6959728..7458cda 100644 --- a/server/serverconf.yaml +++ b/server/serverconf.yaml @@ -1,5 +1,5 @@ database: - db_file : '../database/dns.db' + db_file : 'database/dns.db' DNS: port : 53 listen_host : "0.0.0.0" diff --git a/server/xiaomiandns.py b/server/xiaomiandns.py index baf5c58..4f2fbce 100644 --- a/server/xiaomiandns.py +++ b/server/xiaomiandns.py @@ -7,12 +7,8 @@ import dns.rdatatype import dns.flags import dns.rcode import dns.rrset -import time import sqlite3 import re -import base64 -from cryptography.hazmat.primitives.asymmetric import rsa, padding -from cryptography.hazmat.primitives import serialization, hashes import yaml @@ -74,7 +70,7 @@ class DNSServer: name = question.question[0].name # search domain in database 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() # Create a new RRset for the answer @@ -135,45 +131,37 @@ class DNSAPI: data = request.split('\r\n')[-1] response = self.handle_post_request(url, data) else: - response = self.handle_error_request(url) + response = self.handle_error_request() return response def handle_get_request(self, url): - - # check url start with /add - # 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' - + status_code = 400 + reason_phrase = 'unsupport method, please use POST method' response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) return response.encode("utf-8") - def handle_post_request(self, url, data): - # 处理 POST 请求,data 是 POST 方法提交的数据 + def handle_post_request(self, url:str, data:str)->str: + """处理 POST 请求 + Args: + url (str): url前缀 + data (str): POST 方法提交的数据 + + Returns: + str: http response + """ + # check url start with /add - if re.match(r'^/add\?', url): + if re.match(r'^/add', url): status_code = self.add_data(data) 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): + elif re.match(r'^/delete', url): status_code = self.delete_data(data) if status_code == 200: reason_phrase = 'Delete data successful' @@ -186,7 +174,7 @@ class DNSAPI: response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) return response.encode("utf-8") - def handle_error_request(self, request): + def handle_error_request(self): status_code = 400 reason_phrase = "unsupport method" headers = { @@ -194,10 +182,20 @@ class DNSAPI: 'Connection': 'close', } 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") - 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 domain, ip = self.parse_data(data) @@ -208,23 +206,33 @@ class DNSAPI: conn = sqlite3.connect(self.db_file) c = conn.cursor() - # Check if the data already exists + # Check if the domain already exists c.execute( - "SELECT * FROM xiaomiandns WHERE domain = ? OR ip = ?", (domain, ip)) - existing_data = c.fetchall() + "SELECT * FROM xiaomiandns WHERE domain = ?", [domain]) + existing_domain = c.fetchall() + - c.close() - conn.close() - - if existing_data: - return 400 + if existing_domain: + c.execute("UPDATE xiaomiandns SET ip = ?, timestamp = DATETIME('now') WHERE domain = ?",(ip,domain)) else: # Insert the new data c.execute( "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: + """delete record in database + + Args: + data (str): domain and ip + + Returns: + int: status code + """ # parse and check validation domain, ip = self.parse_data(data) @@ -232,14 +240,23 @@ class DNSAPI: return 400 # connect db - conn = sqlite3.connect(self.db_file) c = conn.cursor() 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() conn.close() - return 200 + + return status_code def parse_data(self, data:str) -> str : """parse data form post data @@ -257,7 +274,6 @@ class DNSAPI: if domain and ip: domain = domain.group(1) ip = ip.group(1) - return domain, ip def check_data(self, domain, ip):