Merge pull request #7545 from yanmaani/unix_sockets
Add support for Unix domain sockets
This commit is contained in:
@@ -1475,6 +1475,8 @@ def get_parser():
|
||||
# daemon
|
||||
parser_daemon = subparsers.add_parser('daemon', help="Run Daemon")
|
||||
parser_daemon.add_argument("-d", "--detached", action="store_true", dest="detach", default=False, help="run daemon in detached mode")
|
||||
parser_daemon.add_argument("--rpcsock", dest="rpcsock", default=None, help="what socket type to which to bind RPC daemon", choices=['unix', 'tcp', 'auto'])
|
||||
parser_daemon.add_argument("--rpcsockpath", dest="rpcsockpath", help="where to place RPC file socket")
|
||||
add_network_options(parser_daemon)
|
||||
add_global_options(parser_daemon)
|
||||
# commands
|
||||
|
||||
@@ -61,10 +61,15 @@ _logger = get_logger(__name__)
|
||||
class DaemonNotRunning(Exception):
|
||||
pass
|
||||
|
||||
def get_rpcsock_defaultpath(config: SimpleConfig):
|
||||
return os.path.join(config.path, 'daemon_rpc_socket')
|
||||
|
||||
def get_rpcsock_default_type(osname):
|
||||
return 'tcp'
|
||||
|
||||
def get_lockfile(config: SimpleConfig):
|
||||
return os.path.join(config.path, 'daemon')
|
||||
|
||||
|
||||
def remove_lockfile(lockfile):
|
||||
os.unlink(lockfile)
|
||||
|
||||
@@ -96,7 +101,15 @@ def request(config: SimpleConfig, endpoint, args=(), timeout=60):
|
||||
create_time = None
|
||||
try:
|
||||
with open(lockfile) as f:
|
||||
(host, port), create_time = ast.literal_eval(f.read())
|
||||
socktype, address, create_time = ast.literal_eval(f.read())
|
||||
if socktype == 'unix':
|
||||
path = address
|
||||
(host, port) = "127.0.0.1", 0
|
||||
# We still need a host and port for e.g. HTTP Host header
|
||||
elif socktype == 'tcp':
|
||||
(host, port) = address
|
||||
else:
|
||||
raise Exception(f"corrupt lockfile; socktype={socktype!r}")
|
||||
except Exception:
|
||||
raise DaemonNotRunning()
|
||||
rpc_user, rpc_password = get_rpc_credentials(config)
|
||||
@@ -104,7 +117,13 @@ def request(config: SimpleConfig, endpoint, args=(), timeout=60):
|
||||
auth = aiohttp.BasicAuth(login=rpc_user, password=rpc_password)
|
||||
loop = asyncio.get_event_loop()
|
||||
async def request_coroutine():
|
||||
async with aiohttp.ClientSession(auth=auth) as session:
|
||||
if socktype == 'unix':
|
||||
connector = aiohttp.UnixConnector(path=path)
|
||||
elif socktype == 'tcp':
|
||||
connector = None # This will transform into TCP.
|
||||
else:
|
||||
raise Exception(f"impossible socktype ({socktype!r})")
|
||||
async with aiohttp.ClientSession(auth=auth, connector=connector) as session:
|
||||
c = util.JsonRPCClient(session, server_url)
|
||||
return await c.request(endpoint, *args)
|
||||
try:
|
||||
@@ -225,6 +244,9 @@ class CommandsServer(AuthenticatedServer):
|
||||
self.daemon = daemon
|
||||
self.fd = fd
|
||||
self.config = daemon.config
|
||||
sockettype = self.config.get('rpcsock', 'auto')
|
||||
self.socktype = sockettype if sockettype != 'auto' else get_rpcsock_default_type(os.name)
|
||||
self.sockpath = self.config.get('rpcsockpath', get_rpcsock_defaultpath(self.config))
|
||||
self.host = self.config.get('rpchost', '127.0.0.1')
|
||||
self.port = self.config.get('rpcport', 0)
|
||||
self.app = web.Application()
|
||||
@@ -239,10 +261,21 @@ class CommandsServer(AuthenticatedServer):
|
||||
async def run(self):
|
||||
self.runner = web.AppRunner(self.app)
|
||||
await self.runner.setup()
|
||||
site = web.TCPSite(self.runner, self.host, self.port)
|
||||
if self.socktype == 'unix':
|
||||
site = web.UnixSite(self.runner, self.sockpath)
|
||||
elif self.socktype == 'tcp':
|
||||
site = web.TCPSite(self.runner, self.host, self.port)
|
||||
else:
|
||||
raise Exception(f"unknown socktype '{self.socktype!r}'")
|
||||
await site.start()
|
||||
socket = site._server.sockets[0]
|
||||
os.write(self.fd, bytes(repr((socket.getsockname(), time.time())), 'utf8'))
|
||||
if self.socktype == 'unix':
|
||||
addr = self.sockpath
|
||||
elif self.socktype == 'tcp':
|
||||
addr = socket.getsockname()
|
||||
else:
|
||||
raise Exception(f"impossible socktype ({self.socktype!r})")
|
||||
os.write(self.fd, bytes(repr((self.socktype, addr, time.time())), 'utf8'))
|
||||
os.close(self.fd)
|
||||
|
||||
async def ping(self):
|
||||
|
||||
@@ -34,6 +34,13 @@ class TestLightning(unittest.TestCase):
|
||||
self.run_shell(['stop', agent])
|
||||
|
||||
|
||||
class TestUnixSockets(TestLightning):
|
||||
agents = []
|
||||
|
||||
def test_unixsockets(self):
|
||||
self.run_shell(['unixsockets'])
|
||||
|
||||
|
||||
class TestLightningAB(TestLightning):
|
||||
agents = ['alice', 'bob']
|
||||
|
||||
|
||||
@@ -356,3 +356,19 @@ if [[ $1 == "watchtower" ]]; then
|
||||
echo "watchtower publishes justice transaction"
|
||||
wait_until_spent $ctx_id 1 # alice's to_local gets punished immediately
|
||||
fi
|
||||
|
||||
if [[ $1 == "unixsockets" ]]; then
|
||||
# This looks different because it has to run the entire daemon
|
||||
# Test domain socket behavior
|
||||
./run_electrum --regtest daemon -d --rpcsock=unix # Start daemon with unix domain socket
|
||||
./run_electrum --regtest stop # Errors if it can't connect
|
||||
# Test custom socket path
|
||||
f=$(mktemp --dry-run)
|
||||
./run_electrum --regtest daemon -d --rpcsock=unix --rpcsockpath=$f
|
||||
[ -S $f ] # filename exists and is socket
|
||||
./run_electrum --regtest stop
|
||||
rm $f # clean up
|
||||
# Test for regressions in the ordinary TCP functionality.
|
||||
./run_electrum --regtest daemon -d --rpcsock=tcp
|
||||
./run_electrum --regtest stop
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user