simpledht 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simpledht/__init__.py +10 -0
- simpledht/cli.py +98 -0
- simpledht/dht_node.py +211 -0
- simpledht/test_dht.py +76 -0
- simpledht-0.1.0.dist-info/METADATA +193 -0
- simpledht-0.1.0.dist-info/RECORD +10 -0
- simpledht-0.1.0.dist-info/WHEEL +5 -0
- simpledht-0.1.0.dist-info/entry_points.txt +2 -0
- simpledht-0.1.0.dist-info/licenses/LICENSE +21 -0
- simpledht-0.1.0.dist-info/top_level.txt +1 -0
simpledht/__init__.py
ADDED
simpledht/cli.py
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
import click
|
2
|
+
from dht_node import DHTNode
|
3
|
+
import json
|
4
|
+
import time
|
5
|
+
import socket
|
6
|
+
import sys
|
7
|
+
|
8
|
+
@click.group()
|
9
|
+
def cli():
|
10
|
+
"""Simple DHT CLI interface."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
@cli.command()
|
14
|
+
@click.option('--host', default='0.0.0.0', help='Host to bind to')
|
15
|
+
@click.option('--port', default=5000, help='Port to bind to')
|
16
|
+
@click.option('--bootstrap', help='Comma-separated list of bootstrap nodes (host:port)')
|
17
|
+
def start(host, port, bootstrap):
|
18
|
+
"""Start a new DHT node."""
|
19
|
+
bootstrap_nodes = bootstrap.split(',') if bootstrap else []
|
20
|
+
node = DHTNode(host, port, bootstrap_nodes)
|
21
|
+
node.start()
|
22
|
+
|
23
|
+
try:
|
24
|
+
while True:
|
25
|
+
pass
|
26
|
+
except KeyboardInterrupt:
|
27
|
+
node.stop()
|
28
|
+
|
29
|
+
def _send_message(host: str, port: int, message: dict, timeout: int = 5) -> dict:
|
30
|
+
"""Send a message to a DHT node and wait for response."""
|
31
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
32
|
+
try:
|
33
|
+
# Set timeout before sending
|
34
|
+
sock.settimeout(timeout)
|
35
|
+
|
36
|
+
# Send message
|
37
|
+
click.echo(f"Sending message to {host}:{port}...")
|
38
|
+
sock.sendto(json.dumps(message).encode(), (host, port))
|
39
|
+
|
40
|
+
# Wait for response
|
41
|
+
try:
|
42
|
+
data, _ = sock.recvfrom(4096)
|
43
|
+
return json.loads(data.decode())
|
44
|
+
except socket.timeout:
|
45
|
+
click.echo(f"Error: No response received from {host}:{port} within {timeout} seconds")
|
46
|
+
click.echo("Possible reasons:")
|
47
|
+
click.echo("1. The node is not running")
|
48
|
+
click.echo("2. The port is blocked by a firewall")
|
49
|
+
click.echo("3. The node is behind NAT without port forwarding")
|
50
|
+
sys.exit(1)
|
51
|
+
except Exception as e:
|
52
|
+
click.echo(f"Error sending message: {e}")
|
53
|
+
sys.exit(1)
|
54
|
+
finally:
|
55
|
+
sock.close()
|
56
|
+
|
57
|
+
@cli.command()
|
58
|
+
@click.option('--host', required=True, help='Host of the DHT node')
|
59
|
+
@click.option('--port', required=True, type=int, help='Port of the DHT node')
|
60
|
+
@click.option('--timeout', default=5, help='Timeout in seconds')
|
61
|
+
@click.argument('key')
|
62
|
+
@click.argument('value')
|
63
|
+
def put(host, port, timeout, key, value):
|
64
|
+
"""Store a key-value pair in the DHT."""
|
65
|
+
response = _send_message(host, port, {
|
66
|
+
'type': 'store',
|
67
|
+
'key': key,
|
68
|
+
'value': value
|
69
|
+
}, timeout)
|
70
|
+
|
71
|
+
if response.get('type') == 'store_ack':
|
72
|
+
click.echo(f"Successfully stored {key}={value}")
|
73
|
+
else:
|
74
|
+
click.echo(f"Failed to store {key}={value}")
|
75
|
+
|
76
|
+
@cli.command()
|
77
|
+
@click.option('--host', required=True, help='Host of the DHT node')
|
78
|
+
@click.option('--port', required=True, type=int, help='Port of the DHT node')
|
79
|
+
@click.option('--timeout', default=5, help='Timeout in seconds')
|
80
|
+
@click.argument('key')
|
81
|
+
def get(host, port, timeout, key):
|
82
|
+
"""Retrieve a value from the DHT."""
|
83
|
+
response = _send_message(host, port, {
|
84
|
+
'type': 'get',
|
85
|
+
'key': key
|
86
|
+
}, timeout)
|
87
|
+
|
88
|
+
if response.get('type') == 'get_response':
|
89
|
+
value = response.get('value')
|
90
|
+
if value is None:
|
91
|
+
click.echo(f"No value found for key: {key}")
|
92
|
+
else:
|
93
|
+
click.echo(f"Value for {key}: {value}")
|
94
|
+
else:
|
95
|
+
click.echo(f"Failed to retrieve value for key: {key}")
|
96
|
+
|
97
|
+
if __name__ == '__main__':
|
98
|
+
cli()
|
simpledht/dht_node.py
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
import socket
|
2
|
+
import threading
|
3
|
+
import json
|
4
|
+
import hashlib
|
5
|
+
from typing import Dict, Optional, Tuple
|
6
|
+
import requests
|
7
|
+
from datetime import datetime
|
8
|
+
import netifaces
|
9
|
+
|
10
|
+
class DHTNode:
|
11
|
+
def __init__(self, host: str, port: int, bootstrap_nodes: list = None):
|
12
|
+
self.host = host
|
13
|
+
self.port = port
|
14
|
+
self.id = self._generate_node_id(host, port)
|
15
|
+
self.data: Dict[str, str] = {}
|
16
|
+
self.routing_table: Dict[str, Tuple[str, int]] = {}
|
17
|
+
self.bootstrap_nodes = bootstrap_nodes or []
|
18
|
+
self.running = False
|
19
|
+
|
20
|
+
# Create socket with proper options for cross-network communication
|
21
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
22
|
+
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
23
|
+
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
24
|
+
|
25
|
+
# Bind to all interfaces if host is 0.0.0.0
|
26
|
+
if host == '0.0.0.0':
|
27
|
+
self.socket.bind(('0.0.0.0', port))
|
28
|
+
# Get actual IP address
|
29
|
+
self.public_ip = self._get_public_ip()
|
30
|
+
self.local_ips = self._get_local_ips()
|
31
|
+
else:
|
32
|
+
self.socket.bind((host, port))
|
33
|
+
self.public_ip = host
|
34
|
+
self.local_ips = [host]
|
35
|
+
|
36
|
+
# Add self to routing table
|
37
|
+
self.routing_table[self.id] = (self.public_ip, port)
|
38
|
+
print(f"Node initialized with public IP: {self.public_ip}")
|
39
|
+
|
40
|
+
def _get_public_ip(self) -> str:
|
41
|
+
"""Get the public IP address of this node."""
|
42
|
+
try:
|
43
|
+
response = requests.get('https://api.ipify.org?format=json', timeout=5)
|
44
|
+
return response.json()['ip']
|
45
|
+
except Exception as e:
|
46
|
+
print(f"Failed to get public IP: {e}")
|
47
|
+
return '0.0.0.0'
|
48
|
+
|
49
|
+
def _get_local_ips(self) -> list:
|
50
|
+
"""Get all local IP addresses of this node."""
|
51
|
+
ips = []
|
52
|
+
for interface in netifaces.interfaces():
|
53
|
+
try:
|
54
|
+
addrs = netifaces.ifaddresses(interface)
|
55
|
+
if netifaces.AF_INET in addrs:
|
56
|
+
for addr in addrs[netifaces.AF_INET]:
|
57
|
+
if 'addr' in addr:
|
58
|
+
ips.append(addr['addr'])
|
59
|
+
except Exception as e:
|
60
|
+
print(f"Failed to get IP for interface {interface}: {e}")
|
61
|
+
return ips
|
62
|
+
|
63
|
+
def _generate_node_id(self, host: str, port: int) -> str:
|
64
|
+
"""Generate a unique node ID based on host and port."""
|
65
|
+
return hashlib.sha256(f"{host}:{port}".encode()).hexdigest()[:16]
|
66
|
+
|
67
|
+
def start(self):
|
68
|
+
"""Start the DHT node."""
|
69
|
+
self.running = True
|
70
|
+
print(f"Starting DHT node {self.id} on {self.public_ip}:{self.port}")
|
71
|
+
print(f"Local IPs: {', '.join(self.local_ips)}")
|
72
|
+
|
73
|
+
# Start listening for messages
|
74
|
+
listen_thread = threading.Thread(target=self._listen)
|
75
|
+
listen_thread.start()
|
76
|
+
|
77
|
+
# Bootstrap with other nodes if available
|
78
|
+
if self.bootstrap_nodes:
|
79
|
+
self._bootstrap()
|
80
|
+
|
81
|
+
def _listen(self):
|
82
|
+
"""Listen for incoming messages."""
|
83
|
+
while self.running:
|
84
|
+
try:
|
85
|
+
data, addr = self.socket.recvfrom(4096)
|
86
|
+
message = json.loads(data.decode())
|
87
|
+
self._handle_message(message, addr)
|
88
|
+
except Exception as e:
|
89
|
+
print(f"Error handling message: {e}")
|
90
|
+
|
91
|
+
def _handle_message(self, message: dict, addr: Tuple[str, int]):
|
92
|
+
"""Handle incoming messages."""
|
93
|
+
msg_type = message.get('type')
|
94
|
+
|
95
|
+
if msg_type == 'ping':
|
96
|
+
self._send_response(addr, {'type': 'pong'})
|
97
|
+
elif msg_type == 'store':
|
98
|
+
key = message.get('key')
|
99
|
+
value = message.get('value')
|
100
|
+
if key and value:
|
101
|
+
self.data[key] = value
|
102
|
+
# Replicate to other nodes
|
103
|
+
self._replicate_data(key, value)
|
104
|
+
self._send_response(addr, {'type': 'store_ack'})
|
105
|
+
elif msg_type == 'get':
|
106
|
+
key = message.get('key')
|
107
|
+
if key in self.data:
|
108
|
+
self._send_response(addr, {
|
109
|
+
'type': 'get_response',
|
110
|
+
'value': self.data[key]
|
111
|
+
})
|
112
|
+
else:
|
113
|
+
# If not found locally, check other nodes
|
114
|
+
self._forward_get_request(key, addr)
|
115
|
+
elif msg_type == 'join':
|
116
|
+
# Add the joining node to our routing table
|
117
|
+
node_id = message.get('node_id')
|
118
|
+
host = message.get('host')
|
119
|
+
port = message.get('port')
|
120
|
+
if node_id and host and port:
|
121
|
+
self.routing_table[node_id] = (host, port)
|
122
|
+
# Send our routing table to the new node
|
123
|
+
self._send_response(addr, {
|
124
|
+
'type': 'join_ack',
|
125
|
+
'routing_table': self.routing_table
|
126
|
+
})
|
127
|
+
elif msg_type == 'join_ack':
|
128
|
+
# Update our routing table with the received nodes
|
129
|
+
received_table = message.get('routing_table', {})
|
130
|
+
self.routing_table.update(received_table)
|
131
|
+
|
132
|
+
def _replicate_data(self, key: str, value: str):
|
133
|
+
"""Replicate data to other nodes in the network."""
|
134
|
+
for node_id, (host, port) in self.routing_table.items():
|
135
|
+
if node_id != self.id: # Don't send to self
|
136
|
+
try:
|
137
|
+
self._send_message((host, port), {
|
138
|
+
'type': 'store',
|
139
|
+
'key': key,
|
140
|
+
'value': value
|
141
|
+
})
|
142
|
+
except Exception as e:
|
143
|
+
print(f"Failed to replicate to {host}:{port}: {e}")
|
144
|
+
|
145
|
+
def _forward_get_request(self, key: str, original_addr: Tuple[str, int]):
|
146
|
+
"""Forward a get request to other nodes if not found locally."""
|
147
|
+
for node_id, (host, port) in self.routing_table.items():
|
148
|
+
if node_id != self.id: # Don't send to self
|
149
|
+
try:
|
150
|
+
response = self._send_message_with_response((host, port), {
|
151
|
+
'type': 'get',
|
152
|
+
'key': key
|
153
|
+
})
|
154
|
+
if response.get('type') == 'get_response' and response.get('value') is not None:
|
155
|
+
# Forward the response back to the original requester
|
156
|
+
self._send_message(original_addr, response)
|
157
|
+
return
|
158
|
+
except Exception as e:
|
159
|
+
print(f"Failed to forward get request to {host}:{port}: {e}")
|
160
|
+
|
161
|
+
# If no node had the value, send a negative response
|
162
|
+
self._send_message(original_addr, {
|
163
|
+
'type': 'get_response',
|
164
|
+
'value': None
|
165
|
+
})
|
166
|
+
|
167
|
+
def _send_message_with_response(self, addr: Tuple[str, int], message: dict) -> dict:
|
168
|
+
"""Send a message and wait for response."""
|
169
|
+
self._send_message(addr, message)
|
170
|
+
data, _ = self.socket.recvfrom(4096)
|
171
|
+
return json.loads(data.decode())
|
172
|
+
|
173
|
+
def _send_response(self, addr: Tuple[str, int], response: dict):
|
174
|
+
"""Send a response to a node."""
|
175
|
+
self.socket.sendto(json.dumps(response).encode(), addr)
|
176
|
+
|
177
|
+
def _bootstrap(self):
|
178
|
+
"""Connect to bootstrap nodes."""
|
179
|
+
for node in self.bootstrap_nodes:
|
180
|
+
try:
|
181
|
+
# Split the address and handle potential errors
|
182
|
+
parts = node.split(':')
|
183
|
+
if len(parts) != 2:
|
184
|
+
print(f"Invalid bootstrap node format: {node}. Expected format: IP:PORT")
|
185
|
+
continue
|
186
|
+
|
187
|
+
host, port = parts
|
188
|
+
try:
|
189
|
+
port = int(port)
|
190
|
+
except ValueError:
|
191
|
+
print(f"Invalid port number in bootstrap node: {node}")
|
192
|
+
continue
|
193
|
+
|
194
|
+
print(f"Attempting to bootstrap with node {host}:{port}")
|
195
|
+
self._send_message((host, port), {
|
196
|
+
'type': 'join',
|
197
|
+
'node_id': self.id,
|
198
|
+
'host': self.public_ip,
|
199
|
+
'port': self.port
|
200
|
+
})
|
201
|
+
except Exception as e:
|
202
|
+
print(f"Failed to bootstrap with {node}: {e}")
|
203
|
+
|
204
|
+
def _send_message(self, addr: Tuple[str, int], message: dict):
|
205
|
+
"""Send a message to another node."""
|
206
|
+
self.socket.sendto(json.dumps(message).encode(), addr)
|
207
|
+
|
208
|
+
def stop(self):
|
209
|
+
"""Stop the DHT node."""
|
210
|
+
self.running = False
|
211
|
+
self.socket.close()
|
simpledht/test_dht.py
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
import subprocess
|
2
|
+
import time
|
3
|
+
import click
|
4
|
+
from dht_node import DHTNode
|
5
|
+
import socket
|
6
|
+
import json
|
7
|
+
|
8
|
+
def start_node(host, port, bootstrap=None):
|
9
|
+
"""Start a DHT node in a separate process."""
|
10
|
+
cmd = ['python', 'cli.py', 'start', '--host', host, '--port', str(port)]
|
11
|
+
if bootstrap:
|
12
|
+
cmd.extend(['--bootstrap', bootstrap])
|
13
|
+
return subprocess.Popen(cmd)
|
14
|
+
|
15
|
+
def send_message(host, port, message):
|
16
|
+
"""Send a message to a DHT node."""
|
17
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
18
|
+
try:
|
19
|
+
sock.sendto(json.dumps(message).encode(), (host, port))
|
20
|
+
sock.settimeout(5.0)
|
21
|
+
data, _ = sock.recvfrom(4096)
|
22
|
+
return json.loads(data.decode())
|
23
|
+
finally:
|
24
|
+
sock.close()
|
25
|
+
|
26
|
+
@click.command()
|
27
|
+
def test():
|
28
|
+
"""Test the DHT with multiple nodes."""
|
29
|
+
click.echo("Starting DHT test with 2 nodes...")
|
30
|
+
|
31
|
+
# Start first node
|
32
|
+
click.echo("\nStarting Node 1 (0.0.0.0:5000)...")
|
33
|
+
node1 = start_node('0.0.0.0', 5000)
|
34
|
+
time.sleep(2) # Wait for node to start
|
35
|
+
|
36
|
+
# Start second node and connect to first
|
37
|
+
click.echo("Starting Node 2 (0.0.0.0:5001) and connecting to Node 1...")
|
38
|
+
node2 = start_node('0.0.0.0', 5001, '0.0.0.0:5000')
|
39
|
+
time.sleep(2) # Wait for nodes to connect
|
40
|
+
|
41
|
+
try:
|
42
|
+
# Store data on Node 1
|
43
|
+
click.echo("\nStoring data on Node 1...")
|
44
|
+
response = send_message('0.0.0.0', 5000, {
|
45
|
+
'type': 'store',
|
46
|
+
'key': 'test_key',
|
47
|
+
'value': 'test_value'
|
48
|
+
})
|
49
|
+
if response.get('type') == 'store_ack':
|
50
|
+
click.echo("Successfully stored data on Node 1")
|
51
|
+
|
52
|
+
# Retrieve data from Node 2
|
53
|
+
click.echo("\nRetrieving data from Node 2...")
|
54
|
+
response = send_message('0.0.0.0', 5001, {
|
55
|
+
'type': 'get',
|
56
|
+
'key': 'test_key'
|
57
|
+
})
|
58
|
+
if response.get('type') == 'get_response':
|
59
|
+
value = response.get('value')
|
60
|
+
if value:
|
61
|
+
click.echo(f"Successfully retrieved value: {value}")
|
62
|
+
else:
|
63
|
+
click.echo("Failed to retrieve value")
|
64
|
+
|
65
|
+
click.echo("\nTest completed!")
|
66
|
+
|
67
|
+
finally:
|
68
|
+
# Clean up
|
69
|
+
click.echo("\nStopping nodes...")
|
70
|
+
node1.terminate()
|
71
|
+
node2.terminate()
|
72
|
+
node1.wait()
|
73
|
+
node2.wait()
|
74
|
+
|
75
|
+
if __name__ == '__main__':
|
76
|
+
test()
|
@@ -0,0 +1,193 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: simpledht
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A simple distributed hash table implementation
|
5
|
+
Home-page: https://github.com/yourusername/simpledht
|
6
|
+
Author: Your Name
|
7
|
+
Author-email: your.email@example.com
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Requires-Python: >=3.6
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
License-File: LICENSE
|
14
|
+
Requires-Dist: requests>=2.25.1
|
15
|
+
Requires-Dist: click>=8.0.1
|
16
|
+
Dynamic: author
|
17
|
+
Dynamic: author-email
|
18
|
+
Dynamic: classifier
|
19
|
+
Dynamic: description
|
20
|
+
Dynamic: description-content-type
|
21
|
+
Dynamic: home-page
|
22
|
+
Dynamic: license-file
|
23
|
+
Dynamic: requires-dist
|
24
|
+
Dynamic: requires-python
|
25
|
+
Dynamic: summary
|
26
|
+
|
27
|
+
# Distributed Hash Table (DHT) Implementation
|
28
|
+
|
29
|
+
A Python-based Distributed Hash Table implementation that allows nodes to connect across different networks using IP addresses. This implementation supports key-value storage and retrieval across multiple nodes.
|
30
|
+
|
31
|
+
## Features
|
32
|
+
|
33
|
+
- Cross-network node communication
|
34
|
+
- Key-value storage and retrieval
|
35
|
+
- Automatic node discovery
|
36
|
+
- Data replication between nodes
|
37
|
+
- Simple CLI interface
|
38
|
+
- Public IP detection
|
39
|
+
- Local network support
|
40
|
+
|
41
|
+
## Installation
|
42
|
+
|
43
|
+
### From PyPI (Recommended)
|
44
|
+
|
45
|
+
```bash
|
46
|
+
pip install simpledht
|
47
|
+
```
|
48
|
+
|
49
|
+
### From Source
|
50
|
+
|
51
|
+
1. Clone the repository:
|
52
|
+
```bash
|
53
|
+
git clone <repository-url>
|
54
|
+
cd SimpleDHT
|
55
|
+
```
|
56
|
+
|
57
|
+
2. Create and activate a virtual environment:
|
58
|
+
```bash
|
59
|
+
python -m venv env
|
60
|
+
source env/bin/activate # On Windows: env\Scripts\activate
|
61
|
+
```
|
62
|
+
|
63
|
+
3. Install the package in development mode:
|
64
|
+
```bash
|
65
|
+
pip install -e .
|
66
|
+
```
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
### Starting a Node
|
71
|
+
|
72
|
+
To start a new DHT node:
|
73
|
+
```bash
|
74
|
+
python cli.py start --host 0.0.0.0 --port 5000
|
75
|
+
```
|
76
|
+
|
77
|
+
To start a node and connect to existing nodes:
|
78
|
+
```bash
|
79
|
+
python cli.py start --host 0.0.0.0 --port 5001 --bootstrap "PUBLIC_IP:5000"
|
80
|
+
```
|
81
|
+
|
82
|
+
### Storing Data
|
83
|
+
|
84
|
+
To store a key-value pair:
|
85
|
+
```bash
|
86
|
+
python cli.py put --host PUBLIC_IP --port 5000 mykey "my value"
|
87
|
+
```
|
88
|
+
|
89
|
+
### Retrieving Data
|
90
|
+
|
91
|
+
To retrieve a value:
|
92
|
+
```bash
|
93
|
+
python cli.py get --host PUBLIC_IP --port 5000 mykey
|
94
|
+
```
|
95
|
+
|
96
|
+
### Cross-Network Example
|
97
|
+
|
98
|
+
1. Start Node 1 (First network):
|
99
|
+
```bash
|
100
|
+
python cli.py start --host 0.0.0.0 --port 5000
|
101
|
+
```
|
102
|
+
|
103
|
+
2. Start Node 2 (Second network):
|
104
|
+
```bash
|
105
|
+
python cli.py start --host 0.0.0.0 --port 5000 --bootstrap "NODE1_PUBLIC_IP:5000"
|
106
|
+
```
|
107
|
+
|
108
|
+
3. Store and retrieve data:
|
109
|
+
```bash
|
110
|
+
# Store on Node 1
|
111
|
+
python cli.py put --host NODE1_PUBLIC_IP --port 5000 test_key "test_value"
|
112
|
+
|
113
|
+
# Retrieve from Node 2
|
114
|
+
python cli.py get --host NODE2_PUBLIC_IP --port 5000 test_key
|
115
|
+
```
|
116
|
+
|
117
|
+
## Network Configuration
|
118
|
+
|
119
|
+
### Firewall Setup
|
120
|
+
|
121
|
+
Ensure the UDP port (default: 5000) is open in your firewall:
|
122
|
+
|
123
|
+
```bash
|
124
|
+
# For UFW (Ubuntu)
|
125
|
+
sudo ufw allow 5000/udp
|
126
|
+
|
127
|
+
# For iptables
|
128
|
+
sudo iptables -A INPUT -p udp --dport 5000 -j ACCEPT
|
129
|
+
```
|
130
|
+
|
131
|
+
### Port Forwarding
|
132
|
+
|
133
|
+
If your node is behind a NAT router:
|
134
|
+
1. Access your router's admin interface
|
135
|
+
2. Set up port forwarding for UDP port 5000
|
136
|
+
3. Forward to your node's local IP address
|
137
|
+
|
138
|
+
## Troubleshooting
|
139
|
+
|
140
|
+
### Common Issues
|
141
|
+
|
142
|
+
1. **Connection Timeout**
|
143
|
+
- Check if the target node is running
|
144
|
+
- Verify firewall settings
|
145
|
+
- Ensure port forwarding is configured correctly
|
146
|
+
- Try increasing the timeout: `--timeout 10`
|
147
|
+
|
148
|
+
2. **Address Already in Use**
|
149
|
+
- The port is already being used by another process
|
150
|
+
- Try a different port number
|
151
|
+
- Check running processes: `netstat -tuln | grep 5000`
|
152
|
+
|
153
|
+
3. **No Response from Node**
|
154
|
+
- Verify the node is running
|
155
|
+
- Check network connectivity: `ping NODE_IP`
|
156
|
+
- Test port connectivity: `nc -vzu NODE_IP 5000`
|
157
|
+
|
158
|
+
### Error Messages
|
159
|
+
|
160
|
+
- `Failed to bootstrap with IP:PORT`: Invalid bootstrap node format
|
161
|
+
- `No response received`: Node is not responding
|
162
|
+
- `Address already in use`: Port conflict
|
163
|
+
- `Failed to get public IP`: Network connectivity issue
|
164
|
+
|
165
|
+
## Architecture
|
166
|
+
|
167
|
+
The DHT implementation uses:
|
168
|
+
- UDP sockets for communication
|
169
|
+
- SHA-256 for node ID generation
|
170
|
+
- Automatic public IP detection
|
171
|
+
- Data replication between nodes
|
172
|
+
- Bootstrap nodes for network discovery
|
173
|
+
|
174
|
+
## Security Considerations
|
175
|
+
|
176
|
+
- This is a basic implementation and should not be used in production without additional security measures
|
177
|
+
- Consider adding:
|
178
|
+
- Encryption for data in transit
|
179
|
+
- Authentication for node joining
|
180
|
+
- Rate limiting to prevent abuse
|
181
|
+
- Input validation
|
182
|
+
|
183
|
+
## Contributing
|
184
|
+
|
185
|
+
1. Fork the repository
|
186
|
+
2. Create a feature branch
|
187
|
+
3. Commit your changes
|
188
|
+
4. Push to the branch
|
189
|
+
5. Create a Pull Request
|
190
|
+
|
191
|
+
## License
|
192
|
+
|
193
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
simpledht/__init__.py,sha256=Rz_9ENT9rhI8ocZTZl_EmiHpiit8oYXiGVipRXKD2Vw,174
|
2
|
+
simpledht/cli.py,sha256=5KrXFqxHAHuU9RleB1FPRyI2YZwKwaJijXH-DAmZV5k,3225
|
3
|
+
simpledht/dht_node.py,sha256=FZHLigu7NLkJ9smWyJyLK2FBFu2kKy9kxr9Mig6yh0M,8481
|
4
|
+
simpledht/test_dht.py,sha256=tsWGhtLkTArFJIdkaWManekhFx2KCUO0Gp93EGBUAlw,2346
|
5
|
+
simpledht-0.1.0.dist-info/licenses/LICENSE,sha256=MrSAzCY_7J3C4Hp8PaXaaFJP2ygfmuAWoLTVowEE3B0,1073
|
6
|
+
simpledht-0.1.0.dist-info/METADATA,sha256=_FnoM3ghlti3zd_zEgCS1HPmQVOfeQFaXTGfB442_7U,4503
|
7
|
+
simpledht-0.1.0.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
8
|
+
simpledht-0.1.0.dist-info/entry_points.txt,sha256=Hm3BYz1MykAhg94m3JSYsMcdEQEia3bdRocokc-0wxs,49
|
9
|
+
simpledht-0.1.0.dist-info/top_level.txt,sha256=VjRxeoXfQJeoPoFhr5JGGMAI5NSLkYJl0WbySmpZ4bs,10
|
10
|
+
simpledht-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Dhruvkumar Patel
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
simpledht
|