mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-19 14:14:20 +01:00
backup: Add support for Tor
Add support for connecting to the backup server through Tor, via its SOCKS5 proxy. See "Usage with Tor" in remote.md for documentation.
This commit is contained in:
committed by
Christian Decker
parent
c8be8a11d7
commit
eada16c688
@@ -15,10 +15,12 @@ The remote backup system consists of two parts:
|
||||
- A server daemon that receives changes from the backup backend and communicates with a local backup backend
|
||||
to store them. The server side does not need to be running c-lightning, nor have it installed.
|
||||
|
||||
The backend URL format is `socket:<host>:<port>`. For example `socket:127.0.0.1:1234`. To supply a IPv6
|
||||
### URL scheme
|
||||
|
||||
The backend URL format is `socket:<host>:<port>[?<param>=<value>[&...]]`. For example `socket:127.0.0.1:1234`. To supply a IPv6
|
||||
address use the bracketed syntax `socket:[::1]:1234`.
|
||||
|
||||
To run the server against a local backend use `backup-cli server file://.../ 127.0.0.1:1234`.
|
||||
The only currently accepted `<param>` is `proxy`. This can be used to connect to the backup server through a proxy. See [Usage with Tor](#usage-with-tor).
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -42,6 +44,9 @@ lightningd ... \
|
||||
--important-plugin /path/to/plugins/backup/backup.py
|
||||
```
|
||||
|
||||
Usage with SSH
|
||||
--------------
|
||||
|
||||
The easiest way to connect the server and client if they are not running on the same host is with a ssh
|
||||
forward. For example, when connecting from another machine to the one running c-lightning use:
|
||||
|
||||
@@ -55,6 +60,25 @@ Or when it is the other way around:
|
||||
ssh backupserver -L 8700:127.0.0.1:8700
|
||||
```
|
||||
|
||||
Usage with Tor
|
||||
--------------
|
||||
|
||||
To use the backup plugin with Tor the Python module PySocks needs to be installed (`pip install --user pysocks`).
|
||||
|
||||
Assuming Tor's `SocksPort` is 9050, the following URL can be used to connect the backup plugin to a backup server over an onion service:
|
||||
|
||||
```
|
||||
socket:axz53......onion:8700?proxy=socks5:127.0.0.1:9050
|
||||
```
|
||||
|
||||
On the server side, manually define an onion service in `torrc` that forwards incoming connections to the local port, e.g.:
|
||||
|
||||
```
|
||||
HiddenServiceDir /var/lib/tor/lightning/
|
||||
HiddenServiceVersion 3
|
||||
HiddenServicePort 8700 127.0.0.1:8700
|
||||
```
|
||||
|
||||
Goals
|
||||
-----
|
||||
|
||||
|
||||
@@ -6,34 +6,37 @@ from urllib.parse import urlparse, parse_qs
|
||||
from backend import Backend, Change
|
||||
from protocol import PacketType, recvall, PKT_CHANGE_TYPES, change_from_packet, packet_from_change, send_packet, recv_packet
|
||||
|
||||
SocketURLInfo = namedtuple('SocketURLInfo', ['host', 'port', 'addrtype'])
|
||||
HostPortInfo = namedtuple('HostPortInfo', ['host', 'port', 'addrtype'])
|
||||
SocketURLInfo = namedtuple('SocketURLInfo', ['target', 'proxytype', 'proxytarget'])
|
||||
|
||||
# Network address type.
|
||||
class AddrType:
|
||||
IPv4 = 0
|
||||
IPv6 = 1
|
||||
NAME = 2
|
||||
|
||||
def parse_socket_url(destination: str) -> SocketURLInfo:
|
||||
'''Parse a socket: URL to extract the information contained in it.'''
|
||||
url = urlparse(destination)
|
||||
if url.scheme != 'socket':
|
||||
raise ValueError('Scheme for socket backend must be socket:...')
|
||||
# Proxy type. Only SOCKS5 supported at the moment as this is sufficient for Tor.
|
||||
class ProxyType:
|
||||
DIRECT = 0
|
||||
SOCKS5 = 1
|
||||
|
||||
if url.path.startswith('['): # bracketed IPv6 address
|
||||
eidx = url.path.find(']')
|
||||
def parse_host_port(path: str) -> HostPortInfo:
|
||||
'''Parse a host:port pair.'''
|
||||
if path.startswith('['): # bracketed IPv6 address
|
||||
eidx = path.find(']')
|
||||
if eidx == -1:
|
||||
raise ValueError('Unterminated bracketed host address.')
|
||||
host = url.path[1:eidx]
|
||||
host = path[1:eidx]
|
||||
addrtype = AddrType.IPv6
|
||||
eidx += 1
|
||||
if eidx >= len(url.path) or url.path[eidx] != ':':
|
||||
if eidx >= len(path) or path[eidx] != ':':
|
||||
raise ValueError('Port number missing.')
|
||||
eidx += 1
|
||||
else:
|
||||
eidx = url.path.find(':')
|
||||
eidx = path.find(':')
|
||||
if eidx == -1:
|
||||
raise ValueError('Port number missing.')
|
||||
host = url.path[0:eidx]
|
||||
host = path[0:eidx]
|
||||
if re.match('\d+\.\d+\.\d+\.\d+$', host): # matches IPv4 address format
|
||||
addrtype = AddrType.IPv4
|
||||
else:
|
||||
@@ -41,17 +44,40 @@ def parse_socket_url(destination: str) -> SocketURLInfo:
|
||||
eidx += 1
|
||||
|
||||
try:
|
||||
port = int(url.path[eidx:])
|
||||
port = int(path[eidx:])
|
||||
except ValueError:
|
||||
raise ValueError('Invalid port number')
|
||||
|
||||
return HostPortInfo(host=host, port=port, addrtype=addrtype)
|
||||
|
||||
def parse_socket_url(destination: str) -> SocketURLInfo:
|
||||
'''Parse a socket: URL to extract the information contained in it.'''
|
||||
url = urlparse(destination)
|
||||
if url.scheme != 'socket':
|
||||
raise ValueError('Scheme for socket backend must be socket:...')
|
||||
|
||||
target = parse_host_port(url.path)
|
||||
|
||||
proxytype = ProxyType.DIRECT
|
||||
proxytarget = None
|
||||
# parse query parameters
|
||||
# reject unknown parameters (currently all of them)
|
||||
qs = parse_qs(url.query)
|
||||
if len(qs):
|
||||
raise ValueError('Invalid query string')
|
||||
for (key, values) in qs.items():
|
||||
if key == 'proxy': # proxy=socks5:127.0.0.1:9050
|
||||
if len(values) != 1:
|
||||
raise ValueError('Proxy can only have one value')
|
||||
|
||||
return SocketURLInfo(host=host, port=port, addrtype=addrtype)
|
||||
(ptype, ptarget) = values[0].split(':', 1)
|
||||
if ptype != 'socks5':
|
||||
raise ValueError('Unknown proxy type ' + ptype)
|
||||
|
||||
proxytype = ProxyType.SOCKS5
|
||||
proxytarget = parse_host_port(ptarget)
|
||||
else:
|
||||
raise ValueError('Unknown query string parameter ' + key)
|
||||
|
||||
return SocketURLInfo(target=target, proxytype=proxytype, proxytarget=proxytarget)
|
||||
|
||||
class SocketBackend(Backend):
|
||||
def __init__(self, destination: str, create: bool):
|
||||
@@ -59,13 +85,22 @@ class SocketBackend(Backend):
|
||||
self.prev_version = None
|
||||
self.destination = destination
|
||||
self.url = parse_socket_url(destination)
|
||||
if self.url.addrtype == AddrType.IPv6:
|
||||
|
||||
if self.url.proxytype == ProxyType.DIRECT:
|
||||
if self.url.target.addrtype == AddrType.IPv6:
|
||||
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
else: # TODO NAME is assumed to be IPv4 for now
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
logging.info('Initialized socket backend, connecting to {}:{} (addrtype {})...'.format(
|
||||
self.url.host, self.url.port, self.url.addrtype))
|
||||
self.sock.connect((self.url.host, self.url.port))
|
||||
else:
|
||||
assert(self.url.proxytype == ProxyType.SOCKS5)
|
||||
import socks
|
||||
self.sock = socks.socksocket()
|
||||
self.sock.set_proxy(socks.SOCKS5, self.url.proxytarget.host, self.url.proxytarget.port)
|
||||
|
||||
logging.info('Initialized socket backend, connecting to {}:{} (addrtype {}, proxytype {}, proxytarget {})...'.format(
|
||||
self.url.target.host, self.url.target.port, self.url.target.addrtype,
|
||||
self.url.proxytype, self.url.proxytarget))
|
||||
self.sock.connect((self.url.target.host, self.url.target.port))
|
||||
logging.info('Connected to {}'.format(destination))
|
||||
|
||||
def _send_packet(self, typ: int, payload: bytes) -> None:
|
||||
|
||||
@@ -297,21 +297,40 @@ def test_parse_socket_url():
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:12bla')
|
||||
# fail: unrecognized query string key
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:1234?dummy=value')
|
||||
# fail: incomplete proxy spec
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:1234?proxy=socks5')
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:1234?proxy=socks5:')
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:1234?proxy=socks5:127.0.0.1:')
|
||||
# fail: unknown proxy scheme
|
||||
socketbackend.parse_socket_url('socket:127.0.0.1:1234?proxy=socks6:127.0.0.1:9050')
|
||||
|
||||
# IPv4
|
||||
s = socketbackend.parse_socket_url('socket:127.0.0.1:1234')
|
||||
assert(s.host == '127.0.0.1')
|
||||
assert(s.port == 1234)
|
||||
assert(s.addrtype == socketbackend.AddrType.IPv4)
|
||||
assert(s.target.host == '127.0.0.1')
|
||||
assert(s.target.port == 1234)
|
||||
assert(s.target.addrtype == socketbackend.AddrType.IPv4)
|
||||
assert(s.proxytype == socketbackend.ProxyType.DIRECT)
|
||||
|
||||
# IPv6
|
||||
s = socketbackend.parse_socket_url('socket:[::1]:1235')
|
||||
assert(s.host == '::1')
|
||||
assert(s.port == 1235)
|
||||
assert(s.addrtype == socketbackend.AddrType.IPv6)
|
||||
assert(s.target.host == '::1')
|
||||
assert(s.target.port == 1235)
|
||||
assert(s.target.addrtype == socketbackend.AddrType.IPv6)
|
||||
assert(s.proxytype == socketbackend.ProxyType.DIRECT)
|
||||
|
||||
# Hostname
|
||||
s = socketbackend.parse_socket_url('socket:backup.local:1236')
|
||||
assert(s.host == 'backup.local')
|
||||
assert(s.port == 1236)
|
||||
assert(s.addrtype == socketbackend.AddrType.NAME)
|
||||
assert(s.target.host == 'backup.local')
|
||||
assert(s.target.port == 1236)
|
||||
assert(s.target.addrtype == socketbackend.AddrType.NAME)
|
||||
assert(s.proxytype == socketbackend.ProxyType.DIRECT)
|
||||
|
||||
# Tor
|
||||
s = socketbackend.parse_socket_url('socket:backupserver.onion:1234?proxy=socks5:127.0.0.1:9050')
|
||||
assert(s.target.host == 'backupserver.onion')
|
||||
assert(s.target.port == 1234)
|
||||
assert(s.target.addrtype == socketbackend.AddrType.NAME)
|
||||
assert(s.proxytype == socketbackend.ProxyType.SOCKS5)
|
||||
assert(s.proxytarget.host == '127.0.0.1')
|
||||
assert(s.proxytarget.port == 9050)
|
||||
assert(s.proxytarget.addrtype == socketbackend.AddrType.IPv4)
|
||||
|
||||
Reference in New Issue
Block a user