221 lines
8.4 KiB
Python
Executable File
221 lines
8.4 KiB
Python
Executable File
#!/var/local/bin/python3.bash
|
|
"""
|
|
Copyright (c) 2014 by nurupo <nurupo.contributions@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
"""
|
|
import socket
|
|
import sys
|
|
import os
|
|
import logging
|
|
|
|
if sys.version_info[0] == 2:
|
|
print("ERROR: This script requires Python 3+ in order to run.")
|
|
sys.exit(1)
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
global LOG
|
|
LOG = logging.getLogger()
|
|
|
|
def print_help(prog: str) -> None:
|
|
"""Print program usage to stdout."""
|
|
LOG.info(f"Usage: {prog} <ipv4|ipv6> <ip/hostname> <port>")
|
|
LOG.info(f" Example: {prog} ipv4 192.210.149.121 33445")
|
|
LOG.info(f" Example: {prog} ipv4 23.226.230.47 33445")
|
|
LOG.info(f" Example: {prog} ipv4 node.tox.biribiri.org 33445")
|
|
LOG.info(f" Example: {prog} ipv4 cerberus.zodiaclabs.org 33445")
|
|
LOG.info(f" Example: {prog} ipv6 2604:180:1::3ded:b280 33445")
|
|
LOG.info(f" Example: {prog} socks '82.196.15.215' 33445"),
|
|
LOG.info(f" Example: {prog} socks5 '84.22.115.205' 33445"), # 'tox.verdict.gg'
|
|
LOG.info(f" Example: {prog} https '61.230.169.50' 33445"), # 'tox.initramfs.io'
|
|
LOG.info("socks or socks5 requires the environment_variable socks_proxy")
|
|
LOG.info("https requires the environment_variable https_proxy")
|
|
LOG.info("")
|
|
LOG.info("Return values:")
|
|
LOG.info(" 0 - received info reply from a node")
|
|
LOG.info(" 1 - incorrect command line arguments")
|
|
LOG.info(" 2 - didnt receive any reply from a node")
|
|
LOG.info(" 3 - received a malformed/unexpected reply")
|
|
|
|
|
|
# https://github.com/irungentoo/toxcore/blob/4940c4c62b6014d1f0586aa6aca7bf6e4ecfcf29/toxcore/network.h#L128
|
|
INFO_PACKET_ID = b"\xF0"
|
|
# https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L28
|
|
INFO_REQUEST_PACKET_LENGTH = 78
|
|
# first byte is INFO_REQUEST_ID, other bytes don't matter as long as reqest's
|
|
# length matches INFO_REQUEST_LENGTH
|
|
INFO_REQUEST_PACKET = INFO_PACKET_ID + (
|
|
b"0" * (INFO_REQUEST_PACKET_LENGTH - len(INFO_PACKET_ID)))
|
|
|
|
PACKET_ID_LENGTH = len(INFO_PACKET_ID)
|
|
PACKET_ID_LENGTH = 1
|
|
# https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L44
|
|
VERSION_LENGTH = 4
|
|
# https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L26
|
|
MAX_MOTD_LENGTH = 256
|
|
|
|
MAX_INFO_RESPONSE_PACKET_LENGTH = PACKET_ID_LENGTH + VERSION_LENGTH + MAX_MOTD_LENGTH
|
|
|
|
SOCK_TIMEOUT_SECONDS = 15.0
|
|
|
|
|
|
def iNodeInfo(protocol: str, host: str, port: int, key: str, environ=None) -> int:
|
|
"""Call the bootstrap node info RPC and output the response."""
|
|
|
|
socks = None
|
|
python_socks = None
|
|
if not environ:
|
|
environ = os.environ
|
|
if protocol == 'socks5' or protocol == 'https':
|
|
# https://github.com/4sp1r3/socksipy-branch
|
|
try:
|
|
import socks
|
|
except ImportError:
|
|
LOG.error("socks/https not supported; download to this directory\n" \
|
|
+" https://github.com/4sp1r3/socksipy-branch/socks.py")
|
|
return 4
|
|
|
|
elif protocol == 'socks':
|
|
# https://github.com/romis2012/python-socks
|
|
try:
|
|
import python_socks
|
|
except ImportError:
|
|
LOG.error("socks not supported; install python_socks\n" \
|
|
+'https://github.com/romis2012/python-socks')
|
|
return 4
|
|
|
|
if socks and protocol == "socks5" and 'socks_proxy' in environ:
|
|
sock = socks.socksocket()
|
|
proxy = environ['socks_proxy']
|
|
if proxy:
|
|
i = proxy.find('//')
|
|
if i > 0:
|
|
proxy = proxy[i+2:]
|
|
phost = proxy.split(':')[0]
|
|
pport = int(proxy.split(':')[1])
|
|
# LOG("DBUG: 'socks_proxy' in environment: ", phost, pport)
|
|
else:
|
|
LOG.debug("NO 'socks_proxy' in environment - defaulting to 127.0.0.1:1080")
|
|
phost = '127.0.0.1'
|
|
pport = 1080
|
|
sock.setproxy(socks.PROXY_TYPE_SOCKS5, phost, pport, True)
|
|
|
|
elif socks and protocol == "https" and 'https_proxy' in environ:
|
|
sock = socks.socksocket()
|
|
proxy = environ['https_proxy']
|
|
if proxy:
|
|
i = proxy.find('//')
|
|
if i > 0:
|
|
proxy = proxy[i+2:]
|
|
phost = proxy.split(':')[0]
|
|
pport = int(proxy.split(':')[1])
|
|
# LOG("DBUG: 'https_proxy' in environment: ", phost, pport)
|
|
else:
|
|
LOG.debug("NO 'https_proxy' in environment - defaulting to 127.0.0.1:8080")
|
|
phost = '127.0.0.1'
|
|
pport = 8080
|
|
sock.setproxy(socks.PROXY_TYPE_HTTP, phost, pport, True)
|
|
|
|
elif python_socks and protocol == "socks" and 'socks_proxy' in environ:
|
|
from python_socks.sync import Proxy
|
|
proxy = Proxy.from_url(environ['socks_proxy'], rdns=True)
|
|
elif protocol == "ipv4":
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
elif protocol == "ipv6":
|
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
|
else:
|
|
LOG.error("1 Invalid first argument " +protocol +" - one of: socks socks5 https ipv4 ipv6")
|
|
print_help(__file__)
|
|
return 1
|
|
|
|
try:
|
|
if python_socks:
|
|
sock = proxy.connect(dest_host=host, dest_port=port)
|
|
else:
|
|
sock.connect((host, port))
|
|
except Exception as e:
|
|
LOG.error("2 Could not connect to bootstrap node " \
|
|
+repr((host, port)) \
|
|
+': ' +str(e))
|
|
return 2
|
|
|
|
try:
|
|
sock.settimeout(SOCK_TIMEOUT_SECONDS)
|
|
sock.sendall(INFO_REQUEST_PACKET)
|
|
except Exception as e:
|
|
LOG.error("3 Could not send to bootstrap node " \
|
|
+repr((host, port)) \
|
|
+' ' +str(e))
|
|
return 3
|
|
|
|
try:
|
|
data, _ = sock.recvfrom(MAX_INFO_RESPONSE_PACKET_LENGTH)
|
|
except socket.timeout:
|
|
LOG.warn("4 The DHT bootstrap node " \
|
|
+repr((host, port)) \
|
|
+" didnt reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec.")
|
|
return 4
|
|
if len(data) == 0:
|
|
try:
|
|
data, _ = sock.recvfrom(MAX_INFO_RESPONSE_PACKET_LENGTH)
|
|
except socket.timeout:
|
|
LOG.warn("4b The DHT bootstrap node " \
|
|
+repr((host, port)) \
|
|
+" didnt reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec.")
|
|
return 4
|
|
|
|
if len(data) == 0:
|
|
LOG.warn("5 Bad response, no data from " +repr((host, port)) )
|
|
return 5
|
|
|
|
packet_id = data[:PACKET_ID_LENGTH]
|
|
if packet_id != INFO_PACKET_ID:
|
|
LOG.warn("Bad response, first byte should be {info_packet_id!r}"
|
|
+" but got {packet_id!r}({data!r})".format(
|
|
info_packet_id=INFO_PACKET_ID,
|
|
packet_id=packet_id,
|
|
data=data,
|
|
))
|
|
LOG.warn("6 Are you sure that you are pointing the script at a Tox "
|
|
"DHT bootstrap node? " \
|
|
+repr((host, port)) \
|
|
)
|
|
return 6
|
|
|
|
version = int.from_bytes(data[PACKET_ID_LENGTH:PACKET_ID_LENGTH + VERSION_LENGTH],
|
|
byteorder="big")
|
|
motd = data[PACKET_ID_LENGTH + VERSION_LENGTH:].decode("utf-8")
|
|
LOG.info("Version: " + str(version) +" MOTD: " + motd[:-1])
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) != 4:
|
|
print_help(sys.argv[0])
|
|
sys.exit(1)
|
|
|
|
try:
|
|
i = iNodeInfo(
|
|
protocol=sys.argv[1],
|
|
host=sys.argv[2],
|
|
port=int(sys.argv[3]),
|
|
)
|
|
except KeyboardInterrupt:
|
|
i = 0
|
|
sys.exit(i)
|