Modify client code to get it able to transmit data over I2P
parent
d7812173fb
commit
9ed01f71ac
|
@ -16,7 +16,7 @@ When you send a message:
|
|||
## Dependencies
|
||||
|
||||
```
|
||||
# apt install python3-pycryptodome
|
||||
# apt install python3-pycryptodome python3-socks
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
|
|
@ -1,20 +1,124 @@
|
|||
#!/usr/bin/python3
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import typing
|
||||
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 5273
|
||||
# Type alias for clarity
|
||||
Address = str
|
||||
|
||||
def send_message(sock, data: bytes):
|
||||
header = struct.pack('!I', len(data))
|
||||
sock.sendall(header + data)
|
||||
# Default timeout for socket operations (seconds)
|
||||
_DEFAULT_TIMEOUT = 10.0
|
||||
|
||||
if __name__ == '__main__':
|
||||
with socket.create_connection((HOST, PORT)) as s:
|
||||
send_message(s, b'hello binary \x00\x01')
|
||||
time.sleep(0.2)
|
||||
send_message(s, b'\xff'*1024) # 发送 1KB 二进制
|
||||
time.sleep(0.5)
|
||||
send_message(s, b'last msg')
|
||||
# 可关闭连接或继续发送更多消息
|
||||
|
||||
def _prepare_payload(data: typing.Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
Convert data into bytes. If data is a str, encode with UTF-8.
|
||||
|
||||
Raises:
|
||||
TypeError: if data is not bytes or str.
|
||||
"""
|
||||
if isinstance(data, bytes):
|
||||
return data
|
||||
if isinstance(data, str):
|
||||
return data.encode('utf-8')
|
||||
raise TypeError("data must be bytes or str")
|
||||
|
||||
|
||||
def _pack_message(payload: bytes) -> bytes:
|
||||
"""
|
||||
Prepend a 4-byte big-endian unsigned length to payload.
|
||||
The protocol supports payload lengths up to 2**32 - 1 bytes.
|
||||
"""
|
||||
length = len(payload)
|
||||
if length >= (1 << 32):
|
||||
raise ValueError("payload too large (must be less than 2**32 bytes)")
|
||||
return struct.pack("!I", length) + payload
|
||||
|
||||
|
||||
def send_data(data: typing.Union[bytes, str], addr: Address, port: int) -> None:
|
||||
"""
|
||||
Send a single length-prefixed message over plain TCP to (addr, port).
|
||||
|
||||
Parameters:
|
||||
- data: bytes or str (if str, encoded as UTF-8)
|
||||
- addr: IPv4/IPv6 address or hostname reachable directly
|
||||
- port: TCP port on the destination
|
||||
|
||||
Behaviour:
|
||||
- Connects, sends the framed message, then closes the socket.
|
||||
- Raises built-in socket exceptions on failure.
|
||||
|
||||
Example:
|
||||
send_data(b'hello', '192.0.2.1', 12345)
|
||||
"""
|
||||
payload = _prepare_payload(data)
|
||||
message = _pack_message(payload)
|
||||
|
||||
# Create a TCP connection with a timeout
|
||||
with socket.create_connection((addr, port), timeout=_DEFAULT_TIMEOUT) as sock:
|
||||
# Ensure blocking mode for sendall
|
||||
sock.setblocking(True)
|
||||
sock.sendall(message)
|
||||
|
||||
|
||||
def send_data_i2p(data: typing.Union[bytes, str], addr: Address, port: int) -> None:
|
||||
"""
|
||||
Send a single length-prefixed message to an I2P destination via a local i2pd SOCKS5 proxy.
|
||||
|
||||
Parameters:
|
||||
- data: bytes or str (if str, encoded as UTF-8)
|
||||
- addr: I2P hostname, e.g. 'xxxxxxxxxxxxxx.b32.i2p' or 'mydest.i2p'
|
||||
- port: destination port exposed by the I2P server tunnel
|
||||
|
||||
Behaviour:
|
||||
- Uses PySocks (socks) to create a socket that speaks SOCKS5 to the local proxy.
|
||||
- Uses remote DNS resolution (rdns=True) so that the proxy resolves .i2p names.
|
||||
- Connects, sends the framed message, then closes the socket.
|
||||
- Raises ImportError if PySocks is not installed.
|
||||
- Raises socket or proxy-related exceptions on failure.
|
||||
|
||||
Environment variables:
|
||||
- I2P_SOCKS_HOST (default '127.0.0.1')
|
||||
- I2P_SOCKS_PORT (default '4447')
|
||||
|
||||
Example:
|
||||
send_data_i2p(b'hello', 'abcd1234xxxxxxx.b32.i2p', 12345)
|
||||
"""
|
||||
try:
|
||||
import socks # PySocks; license: MIT
|
||||
except Exception as e:
|
||||
raise ImportError(
|
||||
"PySocks is required for send_data_i2p. "
|
||||
"Install it with: pip install PySocks"
|
||||
) from e
|
||||
|
||||
payload = _prepare_payload(data)
|
||||
message = _pack_message(payload)
|
||||
|
||||
socks_host = os.environ.get("I2P_SOCKS_HOST", "127.0.0.1")
|
||||
socks_port_str = os.environ.get("I2P_SOCKS_PORT", "4447")
|
||||
try:
|
||||
socks_port = int(socks_port_str)
|
||||
except ValueError:
|
||||
raise ValueError("I2P_SOCKS_PORT environment variable must be an integer")
|
||||
|
||||
# Create a socksified socket. socks.socksocket has the same interface as socket.socket.
|
||||
s = socks.socksocket()
|
||||
# Use SOCKS5; rdns=True ensures the proxy resolves the .i2p name instead of local DNS.
|
||||
s.set_proxy(proxy_type=socks.SOCKS5, addr=socks_host, port=socks_port, rdns=True)
|
||||
s.settimeout(_DEFAULT_TIMEOUT)
|
||||
|
||||
try:
|
||||
# Connect through the proxy to the I2P destination.
|
||||
# When rdns=True, the proxy will perform name resolution for .i2p addresses.
|
||||
s.connect((addr, port))
|
||||
# Ensure blocking mode for sendall
|
||||
s.setblocking(True)
|
||||
s.sendall(message)
|
||||
finally:
|
||||
try:
|
||||
s.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
#!/usr/bin/python3
|
||||
import socket
|
||||
import threading
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import socket, threading, struct, time, sys
|
||||
|
||||
HOST = '0.0.0.0'
|
||||
|
||||
|
|
Loading…
Reference in New Issue