mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-18 21:54:20 +01:00
clearnet: adds clearnet plugin
This can be used to enfroce clearnet connections on all peers or a given `peer_id`.
This commit is contained in:
@@ -15,6 +15,7 @@ Community curated plugins for Core-Lightning.
|
|||||||
| [circular][circular] | A smart rebalancing plugin for Core Lightning routing nodes |
|
| [circular][circular] | A smart rebalancing plugin for Core Lightning routing nodes |
|
||||||
| [csvexportpays][csvexportpays] | A plugin that exports all payments to a CSV file |
|
| [csvexportpays][csvexportpays] | A plugin that exports all payments to a CSV file |
|
||||||
| [currencyrate][currencyrate] | A plugin to convert other currencies to BTC using web requests |
|
| [currencyrate][currencyrate] | A plugin to convert other currencies to BTC using web requests |
|
||||||
|
| [clearnet][clearnet] | A plugin that can be used to enforce clearnet connections when possible |
|
||||||
| [donations][donations] | A simple donations page to accept donations from the web |
|
| [donations][donations] | A simple donations page to accept donations from the web |
|
||||||
| [drain][drain] | Draining, filling and balancing channels with automatic chunks. |
|
| [drain][drain] | Draining, filling and balancing channels with automatic chunks. |
|
||||||
| [event-websocket][event-websocket] | Exposes notifications over a Websocket |
|
| [event-websocket][event-websocket] | Exposes notifications over a Websocket |
|
||||||
@@ -242,3 +243,4 @@ un-archive it.
|
|||||||
[blip12]: https://github.com/lightning/blips/blob/42cec1d0f66eb68c840443abb609a5a9acb34f8e/blip-0012.md
|
[blip12]: https://github.com/lightning/blips/blob/42cec1d0f66eb68c840443abb609a5a9acb34f8e/blip-0012.md
|
||||||
[circular]: https://github.com/giovannizotta/circular
|
[circular]: https://github.com/giovannizotta/circular
|
||||||
[python-teos]: https://github.com/talaia-labs/python-teos
|
[python-teos]: https://github.com/talaia-labs/python-teos
|
||||||
|
[clearnet]: https://github.com/lightningd/plugins/tree/master/clearnet
|
||||||
|
|||||||
10
clearnet/README.md
Normal file
10
clearnet/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# clearnet enforcer plugin
|
||||||
|
This plugin aims to prefer usage over clearnet connections.
|
||||||
|
It does so by disconnecing TOR connections when there are known and usable
|
||||||
|
clearnet addresses.
|
||||||
|
|
||||||
|
# Options
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
## clearnet-enforce [peer_id]
|
||||||
|
Tries to enforce clearnet on all peer or on a given peer_id
|
||||||
114
clearnet/clearnet.py
Executable file
114
clearnet/clearnet.py
Executable file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import socket
|
||||||
|
from contextlib import closing
|
||||||
|
from pyln.client import Plugin, RpcError
|
||||||
|
|
||||||
|
plugin = Plugin()
|
||||||
|
|
||||||
|
|
||||||
|
def get_address_type(addrstr: str):
|
||||||
|
""" I know this can be more sophisticated, but works """
|
||||||
|
if ".onion:" in addrstr:
|
||||||
|
return 'tor'
|
||||||
|
if addrstr[0].isdigit():
|
||||||
|
return 'ipv4'
|
||||||
|
if addrstr.startswith("["):
|
||||||
|
return 'ipv6'
|
||||||
|
return 'dns'
|
||||||
|
|
||||||
|
|
||||||
|
# taken from:
|
||||||
|
# https://stackoverflow.com/questions/19196105/how-to-check-if-a-network-port-is-open
|
||||||
|
def check_socket(host: str, port: int, timeout: float = None):
|
||||||
|
""" Checks if a socket can be opened to a host """
|
||||||
|
if host.count('.') == 3:
|
||||||
|
proto = socket.AF_INET
|
||||||
|
if host.count(':') > 1:
|
||||||
|
proto = socket.AF_INET6
|
||||||
|
with closing(socket.socket(proto, socket.SOCK_STREAM)) as sock:
|
||||||
|
if timeout is not None:
|
||||||
|
sock.settimeout(timeout) # seconds (float)
|
||||||
|
if sock.connect_ex((host, port)) == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def clearnet_pid(peer: dict, messages: list):
|
||||||
|
peer_id = peer['id']
|
||||||
|
if not peer['connected']:
|
||||||
|
messages += [f"Peer is not conencted: {peer_id}"]
|
||||||
|
return False
|
||||||
|
if get_address_type(peer['netaddr'][0]) != 'tor':
|
||||||
|
messages += [f"Already connected via clearnet: {peer_id}"]
|
||||||
|
return True
|
||||||
|
|
||||||
|
# lets check what gossip knows about this peer
|
||||||
|
nodes = plugin.rpc.listnodes(peer_id)['nodes']
|
||||||
|
if len(nodes) == 0:
|
||||||
|
messages += [f"Error: No gossip for: {peer_id}"]
|
||||||
|
return
|
||||||
|
addrs = [a for a in nodes[0]['addresses'] if not a['type'].startswith("tor")]
|
||||||
|
if len(addrs) == 0:
|
||||||
|
messages += [f"Error: No clearnet addresses known for: {peer_id}"]
|
||||||
|
return
|
||||||
|
|
||||||
|
# now check addrs for open ports
|
||||||
|
for addr in addrs:
|
||||||
|
if addr['type'] == 'dns':
|
||||||
|
messages += [f"TODO: DNS lookups for: {addr['address']}"]
|
||||||
|
continue
|
||||||
|
if check_socket(addr['address'], addr['port'], 2.0):
|
||||||
|
# disconnect
|
||||||
|
result = plugin.rpc.disconnect(peer_id, True)
|
||||||
|
if len(result) != 0:
|
||||||
|
messages += [f"Error: Can't disconnect: {peer_id} {result}"]
|
||||||
|
continue
|
||||||
|
|
||||||
|
# try clearnet connection
|
||||||
|
try:
|
||||||
|
result = plugin.rpc.connect(peer_id, addr['address'], addr['port'])
|
||||||
|
newtype = result['address']['type']
|
||||||
|
if not newtype.startswith('tor'):
|
||||||
|
messages += [f"Established clearnet connection for: {peer_id} with {newtype}"]
|
||||||
|
return True
|
||||||
|
except RpcError: # we got an connection error, try reconnect
|
||||||
|
messages += [f"Error: Connection failed for: {peer_id} with {addr['type']}"]
|
||||||
|
try:
|
||||||
|
result = plugin.rpc.connect(peer_id) # without address
|
||||||
|
newtype = result['address']['type']
|
||||||
|
if not newtype.startswith('tor'):
|
||||||
|
messages += [f"Established clearnet connection for: {peer_id} with {newtype}"]
|
||||||
|
return True
|
||||||
|
except RpcError: # we got a reconnection error
|
||||||
|
messages += [f"Error: Reconnection failed for: {peer_id}"]
|
||||||
|
continue
|
||||||
|
messages += [f"Reconnected: {peer_id} with {newtype}"]
|
||||||
|
continue
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.method("clearnet")
|
||||||
|
def clearnet(plugin: Plugin, peer_id: str = None):
|
||||||
|
""" Enforce a clearnet connection on all peers or a given `peer_id`."""
|
||||||
|
if peer_id is None:
|
||||||
|
peers = plugin.rpc.listpeers(peer_id)['peers']
|
||||||
|
else:
|
||||||
|
if not isinstance(peer_id, str) or len(peer_id) != 66:
|
||||||
|
return f"Error: Invalid peer_id: {peer_id}"
|
||||||
|
peers = plugin.rpc.listpeers(peer_id)['peers']
|
||||||
|
if len(peers) == 0:
|
||||||
|
return f"Error: peer not found: {peer_id}"
|
||||||
|
|
||||||
|
messages = []
|
||||||
|
for peer in peers:
|
||||||
|
clearnet_pid(peer, messages)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
|
@plugin.init()
|
||||||
|
def init(options: dict, configuration: dict, plugin: Plugin, **kwargs):
|
||||||
|
plugin.log(f"clearnet enforcer plugin initialized")
|
||||||
|
|
||||||
|
|
||||||
|
plugin.run()
|
||||||
1
clearnet/requirements.txt
Normal file
1
clearnet/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pyln-client>=0.12
|
||||||
22
clearnet/test_clearnet.py
Normal file
22
clearnet/test_clearnet.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
from pyln.testing.fixtures import * # noqa: F401,F403
|
||||||
|
|
||||||
|
plugin_path = os.path.join(os.path.dirname(__file__), "clearnet.py")
|
||||||
|
|
||||||
|
|
||||||
|
def test_clearnet_starts(node_factory):
|
||||||
|
l1 = node_factory.get_node()
|
||||||
|
# Test dynamically
|
||||||
|
l1.rpc.plugin_start(plugin_path)
|
||||||
|
l1.rpc.plugin_stop(plugin_path)
|
||||||
|
l1.rpc.plugin_start(plugin_path)
|
||||||
|
l1.stop()
|
||||||
|
# Then statically
|
||||||
|
l1.daemon.opts["plugin"] = plugin_path
|
||||||
|
l1.start()
|
||||||
|
|
||||||
|
|
||||||
|
def test_clearnet_runs(node_factory):
|
||||||
|
pluginopt = {'plugin': plugin_path}
|
||||||
|
l1, l2 = node_factory.line_graph(2, opts=pluginopt)
|
||||||
|
l1.rpc.clearnet()
|
||||||
Reference in New Issue
Block a user