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

SOCKS5 proxy support

parent bc7504d0
...@@ -17,7 +17,8 @@ Install dependances: ...@@ -17,7 +17,8 @@ Install dependances:
Install the following packages: `libleveldb-dev` `libsodium-dev` `python3` `python3-pip` 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 python3 --version
# If Python < 3.6: # If Python < 3.6:
sudo pip3 install python2_secrets sudo pip3 install python2_secrets
...@@ -27,14 +28,36 @@ servers with different configs) (note that each server must have different dir) ...@@ -27,14 +28,36 @@ servers with different configs) (note that each server must have different dir)
python3 server.py -i -d ~/.gmixer-g1 python3 server.py -i -d ~/.gmixer-g1
Edit config and set your values. If `salt` or `password` are empty, they will be asked Edit config and set your values. If you are not using any proxy, `Bind_` and `Public_` addresses should be the same.
at runtime. For security reasons, it is not a good idea to write them in commandline. 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: Now, start the server:
python3 server.py -s python3 server.py -s
# Use -d if using a different dir than default # 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 ## Protocols
### Peer list file ### Peer list file
......
...@@ -48,8 +48,8 @@ class Confirmation(): ...@@ -48,8 +48,8 @@ class Confirmation():
def genKeys(): def genKeys():
return secrets.token_urlsafe(), secrets.token_urlsafe() return secrets.token_urlsafe(), secrets.token_urlsafe()
def get_peers(host): def get_peers(host, proxy=None, proxy_onion_only=False):
header, content = sdata(host, "GET", "/pubkey/list") header, content = sdata(host, "GET", "/pubkey/list", proxy=proxy, proxy_onion_only=proxy_onion_only)
try: try:
data = ubjson.loadb(content) data = ubjson.loadb(content)
...@@ -86,7 +86,7 @@ def build_path(peers, receiver, layers=3): ...@@ -86,7 +86,7 @@ def build_path(peers, receiver, layers=3):
path.append(receiver) path.append(receiver)
return host, path 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() start_time = time.time()
onetime_keys = [] onetime_keys = []
...@@ -104,7 +104,7 @@ def mix(amount, base, sender, path, host): ...@@ -104,7 +104,7 @@ def mix(amount, base, sender, path, host):
while True: while True:
try: 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: except ConnectionRefusedError:
print("Error: Connection refused; retrying...") print("Error: Connection refused; retrying...")
time.sleep(3) time.sleep(3)
...@@ -125,7 +125,7 @@ def mix(amount, base, sender, path, host): ...@@ -125,7 +125,7 @@ def mix(amount, base, sender, path, host):
time.sleep(5) time.sleep(5)
print("Asking input node for confirmation...") print("Asking input node for confirmation...")
try: 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: except ConnectionRefusedError:
continue continue
...@@ -202,7 +202,7 @@ def mix(amount, base, sender, path, host): ...@@ -202,7 +202,7 @@ def mix(amount, base, sender, path, host):
exit() 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: ") salt = getpass.getpass("Salt: ")
password = getpass.getpass("Psw: ") password = getpass.getpass("Psw: ")
keys = SigningKey.from_credentials(salt, password) # sender keys = SigningKey.from_credentials(salt, password) # sender
...@@ -210,7 +210,7 @@ async def test1(host, receiver, amount=1000, layers=3): ...@@ -210,7 +210,7 @@ async def test1(host, receiver, amount=1000, layers=3):
if input("OK? [yn]: ").lower() != "y": if input("OK? [yn]: ").lower() != "y":
return return
peers = get_peers(host) peers = get_peers(host, proxy, proxy_onion_only)
if peers == None: if peers == None:
print("Error getting peer list") print("Error getting peer list")
exit(1) exit(1)
...@@ -224,7 +224,7 @@ async def test1(host, receiver, amount=1000, layers=3): ...@@ -224,7 +224,7 @@ async def test1(host, receiver, amount=1000, layers=3):
if input("OK? [yn]: ").lower() == "y": if input("OK? [yn]: ").lower() == "y":
break break
mix(amount, 0, keys, path, host1) mix(amount, 0, keys, path, host1, proxy, proxy_onion_only)
if __name__ == "__main__": if __name__ == "__main__":
if "--help" in sys.argv: if "--help" in sys.argv:
...@@ -235,9 +235,11 @@ Options: ...@@ -235,9 +235,11 @@ Options:
-l <int> number of onion layers (default 3) -l <int> number of onion layers (default 3)
-a <int> amount (default 1000 = 10.00Ğ1) -a <int> amount (default 1000 = 10.00Ğ1)
-h <host> <port> host for sync peer list -h <host> <port> host for sync peer list
-p <host> <port> SOCKS5 proxy
--onion use proxy only when connecting to .onion
Example: 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 ⤷ send 10Ğ1 to Duniter developers
""") """)
exit() exit()
...@@ -253,5 +255,9 @@ python3 client.py -h 2a01:cb19:982:f800:ec34:6087:5197:4752 10951 -r 78ZwwgpgdH5 ...@@ -253,5 +255,9 @@ python3 client.py -h 2a01:cb19:982:f800:ec34:6087:5197:4752 10951 -r 78ZwwgpgdH5
port = int(getargv("-h", "", 2)) port = int(getargv("-h", "", 2))
amount = int(getargv("-a", "1000")) amount = int(getargv("-a", "1000"))
layers = int(getargv("-l", "3")) 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 ...@@ -38,8 +38,10 @@ out_ = something I'm sending to a node
""" """
DIR = "~/.gmixer" DIR = "~/.gmixer"
HOST = socket.gethostname() BIND_HOST = socket.gethostname()
PORT = "10951" 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" 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_SALT = "" # if empty, will be asked at runtime
ID_PSW = "" # if empty, will be asked at runtime ID_PSW = "" # if empty, will be asked at runtime
...@@ -79,7 +81,7 @@ class TX: ...@@ -79,7 +81,7 @@ class TX:
self.confirms = b"" self.confirms = b""
self.tx_sent = False self.tx_sent = False
def genMixConfirm(self, keys, conf): def genMixConfirm(self, keys):
message = { message = {
"document": "gmixer-mixconfirm1", "document": "gmixer-mixconfirm1",
"sender_pubkey": self.sender_pubkey, "sender_pubkey": self.sender_pubkey,
...@@ -161,12 +163,20 @@ def readConfig(dir): ...@@ -161,12 +163,20 @@ def readConfig(dir):
conf["Crypto"] = {} conf["Crypto"] = {}
if not "Mix" in conf: if not "Mix" in conf:
conf["Mix"] = {} conf["Mix"] = {}
if not "Host" in conf["Server"]: if not "Bind_Host" in conf["Server"]:
conf["Server"]["Host"] = HOST conf["Server"]["Bind_Host"] = BIND_HOST
if not "Port" in conf["Server"]: if not "Bind_Port" in conf["Server"]:
conf["Server"]["Port"] = PORT 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"]: if not "BMA_Hosts" in conf["Client"]:
conf["Client"]["BMA_Hosts"] = BMA_HOSTS 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"]: if not "ID_Salt" in conf["Crypto"]:
conf["Crypto"]["ID_Salt"] = ID_SALT conf["Crypto"]["ID_Salt"] = ID_SALT
if not "ID_Password" in conf["Crypto"]: if not "ID_Password" in conf["Crypto"]:
...@@ -200,7 +210,7 @@ class ServerThread(Thread): ...@@ -200,7 +210,7 @@ class ServerThread(Thread):
self.work = True self.work = True
def run(self): 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 if ":" in server_addr[0]: # IPv6
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
else: # IPv4 else: # IPv4
...@@ -326,7 +336,7 @@ class ServerThread(Thread): ...@@ -326,7 +336,7 @@ class ServerThread(Thread):
# Save tx in pool # Save tx in pool
t = time.time() 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 last_node = len(message) == 0
tx.need_send = not last_node tx.need_send = not last_node
tx.can_confirm = last_node tx.can_confirm = last_node
...@@ -467,6 +477,11 @@ class ClientThread(Thread): ...@@ -467,6 +477,11 @@ class ClientThread(Thread):
self.bma_endpoints = ["BMAS "+host for host in conf["Client"]["BMA_Hosts"].split(",")] self.bma_endpoints = ["BMAS "+host for host in conf["Client"]["BMA_Hosts"].split(",")]
self.work = True 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 def detectPeers(self):# Check known peers and ask them for their known peer list
logPrint("Start peers detection", LOG_TRACE) logPrint("Start peers detection", LOG_TRACE)
...@@ -483,16 +498,16 @@ class ClientThread(Thread): ...@@ -483,16 +498,16 @@ class ClientThread(Thread):
message = ubjson.dumpb({ message = ubjson.dumpb({
"pubkey": self.keys.pubkey, "pubkey": self.keys.pubkey,
"host": self.conf["Server"]["Host"], "host": self.conf["Server"]["Public_Host"],
"port": self.conf["Server"]["Port"], "port": self.conf["Server"]["Public_Port"],
"time": time.time() "time": time.time()
}) })
message = peer.keys.encrypt_seal(message) # Encrypt message = peer.keys.encrypt_seal(message) # Encrypt
message = self.keys.sign(message) # Sign message = self.keys.sign(message) # Sign
logPrint("Ask "+str(peer), LOG_TRACE) logPrint("Ask "+str(peer), LOG_TRACE)
try: try:
header, content = sdata((peer.host, peer.port), "POST", "/list/new/"+self.keys.pubkey, message) # Send 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: except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False peer.up = False
logPrint("Down "+str(peer), LOG_TRACE) logPrint("Down "+str(peer), LOG_TRACE)
continue continue
...@@ -619,7 +634,7 @@ class ClientThread(Thread): ...@@ -619,7 +634,7 @@ class ClientThread(Thread):
message = self.keys.sign(tx.out_comment.encode() + tx.message) # Sign message = self.keys.sign(tx.out_comment.encode() + tx.message) # Sign
try: 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) data = ubjson.loadb(content)
assert data["mix_ok"] == tx.out_comment assert data["mix_ok"] == tx.out_comment
tx.need_send = False tx.need_send = False
...@@ -627,7 +642,7 @@ class ClientThread(Thread): ...@@ -627,7 +642,7 @@ class ClientThread(Thread):
peer.up = True 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("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) logPrint("Up "+str(peer), LOG_TRACE)
except ConnectionRefusedError: except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False peer.up = False
logPrint("Down "+str(peer), LOG_TRACE) logPrint("Down "+str(peer), LOG_TRACE)
except (ubjson.decoder.DecoderException, KeyError, AssertionError): except (ubjson.decoder.DecoderException, KeyError, AssertionError):
...@@ -643,7 +658,7 @@ class ClientThread(Thread): ...@@ -643,7 +658,7 @@ class ClientThread(Thread):
message = tx.genMixConfirm(self.keys) message = tx.genMixConfirm(self.keys)
try: 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) data = ubjson.loadb(content)
assert data["confirm_ok"] == tx.in_comment assert data["confirm_ok"] == tx.in_comment
tx.need_confirm = False tx.need_confirm = False
...@@ -651,7 +666,7 @@ class ClientThread(Thread): ...@@ -651,7 +666,7 @@ class ClientThread(Thread):
peer.up = True 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("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) logPrint("Up "+str(peer), LOG_TRACE)
except ConnectionRefusedError: except (ConnectionRefusedError, socks.GeneralProxyError):
peer.up = False peer.up = False
logPrint("Down "+str(peer), LOG_TRACE) logPrint("Down "+str(peer), LOG_TRACE)
except (ubjson.decoder.DecoderException, KeyError, AssertionError): except (ubjson.decoder.DecoderException, KeyError, AssertionError):
......
...@@ -17,8 +17,12 @@ ...@@ -17,8 +17,12 @@
""" """
import sys, os, re, socket, time import sys, os, re, socket, time
import socks
from duniterpy.key import SigningKey, PublicKey from duniterpy.key import SigningKey, PublicKey
_argv = sys.argv # silkaj reads sys.argv!
sys.argv = []
import silkaj.money, silkaj.tx, silkaj.auth import silkaj.money, silkaj.tx, silkaj.auth
sys.argv = _argv
#-------- DATA #-------- DATA
...@@ -47,11 +51,15 @@ RECBUF = 1024 ...@@ -47,11 +51,15 @@ RECBUF = 1024
p_clen = re.compile("\r?\ncontent-length: *(\d+)\r?\n?", re.IGNORECASE) 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 if ":" in host[0]: # IPv6
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock = tsocket(socket.AF_INET6, socket.SOCK_STREAM)
else: # IPv4 else: # IPv4
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = tsocket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(host) 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 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