simpledht 0.1.0__tar.gz

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.
@@ -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,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,167 @@
1
+ # Distributed Hash Table (DHT) Implementation
2
+
3
+ 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.
4
+
5
+ ## Features
6
+
7
+ - Cross-network node communication
8
+ - Key-value storage and retrieval
9
+ - Automatic node discovery
10
+ - Data replication between nodes
11
+ - Simple CLI interface
12
+ - Public IP detection
13
+ - Local network support
14
+
15
+ ## Installation
16
+
17
+ ### From PyPI (Recommended)
18
+
19
+ ```bash
20
+ pip install simpledht
21
+ ```
22
+
23
+ ### From Source
24
+
25
+ 1. Clone the repository:
26
+ ```bash
27
+ git clone <repository-url>
28
+ cd SimpleDHT
29
+ ```
30
+
31
+ 2. Create and activate a virtual environment:
32
+ ```bash
33
+ python -m venv env
34
+ source env/bin/activate # On Windows: env\Scripts\activate
35
+ ```
36
+
37
+ 3. Install the package in development mode:
38
+ ```bash
39
+ pip install -e .
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ### Starting a Node
45
+
46
+ To start a new DHT node:
47
+ ```bash
48
+ python cli.py start --host 0.0.0.0 --port 5000
49
+ ```
50
+
51
+ To start a node and connect to existing nodes:
52
+ ```bash
53
+ python cli.py start --host 0.0.0.0 --port 5001 --bootstrap "PUBLIC_IP:5000"
54
+ ```
55
+
56
+ ### Storing Data
57
+
58
+ To store a key-value pair:
59
+ ```bash
60
+ python cli.py put --host PUBLIC_IP --port 5000 mykey "my value"
61
+ ```
62
+
63
+ ### Retrieving Data
64
+
65
+ To retrieve a value:
66
+ ```bash
67
+ python cli.py get --host PUBLIC_IP --port 5000 mykey
68
+ ```
69
+
70
+ ### Cross-Network Example
71
+
72
+ 1. Start Node 1 (First network):
73
+ ```bash
74
+ python cli.py start --host 0.0.0.0 --port 5000
75
+ ```
76
+
77
+ 2. Start Node 2 (Second network):
78
+ ```bash
79
+ python cli.py start --host 0.0.0.0 --port 5000 --bootstrap "NODE1_PUBLIC_IP:5000"
80
+ ```
81
+
82
+ 3. Store and retrieve data:
83
+ ```bash
84
+ # Store on Node 1
85
+ python cli.py put --host NODE1_PUBLIC_IP --port 5000 test_key "test_value"
86
+
87
+ # Retrieve from Node 2
88
+ python cli.py get --host NODE2_PUBLIC_IP --port 5000 test_key
89
+ ```
90
+
91
+ ## Network Configuration
92
+
93
+ ### Firewall Setup
94
+
95
+ Ensure the UDP port (default: 5000) is open in your firewall:
96
+
97
+ ```bash
98
+ # For UFW (Ubuntu)
99
+ sudo ufw allow 5000/udp
100
+
101
+ # For iptables
102
+ sudo iptables -A INPUT -p udp --dport 5000 -j ACCEPT
103
+ ```
104
+
105
+ ### Port Forwarding
106
+
107
+ If your node is behind a NAT router:
108
+ 1. Access your router's admin interface
109
+ 2. Set up port forwarding for UDP port 5000
110
+ 3. Forward to your node's local IP address
111
+
112
+ ## Troubleshooting
113
+
114
+ ### Common Issues
115
+
116
+ 1. **Connection Timeout**
117
+ - Check if the target node is running
118
+ - Verify firewall settings
119
+ - Ensure port forwarding is configured correctly
120
+ - Try increasing the timeout: `--timeout 10`
121
+
122
+ 2. **Address Already in Use**
123
+ - The port is already being used by another process
124
+ - Try a different port number
125
+ - Check running processes: `netstat -tuln | grep 5000`
126
+
127
+ 3. **No Response from Node**
128
+ - Verify the node is running
129
+ - Check network connectivity: `ping NODE_IP`
130
+ - Test port connectivity: `nc -vzu NODE_IP 5000`
131
+
132
+ ### Error Messages
133
+
134
+ - `Failed to bootstrap with IP:PORT`: Invalid bootstrap node format
135
+ - `No response received`: Node is not responding
136
+ - `Address already in use`: Port conflict
137
+ - `Failed to get public IP`: Network connectivity issue
138
+
139
+ ## Architecture
140
+
141
+ The DHT implementation uses:
142
+ - UDP sockets for communication
143
+ - SHA-256 for node ID generation
144
+ - Automatic public IP detection
145
+ - Data replication between nodes
146
+ - Bootstrap nodes for network discovery
147
+
148
+ ## Security Considerations
149
+
150
+ - This is a basic implementation and should not be used in production without additional security measures
151
+ - Consider adding:
152
+ - Encryption for data in transit
153
+ - Authentication for node joining
154
+ - Rate limiting to prevent abuse
155
+ - Input validation
156
+
157
+ ## Contributing
158
+
159
+ 1. Fork the repository
160
+ 2. Create a feature branch
161
+ 3. Commit your changes
162
+ 4. Push to the branch
163
+ 5. Create a Pull Request
164
+
165
+ ## License
166
+
167
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="simpledht",
8
+ version="0.1.0",
9
+ author="Your Name",
10
+ author_email="your.email@example.com",
11
+ description="A simple distributed hash table implementation",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/yourusername/simpledht",
15
+ packages=find_packages(),
16
+ classifiers=[
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ],
21
+ python_requires=">=3.6",
22
+ install_requires=[
23
+ "requests>=2.25.1",
24
+ "click>=8.0.1",
25
+ ],
26
+ entry_points={
27
+ "console_scripts": [
28
+ "simpledht=simpledht.cli:main",
29
+ ],
30
+ },
31
+ )
@@ -0,0 +1,10 @@
1
+ """
2
+ SimpleDHT - A simple distributed hash table implementation
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ from .dht_node import DHTNode
8
+ from .cli import main
9
+
10
+ __all__ = ["DHTNode", "main"]
@@ -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()
@@ -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()
@@ -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,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ simpledht/__init__.py
6
+ simpledht/cli.py
7
+ simpledht/dht_node.py
8
+ simpledht/test_dht.py
9
+ simpledht.egg-info/PKG-INFO
10
+ simpledht.egg-info/SOURCES.txt
11
+ simpledht.egg-info/dependency_links.txt
12
+ simpledht.egg-info/entry_points.txt
13
+ simpledht.egg-info/requires.txt
14
+ simpledht.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ simpledht = simpledht.cli:main
@@ -0,0 +1,2 @@
1
+ requests>=2.25.1
2
+ click>=8.0.1
@@ -0,0 +1 @@
1
+ simpledht