Commit a04dfb61 authored by Pascal Engélibert's avatar Pascal Engélibert

SOCKS5 proxy support

parent bc7504d0
......@@ -17,7 +17,8 @@ Install dependances:
Install the following packages: `libleveldb-dev` `libsodium-dev` `python3` `python3-pip`
sudo pip3 install libnacl duniterpy silkaj py-ubjson plyvel
sudo pip3 install libnacl duniterpy silkaj py-ubjson plyvel PySocks
python3 --version
# If Python < 3.6:
sudo pip3 install python2_secrets
......@@ -27,14 +28,36 @@ servers with different configs) (note that each server must have different dir)
python3 server.py -i -d ~/.gmixer-g1
Edit config and set your values. If `salt` or `password` are empty, they will be asked
at runtime. For security reasons, it is not a good idea to write them in commandline.
Edit config and set your values. If you are not using any proxy, `Bind_` and `Public_` addresses should be the same.
If `salt` or `password` are empty, they will be asked at runtime. For security reasons, it is not a good idea to write them in commandline.
Now, start the server:
python3 server.py -s
# Use -d if using a different dir than default
### Proxy
To use a SOCKS5 proxy for client connections, set the `Proxy` value in `config.ini` as following:
Proxy: 127.0.0.1 8080 # address port
Proxy_OnionOnly: 0
To use a proxy only when connecting to a `.onion` address, set the `Proxy_OnionOnly` value to `1`:
Proxy: 127.0.0.1 9050
Proxy_OnionOnly: 1
If using a reverse proxy (i.e. Tor hidden service), example:
Bind_Host: 127.0.0.1 # local address set in torrc file
Bind_Port: 10951
Public_Host: svetsae7j3usrycn.onion # other nodes contact me with this address
Public_Port: 10951
To unactivate proxy, leave empty or remove the `Proxy` line.
## Protocols
### Peer list file
......
......@@ -48,8 +48,8 @@ class Confirmation():
def genKeys():
return secrets.token_urlsafe(), secrets.token_urlsafe()
def get_peers(host):
header, content = sdata(host, "GET", "/pubkey/list")
def get_peers(host, proxy=None, proxy_onion_only=False):
header, content = sdata(host, "GET", "/pubkey/list", proxy=proxy, proxy_onion_only=proxy_onion_only)
try:
data = ubjson.loadb(content)
......@@ -86,7 +86,7 @@ def build_path(peers, receiver, layers=3):
path.append(receiver)
return host, path
def mix(amount, base, sender, path, host):
def mix(amount, base, sender, path, host, proxy=None, proxy_onion_only=False):
start_time = time.time()
onetime_keys = []
......@@ -104,7 +104,7 @@ def mix(amount, base, sender, path, host):
while True:
try:
header, content = sdata(host, "POST", "/pubkey/mix/"+sender.pubkey+"/"+str(int(amount))+"/"+str(int(base))+"/client", message)
header, content = sdata(host, "POST", "/pubkey/mix/"+sender.pubkey+"/"+str(int(amount))+"/"+str(int(base))+"/client", message, proxy=proxy, proxy_onion_only=proxy_onion_only)
except ConnectionRefusedError:
print("Error: Connection refused; retrying...")
time.sleep(3)
......@@ -125,7 +125,7 @@ def mix(amount, base, sender, path, host):
time.sleep(5)
print("Asking input node for confirmation...")
try:
header, content = sdata(host, "GET", "/getconfirm/"+sender.pubkey+"/"+out_comment)
header, content = sdata(host, "GET", "/getconfirm/"+sender.pubkey+"/"+out_comment, proxy=proxy, proxy_onion_only=proxy_onion_only)
except ConnectionRefusedError:
continue
......@@ -202,7 +202,7 @@ def mix(amount, base, sender, path, host):
exit()
async def test1(host, receiver, amount=1000, layers=3):
async def test1(host, receiver, amount=1000, layers=3, proxy=None, proxy_onion_only=False):
salt = getpass.getpass("Salt: ")
password = getpass.getpass("Psw: ")
keys = SigningKey.from_credentials(salt, password) # sender
......@@ -210,7 +210,7 @@ async def test1(host, receiver, amount=1000, layers=3):
if input("OK? [yn]: ").lower() != "y":
return
peers = get_peers(host)
peers = get_peers(host, proxy, proxy_onion_only)
if peers == None:
print("Error getting peer list")
exit(1)
......@@ -224,7 +224,7 @@ async def test1(host, receiver, amount=1000, layers=3):
if input("OK? [yn]: ").lower() == "y":
break
mix(amount, 0, keys, path, host1)
mix(amount, 0, keys, path, host1, proxy, proxy_onion_only)
if __name__ == "__main__":
if "--help" in sys.argv:
......@@ -235,9 +235,11 @@ Options:
-l <int> number of onion layers (default 3)
-a <int> amount (default 1000 = 10.00Ğ1)
-h <host> <port> host for sync peer list
-p <host> <port> SOCKS5 proxy
--onion use proxy only when connecting to .onion
Example:
python3 client.py -h 2a01:cb19:982:f800:ec34:6087:5197:4752 10951 -r 78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8
python3 client.py -h 127.0.0.1 10951 -r 78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8 -p 127.0.0.1 9050 --onion
⤷ send 10Ğ1 to Duniter developers
""")
exit()
......@@ -253,5 +255,9 @@ python3 client.py -h 2a01:cb19:982:f800:ec34:6087:5197:4752 10951 -r 78ZwwgpgdH5
port = int(getargv("-h", "", 2))
amount = int(getargv("-a", "1000"))
layers = int(getargv("-l", "3"))
proxy = None
if "-p" in sys.argv:
proxy = (getargv("-p", "127.0.0.1", 1), int(getargv("-p", 9050, 2)))
proxy_onion_only = "--onion" in sys.argv
asyncio.get_event_loop().run_until_complete(test1((host, port), receiver, amount, layers))
asyncio.get_event_loop().run_until_complete(test1((host, port), receiver, amount, layers, proxy, proxy_onion_only))
......@@ -38,8 +38,10 @@ out_ = something I'm sending to a node
"""
DIR = "~/.gmixer"
HOST = socket.gethostname()
PORT = "10951"
BIND_HOST = socket.gethostname()
BIND_PORT = "10951"
PUBLIC_HOST = BIND_HOST
PUBLIC_PORT = BIND_PORT
BMA_HOSTS = "g1.duniter.fr 443,g1.duniter.org 443,g1.presles.fr 443,g1.cgeek.fr 443,ts.g1.librelois.fr 443"
ID_SALT = "" # if empty, will be asked at runtime
ID_PSW = "" # if empty, will be asked at runtime
......@@ -79,7 +81,7 @@ class TX:
self.confirms = b""
self.tx_sent = False
def genMixConfirm(self, keys, conf):
def genMixConfirm(self, keys):
message = {
"document": "gmixer-mixconfirm1",
"sender_pubkey": self.sender_pubkey,
......@@ -161,12 +163,20 @@ def readConfig(dir):
conf["Crypto"] = {}
if not "Mix" in conf:
conf["Mix"] = {}
if not "Host" in conf["Server"]:
conf["Server"]["Host"] = HOST
if not "Port" in conf["Server"]:
conf["Server"]["Port"] = PORT
if not "Bind_Host" in conf["Server"]:
conf["Server"]["Bind_Host"] = BIND_HOST
if not "Bind_Port" in conf["Server"]:
conf["Server"]["Bind_Port"] = BIND_PORT
if not "Public_Host" in conf["Server"]:
conf["Server"]["Public_Host"] = PUBLIC_HOST
if not "Public_Port" in conf["Server"]:
conf["Server"]["Public_Port"] = PUBLIC_PORT
if not "BMA_Hosts" in conf["Client"]:
conf["Client"]["BMA_Hosts"] = BMA_HOSTS
if not "Proxy" in conf["Client"]:
conf["Client"]["Proxy"] = ""
if not "Proxy_OnionOnly" in conf["Client"]:
conf["Client"]["Proxy_OnionOnly"] = "0"
if not "ID_Salt" in conf["Crypto"]:
conf["Crypto"]["ID_Salt"] = ID_SALT
if not "ID_Password" in conf["Crypto"]:
......@@ -200,7 +210,7 @@ class ServerThread(Thread):
self.work = True
def run(self):
server_addr = (self.conf["Server"]["Host"], int(self.conf["Server"]["Port"]))
server_addr = (self.conf["Server"]["Bind_Host"], int(self.conf["Server"]["Bind_Port"]))
if ":" in server_addr[0]: # IPv6
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else: # IPv4
......@@ -326,7 +336,7 @@ class ServerThread(Thread):
# Save tx in pool
t = time.time()
tx = TX(sender_pubkey, receiver_pubkey, onetime_pubkey, in_amount, in_base, in_amount, in_base, message, in_comment, out_comment, send_confirm, t, t+self.conf["Mix"]["MixReqAgeMax"])
tx = TX(sender_pubkey, receiver_pubkey, onetime_pubkey, in_amount, in_base, in_amount, in_base, message, in_comment, out_comment, send_confirm, t, t+int(self.conf["Mix"]["MixReqAgeMax"]))
last_node = len(message) == 0
tx.need_send = not last_node
tx.can_confirm = last_node
......@@ -467,6 +477,11 @@ class ClientThread(Thread):
self.bma_endpoints = ["BMAS "+host for host in conf["Client"]["BMA_Hosts"].split(",")]
self.work = True
self.proxy = None
if conf["Client"]["Proxy"] != "":
_proxy = conf["Client"]["Proxy"].split(" ")
self.proxy = (_proxy[0], int(_proxy[1]))
self.proxy_onion_only = conf["Client"]["Proxy_OnionOnly"] == "1"
def detectPeers(self):# Check known peers and ask them for their known peer list
logPrint("Start peers detection", LOG_TRACE)
......@@ -483,16 +498,16 @@ class ClientThread(Thread):
message = ubjson.dumpb({
"pubkey": self.keys.pubkey,
"host": self.conf["Server"]["Host"],
"port": self.conf["Server"]["Port"],
"host": self.conf["Server"]["Public_Host"],
"port": self.conf["Server"]["Public_Port"],
"time": time.time()
})
message = peer.keys.encrypt_seal(message) # Encrypt
message = self.keys.sign(message) # Sign
logPrint("Ask "+str(peer), LOG_TRACE)
try:
header, content = sdata((peer.host, peer.port), "POST", "/list/new/"+self.keys.pubkey, message) # Send
except ConnectionRefusedError:
header, content = sdata((peer.host, peer.port), "POST", "/list/new/"+self.keys.pubkey, message, proxy=self.proxy, proxy_onion_only=self.proxy_onion_only) # Send
except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False
logPrint("Down "+str(peer), LOG_TRACE)
continue
......@@ -619,7 +634,7 @@ class ClientThread(Thread):
message = self.keys.sign(tx.out_comment.encode() + tx.message) # Sign
try:
header, content = sdata((peer.host, peer.port), "POST", "/mix/"+self.keys.pubkey+"/"+tx.out_amount+"/"+tx.out_base, message)
header, content = sdata((peer.host, peer.port), "POST", "/mix/"+self.keys.pubkey+"/"+tx.out_amount+"/"+tx.out_base, message, proxy=self.proxy, proxy_onion_only=self.proxy_onion_only)
data = ubjson.loadb(content)
assert data["mix_ok"] == tx.out_comment
tx.need_send = False
......@@ -627,7 +642,7 @@ class ClientThread(Thread):
peer.up = True
logPrint("Sent "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), LOG_TRACE)
logPrint("Up "+str(peer), LOG_TRACE)
except ConnectionRefusedError:
except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False
logPrint("Down "+str(peer), LOG_TRACE)
except (ubjson.decoder.DecoderException, KeyError, AssertionError):
......@@ -643,7 +658,7 @@ class ClientThread(Thread):
message = tx.genMixConfirm(self.keys)
try:
header, content = sdata((peer.host, peer.port), "POST", "/confirm/"+self.keys.pubkey+"/"+tx.in_comment, message)
header, content = sdata((peer.host, peer.port), "POST", "/confirm/"+self.keys.pubkey+"/"+tx.in_comment, message, proxy=self.proxy, proxy_onion_only=self.proxy_onion_only)
data = ubjson.loadb(content)
assert data["confirm_ok"] == tx.in_comment
tx.need_confirm = False
......@@ -651,7 +666,7 @@ class ClientThread(Thread):
peer.up = True
logPrint("Confirmed "+tx.sender_pubkey[:8]+" -> "+tx.receiver_pubkey[:8]+" = "+str(tx.in_amount)+":"+str(tx.in_base)+" -> "+str(tx.out_amount)+":"+str(tx.out_base), LOG_TRACE)
logPrint("Up "+str(peer), LOG_TRACE)
except ConnectionRefusedError:
except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False
logPrint("Down "+str(peer), LOG_TRACE)
except (ubjson.decoder.DecoderException, KeyError, AssertionError):
......
......@@ -17,8 +17,12 @@
"""
import sys, os, re, socket, time
import socks
from duniterpy.key import SigningKey, PublicKey
_argv = sys.argv # silkaj reads sys.argv!
sys.argv = []
import silkaj.money, silkaj.tx, silkaj.auth
sys.argv = _argv
#-------- DATA
......@@ -47,11 +51,15 @@ RECBUF = 1024
p_clen = re.compile("\r?\ncontent-length: *(\d+)\r?\n?", re.IGNORECASE)
def sdata(host, mode, url="/", data=b"", uagent="GMixer-py"):
def sdata(host, mode, url="/", data=b"", uagent="GMixer-py", proxy=None, proxy_onion_only=False):
tsocket = socket.socket
if proxy and (not proxy_onion_only or re.match("^.+\.onion$", host[0])):
socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, proxy[0], proxy[1])
tsocket = socks.socksocket
if ":" in host[0]: # IPv6
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock = tsocket(socket.AF_INET6, socket.SOCK_STREAM)
else: # IPv4
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = tsocket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(host)
raw = (mode+" "+url+" HTTP/1.1\r\nHost: "+host[0]+":"+str(host[1])+"\r\nUser-Agent: "+uagent+"\r\nAccept: */*\r\nContent-Length: "+str(len(data))+"\r\n\r\n").encode()+data
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment