diff --git a/README.md b/README.md index f673691..d12fa32 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## 项目原理 -本项目目标是实现一层代理 + dns 解析自定义域名.xiaomian + 搭建匿名网站。 +本项目目标是实现三层代理 + dns 解析自定义域名.xiaomian + 搭建匿名网站。 server路径下包含一个目录服务器,用于创建目录服务器,记录加入节点。 client路径下是客户端程序,客户端程序通过访问目录服务器获取当前的路由,并通过随机路由算法选择代理节点。 本项目选择sqlite作为数据库,存储节点信息等数据。 @@ -13,6 +13,7 @@ client路径下是客户端程序,客户端程序通过访问目录服务器 该项目依赖以下软件: python 3.11 +sqlite3 ## 安装步骤 diff --git a/client/dnssender.py b/client/dnssender.py new file mode 100644 index 0000000..4a689f4 --- /dev/null +++ b/client/dnssender.py @@ -0,0 +1,22 @@ +import dns.resolver + + +def resolver(domain): + # 构造 DNS 查询请求 + qtype = 'A' + + # 发送 DNS 查询请求 + resolver = dns.resolver.Resolver() + resolver.nameservers = ["127.0.0.1"] + + try: + ip = resolver.resolve(domain, qtype)[0] + return ip + except dns.resolver.NXDOMAIN: + print("can't find IP") + + +if __name__ = "__main__": + domain = 'mamahaha.work' + ip = resolver(domain) + print(ip) \ No newline at end of file diff --git a/client/main.py b/client/main.py new file mode 100644 index 0000000..92b7721 --- /dev/null +++ b/client/main.py @@ -0,0 +1,51 @@ +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import serialization, hashes +import random + +# 生产随机域名 +def generate_domain(): + domain = random.getrandbits(64) + domain = hex(domain)[2:] + return domain + ".xiaomian" + + +# Generate a new RSA key pair +private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048 +) +public_key = private_key.public_key() + +# Convert the public key to bytes +public_key_bytes = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo +) + +# Encrypt a message using the public key +message = b"Hello World" +encrypted_message = public_key.encrypt( + message, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) +) + +# Decrypt the message using the private key +decrypted_message = private_key.decrypt( + encrypted_message, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None + ) +) +print(decrypted_message) + + +if __name__ == '__main__': + print("Welcome to my xiaomiao tor network") + domain = generate_domain() + print(domain) diff --git a/database/dns.db b/database/dns.db new file mode 100644 index 0000000..a8e3ee5 Binary files /dev/null and b/database/dns.db differ diff --git a/database/initdb.py b/database/initdb.py new file mode 100644 index 0000000..997ab82 --- /dev/null +++ b/database/initdb.py @@ -0,0 +1,13 @@ +import sqlite3 + +db_file = 'dns.db' +if __name__ == '__main__': + conn = sqlite3.connect(db_file) + cursor = conn.cursor() + try: + cursor.execute('''CREATE TABLE xiaomiandns(domain TEXT PRIMARY KEY, ip TEXT, pubkey TEXT)''') + except sqlite3.OperationalError: + print("table xiaomiandns already exists") + conn.commit() + cursor.close() + conn.close() \ No newline at end of file diff --git a/server/main.py b/server/main.py new file mode 100644 index 0000000..3a40871 --- /dev/null +++ b/server/main.py @@ -0,0 +1,10 @@ +import xiaomiandns +import asyncio + +if __name__ == '__main__': + db_file = '../database/dns.db' + DNS_port = 53 + listen_host= "0.0.0.0" + + DNSServer = xiaomiandns.DNSServer(listen_host, DNS_port, db_file) + DNSServer.run() diff --git a/server/xiaomiandns.py b/server/xiaomiandns.py new file mode 100644 index 0000000..d710867 --- /dev/null +++ b/server/xiaomiandns.py @@ -0,0 +1,200 @@ +import socket +import threading +import dns.resolver +import dns.message +import dns.rdataclass +import dns.rdatatype +import dns.flags +import dns.rcode +import dns.rrset +import time +import sqlite3 +import re + + +class DNSServer: + def __init__(self, hostname, port, db_file): + self.hostname = hostname + self.port = port + self.db_file = db_file + + def run(self): + self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.udp_socket.bind((self.hostname, self.port)) + self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.tcp_socket.bind((self.hostname, self.port)) + self.tcp_socket.listen(1) + print(f"DNS server running on {self.hostname}:{self.port}") + for i in range(3): + udp_thread = threading.Thread(target=self.handle_udp_request) + udp_thread.start() + tcp_thread = threading.Thread(target=self.handle_tcp_request) + tcp_thread.start() + + def handle_udp_request(self): + data, address = self.udp_socket.recvfrom(1024) + response = self.handle_request(data) + self.udp_socket.sendto(response, address) + udp_thread = threading.Thread(target=self.handle_udp_request) + udp_thread.start() + + def handle_tcp_request(self): + connection, address = self.tcp_socket.accept() + data = connection.recv(1024) + response = self.handle_request(data) + connection.send(response) + connection.close() + tcp_thread = threading.Thread(target=self.handle_tcp_request) + tcp_thread.start() + + def handle_request(self, data): + conn = sqlite3.connect(self.db_file) + cur = conn.cursor() + question = dns.message.from_wire(data) + response = self.build_response(question, cur) + return response + + def build_response(self, question, dbcursor, rcode=dns.rcode.NOERROR, answer=None): + # Create a new DNS message object + response = dns.message.Message() + + # Set the message header fields + response.id = question.id + response.flags = dns.flags.QR | dns.flags.RA + + # Add the question to the message + response.question = question.question + + name = question.question[0].name + # search domain in database + dbcursor.execute( + "SELECT ip FROM xiaomiandns WHERE domain = ?", (str(name)[:-1],)) + result = dbcursor.fetchone() + + # Create a new RRset for the answer + if result is not None: + answer = dns.rrset.RRset(name, dns.rdataclass.IN, dns.rdatatype.A) + rdata = dns.rdata.from_text( + dns.rdataclass.IN, dns.rdatatype.A, result[0]) + answer.add(rdata) + response.answer.append(answer) + # Set the response code + response.set_rcode(rcode) + else: + response.set_rcode(dns.rcode.NXDOMAIN) + return response.to_wire() + + +class DNSAPI: + # usage: /add?domian=xxxx&ip=xx.xx.xx.xx&key=xxxxx + # /delete?domian=xxxx&ip=xx.xx.xx.xx&key=xxxxx + + def __init__(self, hostname, port, db_file): + self.hostname = hostname + self.port = port + self.db_file = db_file + + + def run(self): + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # 绑定 IP 地址和端口号 + server_socket.bind((self.hostname, self.port)) + # 监听连接 + server_socket.listen(5) + print(f"API server running on {self.hostname}:{self.port}") + while True: + # 接受连接 + conn, addr = server_socket.accept() + # 处理请求 + t = threading.Thread(target=self.handle_tcp_request, args=(conn,)) + t.start() + + def handle_tcp_request(self,conn): + request = conn.recv(1024).decode('utf-8') + response = self.handle_http_request(request) + conn.send(response) + conn.close() + + def handle_http_request(self, request): + request_line, headers= request.split('\r\n\r\n', 2) + method, url, version = request_line.split(' ', 2) + print(method,url) + if method == 'GET': + response = self.handle_get_request(url) + else: + 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' + + headers = { + 'Content-Type': 'text/html', + 'Connection': 'close', + } + response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) + return response.encode("utf-8") + + def handle_error_request(self, request): + status_code = 400 + reason_phrase = "unsupport method" + headers = { + 'Content-Type': 'text/html', + 'Connection': 'close', + } + response = 'HTTP/1.1 {} {}\r\n'.format(status_code, reason_phrase) + return response.encode("utf-8") + + def add_data(self, url): + domain = re.search(r'domain=([^&]+)', url) + ip = re.search(r'ip=([^&]+)', url) + key = re.search(r'ip=([^&]+)', url) + if domain and ip and key: + domain = domain.group(1) + ip = ip.group(1) + key = key.group(1) + else: + return 400 + return 200 + + def delete_data(self,url): + m = re.search(r'domain=([^&]+)', url) + if m: + domain = m.group(1) + print(domain) + else: + print('not matched') + return 200 + + + +if __name__ == '__main__': + # some config + db_file = '../database/dns.db' + DNS_port = 53 + listen_host = "0.0.0.0" + API_port = 81 + + # start dns server + server = DNSServer(listen_host, DNS_port, db_file) + server.run() + + # start dns api server + APIserver = DNSAPI(listen_host, API_port, db_file) + APIserver.run()