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) 2014-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"""Linux, macOS, BSD and illumos network utilities.7Roughly based on https://web.archive.org/web/20190424172231/http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-lines-of-code/ by Ricardo Pascal8"""10import http.client11import sys12import socket13import struct14import array15import os17# Easily unreachable address. Attempts to connect to it will stay within the machine.18# Used to avoid non-loopback traffic or DNS queries.19UNREACHABLE_PROXY_ARG = '-proxy=127.0.0.1:1'21# STATE_ESTABLISHED = '01'22# STATE_SYN_SENT = '02'23# STATE_SYN_RECV = '03'24# STATE_FIN_WAIT1 = '04'25# STATE_FIN_WAIT2 = '05'26# STATE_TIME_WAIT = '06'27# STATE_CLOSE = '07'28# STATE_CLOSE_WAIT = '08'29# STATE_LAST_ACK = '09'30STATE_LISTEN = '0A'31# STATE_CLOSING = '0B'33# Address manager size constants as defined in addrman_impl.h34ADDRMAN_NEW_BUCKET_COUNT = 1 << 1035ADDRMAN_TRIED_BUCKET_COUNT = 1 << 836ADDRMAN_BUCKET_SIZE = 1 << 638# When a test expects a server disconnection, any of these errors are39# acceptable. The specific event is determined by race condition and platform OS.40NETWORK_ERRORS = (41BrokenPipeError, # write to a closed socket/pipe42ConnectionResetError, # connection forcibly closed by peer43ConnectionAbortedError, # connection aborted locally or by network stack44http.client.ResponseNotReady, # server response not ready or connection out of sync45)47def get_socket_inodes(pid):48'''49Get list of socket inodes for process pid.50'''51base = '/proc/%i/fd' % pid52inodes = []53for item in os.listdir(base):54try:55target = os.readlink(os.path.join(base, item))56if target.startswith('socket:'):57inodes.append(int(target[8:-1]))58except FileNotFoundError:59pass60return inodes62def _remove_empty(array):63return [x for x in array if x !='']65def _convert_ip_port(array):66host,port = array.split(':')67# convert host from mangled-per-four-bytes form as used by kernel68host = bytes.fromhex(host)69host_out = ''70for x in range(0, len(host) // 4):71(val,) = struct.unpack('=I', host[x*4:(x+1)*4])72host_out += '%08x' % val74return host_out,int(port,16)76def netstat(typ='tcp'):77'''78Function to return a list with status of tcp connections at linux systems79To get pid of all network process running on system, you must run this script80as superuser81'''82with open('/proc/net/'+typ,'r') as f:83content = f.readlines()84content.pop(0)85result = []86for line in content:87line_array = _remove_empty(line.split(' ')) # Split lines and remove empty spaces.88tcp_id = line_array[0]89l_addr = _convert_ip_port(line_array[1])90r_addr = _convert_ip_port(line_array[2])91state = line_array[3]92inode = int(line_array[9]) # Need the inode to match with process pid.93nline = [tcp_id, l_addr, r_addr, state, inode]94result.append(nline)95return result97def get_bind_addrs(pid):98'''99Get bind addresses as (host,port) tuples for process pid.100'''101if sys.platform == 'linux':102inodes = get_socket_inodes(pid)103bind_addrs = []104for conn in netstat('tcp') + netstat('tcp6'):105if conn[3] == STATE_LISTEN and conn[4] in inodes:106bind_addrs.append(conn[1])107return bind_addrs108# OpenBSD is not included, as it does not ship the lsof utility.109elif sys.platform.startswith(("darwin", "freebsd", "netbsd", "sunos5")):110import re111import subprocess112output = subprocess.check_output(["lsof",113*(["-Di"] if sys.platform.startswith(("freebsd", "netbsd", "sunos5")) else []), # Ignore device cache to avoid stderr warnings.114*(["-w"] if sys.platform.startswith("netbsd") else []), # Ignore point release mismatch warnings.115"-nP", # Keep hosts and ports numeric.116"-a", # Require all filters to match.117"-p", str(pid), # Limit results to the target pid.118"-iTCP", # Only inspect TCP sockets.119"-sTCP:LISTEN", # Only keep listening sockets.120"-Ftn", # Emit machine-readable type and name fields.121], text=True)122return [123(addr_to_hex(("::" if sock_type == "IPv6" else "0.0.0.0") if host == "*" else host.strip("[]")), int(port))124for sock_type, host, port in re.findall(r"t(IPv[46])\nn(\*|\[.+?]|[^:]+):(\d+)", output)125]126else:127raise NotImplementedError(f"get_bind_addrs is not supported on {sys.platform}")129def all_interfaces():130'''131Return all IPv4 interfaces that are up.132'''133if sys.platform == 'linux':134import fcntl # Linux only, so only import when required136is_64bits = sys.maxsize > 2**32137struct_size = 40 if is_64bits else 32138s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)139max_possible = 8 # initial value140while True:141bytes = max_possible * struct_size142names = array.array('B', b'\0' * bytes)143outbytes = struct.unpack('iL', fcntl.ioctl(144s.fileno(),1450x8912, # SIOCGIFCONF146struct.pack('iL', bytes, names.buffer_info()[0])147))[0]148if outbytes == bytes:149max_possible *= 2150else:151break152namestr = names.tobytes()153return [(namestr[i:i+16].split(b'\0', 1)[0],154socket.inet_ntoa(namestr[i+20:i+24]))155for i in range(0, outbytes, struct_size)]156elif sys.platform.startswith(("darwin", "freebsd", "netbsd", "openbsd", "sunos5")):157import re158import subprocess159output = subprocess.check_output(["ifconfig", "-au"], text=True)160return [161(m["iface"].encode(), ip)162for m in re.finditer(r"(?m)^(?P<iface>\S+):(?P<block>[^\n]*(?:\n[ \t]+[^\n]*)*)", output)163for ip in re.findall(r"inet ([^\s/]+)", m["block"])164]165else:166raise NotImplementedError(f"all_interfaces is not supported on {sys.platform}")168def addr_to_hex(addr):169'''170Convert string IPv4 or IPv6 address to binary address as returned by171get_bind_addrs.172Very naive implementation that certainly doesn't work for all IPv6 variants.173'''174if '.' in addr: # IPv4175addr = [int(x) for x in addr.split('.')]176elif ':' in addr: # IPv6177sub = [[], []] # prefix, suffix178x = 0179addr = addr.split(':')180for i,comp in enumerate(addr):181if comp == '':182if i == 0 or i == (len(addr)-1): # skip empty component at beginning or end183continue184x += 1 # :: skips to suffix185assert x < 2186else: # two bytes per component187val = int(comp, 16)188sub[x].append(val >> 8)189sub[x].append(val & 0xff)190nullbytes = 16 - len(sub[0]) - len(sub[1])191assert (x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)192addr = sub[0] + ([0] * nullbytes) + sub[1]193else:194raise ValueError('Could not parse address %s' % addr)195return bytearray(addr).hex()197def test_ipv6_local():198'''199Check for (local) IPv6 support.200'''201# By using SOCK_DGRAM this will not actually make a connection, but it will202# fail if there is no route to IPv6 localhost.203have_ipv6 = True204try:205s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)206s.connect(('::1', 1))207except socket.error:208have_ipv6 = False209return have_ipv6211def test_unix_socket():212'''Return True if UNIX sockets are available on this platform.'''213try:214socket.AF_UNIX215except AttributeError:216return False217else:218return True220def format_addr_port(addr, port):221'''Return either "addr:port" or "[addr]:port" based on whether addr looks like an IPv6 address.'''222if ":" in addr:223return f"[{addr}]:{port}"224else:225return f"{addr}:{port}"227def format_sock(sock, *, local):228'''229Format either local or remote side of a socket to a human readable string, e.g.2301.2.3.4:8333 or231[11:22::33]:8333 or232/path/to/socket or233@abstract-socket234'''235try:236if local:237name = sock.getsockname()238else:239name = sock.getpeername()240except Exception:241return "n/a"243if sock.family == socket.AF_INET:244return f"{name[0]}:{name[1]}"246if sock.family == socket.AF_INET6:247return f"[{name[0]}]:{name[1]}"249if sock.family == socket.AF_UNIX:250if isinstance(name, bytes):
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.