Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2015-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Dummy Socks5 server for testing."""7import select8import socket9import threading10import queue11import logging13from .netutil import (14format_addr_port,15format_sock,16set_ephemeral_port_range,17)19logger = logging.getLogger("TestFramework.socks5")21# Protocol constants22class Command:23CONNECT = 0x0125class AddressType:26IPV4 = 0x0127DOMAINNAME = 0x0328IPV6 = 0x0430# Utility functions31def recvall(s, n):32"""Receive n bytes from a socket, or fail."""33rv = bytearray()34while n > 0:35d = s.recv(n)36if not d:37raise IOError('Unexpected end of stream')38rv.extend(d)39n -= len(d)40return rv42def sendall(s, data):43"""Send all data to a socket, or fail."""44sent = 045while sent < len(data):46_, wlist, _ = select.select([], [s], [])47if len(wlist) > 0:48n = s.send(data[sent:])49if n == 0:50raise IOError('send() on socket returned 0')51sent += n53def forward_sockets(a, b, wakeup_socket, serv):54"""Forwards data between sockets a and b until EOF, error, or shutdown.56Monitors wakeup_socket for a shutdown signal and checks serv.is_running()57to exit gracefully when the server is stopping.58"""59# Prefix messages with e.g.:60# forward_sockets(a{remote=127.0.0.1:36935, local=127.0.0.1:9050} <-> b{local=127.0.0.1:33424, remote=127.0.0.1:8333})61log_prefix = ("forward_sockets("62f"a{{remote={format_sock(a, local=False)}, local={format_sock(a, local=True)}}} <-> "63f"b{{local={format_sock(b, local=True)}, remote={format_sock(b, local=False)}}}): ")65# Mark as non-blocking so that we do not end up in a deadlock-like situation66# where we block and wait on data from `a` while there is data ready to be67# received on `b` and forwarded to `a`. And at the same time the application68# at `a` is not sending anything because it waits for the data from `b` to69# respond.70a.setblocking(False)71b.setblocking(False)72sockets = [a, b, wakeup_socket]73while True:74# Blocking select with timeout75rlist, _, xlist = select.select(sockets, [], sockets, 2)76if not serv.is_running():77logger.debug(f"{log_prefix}Exit due to shutdown")78return79if len(xlist) > 0:80raise IOError(f"{log_prefix}Exceptional condition on socket")81for s in rlist:82try:83data = s.recv(4096)84if data is None or len(data) == 0:85return86if s == a:87sendall(b, data)88elif s == b:89sendall(a, data)90except (BrokenPipeError, ConnectionResetError) as e:91logger.debug(f"{log_prefix}cannot send or receive data on socket {'a' if s == a else 'b'}: {str(e)}")92return94# Implementation classes95class Socks5Configuration():96"""Proxy configuration."""97def __init__(self):98self.addr = None # Bind address (must be set)99self.af = socket.AF_INET # Bind address family100self.unauth = False # Support unauthenticated101self.auth = False # Support authentication102self.keep_alive = False # Do not automatically close connections103# This function is called whenever a new connection arrives to the proxy104# and it decides where the connection is redirected to. It is passed:105# - the address the client requested to connect to106# - the port the client requested to connect to107# - the client's socket address as seen by the proxy, formatted as host:port108# It is supposed to return an object like:109# {110# "actual_to_addr": "127.0.0.1"111# "actual_to_port": 28276112# }113# or None.114# If it returns an object then the connection is redirected to actual_to_addr:actual_to_port.115# If it returns None, or destinations_factory itself is None then the connection is closed.116self.destinations_factory = None118class Socks5Command():119"""Information about an incoming socks5 command."""120def __init__(self, cmd, atyp, addr, port, username, password):121self.cmd = cmd # Command (one of Command.*)122self.atyp = atyp # Address type (one of AddressType.*)123self.addr = addr # Address124self.port = port # Port to connect to125self.username = username126self.password = password127def __repr__(self):128return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password)130class Socks5Connection():131def __init__(self, serv, conn):132self.serv = serv133self.conn = conn134# Socket-pair used to wake up blocking forwarding select135# Note: a pipe could be used as well, but that does not work with select() on Windows136self.wakeup_socket_pair = socket.socketpair()137# Index of this handler (within the server)138self.handler_index = None140def handle(self):141"""Handle socks5 request according to RFC1928."""142log_exception_prefix = "Socks5Connection.handle(): "143try:144proxy_client = format_sock(self.conn, local=False)145log_exception_prefix = ("Socks5Connection.handle("146f"client={proxy_client}, "147f"proxy={format_sock(self.conn, local=True)}): ")149# Verify socks version150ver = recvall(self.conn, 1)[0]151if ver != 0x05:152raise IOError('Invalid socks version %i' % ver)153# Choose authentication method154nmethods = recvall(self.conn, 1)[0]155methods = bytearray(recvall(self.conn, nmethods))156method = None157if 0x02 in methods and self.serv.conf.auth:158method = 0x02 # username/password159elif 0x00 in methods and self.serv.conf.unauth:160method = 0x00 # unauthenticated161if method is None:162raise IOError('No supported authentication method was offered')163# Send response164self.conn.sendall(bytearray([0x05, method]))165# Read authentication (optional)166username = None167password = None168if method == 0x02:169ver = recvall(self.conn, 1)[0]170if ver != 0x01:171raise IOError('Invalid auth packet version %i' % ver)172ulen = recvall(self.conn, 1)[0]173username = str(recvall(self.conn, ulen))174plen = recvall(self.conn, 1)[0]175password = str(recvall(self.conn, plen))176# Send authentication response177self.conn.sendall(bytearray([0x01, 0x00]))179# Read connect request180ver, cmd, _, atyp = recvall(self.conn, 4)181if ver != 0x05:182raise IOError('Invalid socks version %i in connect request' % ver)183if cmd != Command.CONNECT:184raise IOError('Unhandled command %i in connect request' % cmd)186if atyp == AddressType.IPV4:187addr = recvall(self.conn, 4)188elif atyp == AddressType.DOMAINNAME:189n = recvall(self.conn, 1)[0]190addr = recvall(self.conn, n)191elif atyp == AddressType.IPV6:192addr = recvall(self.conn, 16)193else:194raise IOError('Unknown address type %i' % atyp)195port_hi,port_lo = recvall(self.conn, 2)196port = (port_hi << 8) | port_lo198# Reply SUCCESS before calling destinations_factory, so the client can finish199# establishing the connection and register the peer; factories that consult200# getpeerinfo depend on that order.201self.conn.sendall(bytearray([0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))203cmdin = Socks5Command(cmd, atyp, addr, port, username, password)204self.serv.queue.put(cmdin)205logger.debug('Proxy: %s', cmdin)207requested_to_addr = addr.decode("utf-8")208requested_to = format_addr_port(requested_to_addr, port)210if self.serv.is_running():211if self.serv.conf.destinations_factory is not None:212dest = self.serv.conf.destinations_factory(requested_to_addr, port, proxy_client)213if dest is not None:214logger.debug(f"Serving connection to {requested_to}, will redirect it to "215f"{dest['actual_to_addr']}:{dest['actual_to_port']} instead")216with socket.create_connection((dest["actual_to_addr"], dest["actual_to_port"])) as conn_to:217forward_sockets(self.conn, conn_to, self.wakeup_socket_pair[1], self.serv)218conn_to.close()219else:220logger.debug(f"Can't serve the connection to {requested_to}: the destinations factory returned None")221else:222logger.debug(f"Can't serve the connection to {requested_to}: no destinations factory")224# Disconnect happens in the "finally" block below.226except (BrokenPipeError, ConnectionResetError) as e:227logger.debug(f"{log_exception_prefix}abnormal connection close: {str(e)}")228except Exception as e:229logger.exception(f"{log_exception_prefix}exception: {str(e)} (running {self.serv.is_running()})")230if self.serv.is_running():231self.serv.queue.put(e)232finally:233if not self.serv.keep_alive:234self.conn.close()235else:236logger.debug("Keeping client connection alive")237s0 = self.wakeup_socket_pair[0]238s1 = self.wakeup_socket_pair[1]239self.wakeup_socket_pair = None240try:241s0.close()242s1.close()243except OSError:244pass245self.serv.remove_handler(self.handler_index)246self.handler_index = None248def wakeup(self):249# Wake up the blocking forwarding select by writing to the wake-up socket250try:
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.