1#!/usr/bin/python3 2# 3# Copyright 2007 Google Inc. All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17"""Pure python code for finding unused ports on a host. 18 19This module provides a pick_unused_port() function. 20It can also be called via the command line for use in shell scripts. 21When called from the command line, it takes one optional argument, which, 22if given, is sent to portserver instead of portpicker's PID. 23To reserve a port for the lifetime of a bash script, use $BASHPID as this 24argument. 25 26There is a race condition between picking a port and your application code 27binding to it. The use of a port server to prevent that is recommended on 28loaded test hosts running many tests at a time. 29 30If your code can accept a bound socket as input rather than being handed a 31port number consider using socket.bind(('localhost', 0)) to bind to an 32available port without a race condition rather than using this library. 33 34Typical usage: 35 test_port = portpicker.pick_unused_port() 36""" 37 38# pylint: disable=consider-using-f-string 39# Some people still use this on old Pythons despite our test matrix and 40# supported versions. Be kind for now, until it gets in our way. 41from __future__ import print_function 42 43import logging 44import os 45import random 46import socket 47import sys 48import time 49 50_winapi = None # pylint: disable=invalid-name 51if sys.platform == 'win32': 52 try: 53 import _winapi 54 except ImportError: 55 _winapi = None 56 57# The legacy Bind, IsPortFree, etc. names are not exported. 58__all__ = ('bind', 'is_port_free', 'pick_unused_port', 'return_port', 59 'add_reserved_port', 'get_port_from_port_server') 60 61_PROTOS = [(socket.SOCK_STREAM, socket.IPPROTO_TCP), 62 (socket.SOCK_DGRAM, socket.IPPROTO_UDP)] 63 64 65# Ports that are currently available to be given out. 66_free_ports = set() 67 68# Ports that are reserved or from the portserver that may be returned. 69_owned_ports = set() 70 71# Ports that we chose randomly that may be returned. 72_random_ports = set() 73 74 75class NoFreePortFoundError(Exception): 76 """Exception indicating that no free port could be found.""" 77 78 79def add_reserved_port(port): 80 """Add a port that was acquired by means other than the port server.""" 81 _free_ports.add(port) 82 83 84def return_port(port): 85 """Return a port that is no longer being used so it can be reused.""" 86 if port in _random_ports: 87 _random_ports.remove(port) 88 elif port in _owned_ports: 89 _owned_ports.remove(port) 90 _free_ports.add(port) 91 elif port in _free_ports: 92 logging.info("Returning a port that was already returned: %s", port) 93 else: 94 logging.info("Returning a port that wasn't given by portpicker: %s", 95 port) 96 97 98def bind(port, socket_type, socket_proto): 99 """Try to bind to a socket of the specified type, protocol, and port. 100 101 This is primarily a helper function for PickUnusedPort, used to see 102 if a particular port number is available. 103 104 For the port to be considered available, the kernel must support at least 105 one of (IPv6, IPv4), and the port must be available on each supported 106 family. 107 108 Args: 109 port: The port number to bind to, or 0 to have the OS pick a free port. 110 socket_type: The type of the socket (ex: socket.SOCK_STREAM). 111 socket_proto: The protocol of the socket (ex: socket.IPPROTO_TCP). 112 113 Returns: 114 The port number on success or None on failure. 115 """ 116 return _bind(port, socket_type, socket_proto) 117 118 119def _bind(port, socket_type, socket_proto, return_socket=None, 120 return_family=socket.AF_INET6): 121 """Internal implementation of bind. 122 123 Args: 124 port, socket_type, socket_proto: see bind(). 125 return_socket: If supplied, a list that we will append an open bound 126 reuseaddr socket on the port in question to. 127 return_family: The socket family to return in return_socket. 128 129 Returns: 130 The port number on success or None on failure. 131 """ 132 # Our return family must come last when returning a bound socket 133 # as we cannot keep it bound while testing a bind on the other 134 # family with many network stack configurations. 135 if return_socket is None or return_family == socket.AF_INET: 136 socket_families = (socket.AF_INET6, socket.AF_INET) 137 elif return_family == socket.AF_INET6: 138 socket_families = (socket.AF_INET, socket.AF_INET6) 139 else: 140 raise ValueError('unknown return_family %s' % return_family) 141 got_socket = False 142 for family in socket_families: 143 try: 144 sock = socket.socket(family, socket_type, socket_proto) 145 got_socket = True 146 except socket.error: 147 continue 148 try: 149 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 150 sock.bind(('', port)) 151 if socket_type == socket.SOCK_STREAM: 152 sock.listen(1) 153 port = sock.getsockname()[1] 154 except socket.error: 155 return None 156 finally: 157 if return_socket is None or family != return_family: 158 try: 159 # Adding this resolved 1 in ~500 flakiness that we were 160 # seeing from an integration test framework managing a set 161 # of ports with is_port_free(). close() doesn't move the 162 # TCP state machine along quickly. 163 sock.shutdown(socket.SHUT_RDWR) 164 except OSError: 165 pass 166 sock.close() 167 if return_socket is not None and family == return_family: 168 return_socket.append(sock) 169 break # Final iteration due to pre-loop logic; don't close. 170 return port if got_socket else None 171 172 173def is_port_free(port): 174 """Check if specified port is free. 175 176 Args: 177 port: integer, port to check 178 179 Returns: 180 bool, whether port is free to use for both TCP and UDP. 181 """ 182 return _is_port_free(port) 183 184 185def _is_port_free(port, return_sockets=None): 186 """Internal implementation of is_port_free. 187 188 Args: 189 port: integer, port to check 190 return_sockets: If supplied, a list that we will append open bound 191 sockets on the port in question to rather than closing them. 192 193 Returns: 194 bool, whether port is free to use for both TCP and UDP. 195 """ 196 return (_bind(port, *_PROTOS[0], return_socket=return_sockets) and 197 _bind(port, *_PROTOS[1], return_socket=return_sockets)) 198 199 200def pick_unused_port(pid=None, portserver_address=None): 201 """Picks an unused port and reserves it for use by a given process id. 202 203 Args: 204 pid: PID to tell the portserver to associate the reservation with. If 205 None, the current process's PID is used. 206 portserver_address: The address (path) of a unix domain socket 207 with which to connect to a portserver, a leading '@' 208 character indicates an address in the "abstract namespace". OR 209 On systems without socket.AF_UNIX, this is an AF_INET address. 210 If None, or no port is returned by the portserver at the provided 211 address, the environment will be checked for a PORTSERVER_ADDRESS 212 variable. If that is not set, no port server will be used. 213 214 If no portserver is used, no pid based reservation is managed by any 215 central authority. Race conditions and duplicate assignments may occur. 216 217 Returns: 218 A port number that is unused on both TCP and UDP. 219 220 Raises: 221 NoFreePortFoundError: No free port could be found. 222 """ 223 return _pick_unused_port(pid, portserver_address) 224 225 226def _pick_unused_port(pid=None, portserver_address=None, 227 noserver_bind_timeout=0): 228 """Internal implementation of pick_unused_port. 229 230 Args: 231 pid, portserver_address: See pick_unused_port(). 232 noserver_bind_timeout: If no portserver was used, this is the number of 233 seconds we will attempt to keep a child process around with the ports 234 returned open and bound SO_REUSEADDR style to help avoid race condition 235 port reuse. A non-zero value attempts os.fork(). Do not use it in a 236 multithreaded process. 237 """ 238 try: # Instead of `if _free_ports:` to handle the race condition. 239 port = _free_ports.pop() 240 except KeyError: 241 pass 242 else: 243 _owned_ports.add(port) 244 return port 245 # Provide access to the portserver on an opt-in basis. 246 if portserver_address: 247 port = get_port_from_port_server(portserver_address, pid=pid) 248 if port: 249 return port 250 if 'PORTSERVER_ADDRESS' in os.environ: 251 port = get_port_from_port_server(os.environ['PORTSERVER_ADDRESS'], 252 pid=pid) 253 if port: 254 return port 255 return _pick_unused_port_without_server(bind_timeout=noserver_bind_timeout) 256 257 258def _spawn_bound_port_holding_daemon(port, bound_sockets, timeout): 259 """If possible, fork()s a daemon process to hold bound_sockets open. 260 261 Emits a warning to stderr if it cannot. 262 263 Args: 264 port: The port number the sockets are bound to (informational). 265 bound_sockets: The list of bound sockets our child process will hold 266 open. If the list is empty, no action is taken. 267 timeout: A positive number of seconds the child should sleep for before 268 closing the sockets and exiting. 269 """ 270 if bound_sockets and timeout > 0: 271 try: 272 fork_pid = os.fork() # This concept only works on POSIX. 273 except Exception as err: # pylint: disable=broad-except 274 print('WARNING: Cannot timeout unbinding close of port', port, 275 ' closing on exit. -', err, file=sys.stderr) 276 else: 277 if fork_pid == 0: 278 # This child process inherits and holds bound_sockets open 279 # for bind_timeout seconds. 280 try: 281 # Close the stdio fds as may be connected to 282 # a pipe that will cause a grandparent process 283 # to wait on before returning. (cl/427587550) 284 os.close(sys.stdin.fileno()) 285 os.close(sys.stdout.fileno()) 286 os.close(sys.stderr.fileno()) 287 time.sleep(timeout) 288 for held_socket in bound_sockets: 289 held_socket.close() 290 finally: 291 os._exit(0) 292 293 294def _pick_unused_port_without_server(bind_timeout=0): 295 """Pick an available network port without the help of a port server. 296 297 This code ensures that the port is available on both TCP and UDP. 298 299 This function is an implementation detail of PickUnusedPort(), and 300 should not be called by code outside of this module. 301 302 Args: 303 bind_timeout: number of seconds to attempt to keep a child process 304 process around bound SO_REUSEADDR style to the port. If we cannot 305 do that we emit a warning to stderr. 306 307 Returns: 308 A port number that is unused on both TCP and UDP. 309 310 Raises: 311 NoFreePortFoundError: No free port could be found. 312 """ 313 # Next, try a few times to get an OS-assigned port. 314 # Ambrose discovered that on the 2.6 kernel, calling Bind() on UDP socket 315 # returns the same port over and over. So always try TCP first. 316 port = None 317 bound_sockets = [] if bind_timeout > 0 else None 318 for _ in range(10): 319 # Ask the OS for an unused port. 320 port = _bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP, bound_sockets) 321 # Check if this port is unused on the other protocol. 322 if (port and port not in _random_ports and 323 _bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP, bound_sockets)): 324 _random_ports.add(port) 325 _spawn_bound_port_holding_daemon(port, bound_sockets, bind_timeout) 326 return port 327 if bound_sockets: 328 for held_socket in bound_sockets: 329 held_socket.close() 330 del bound_sockets[:] 331 332 # Try random ports as a last resort. 333 rng = random.Random() 334 for _ in range(10): 335 port = int(rng.randrange(15000, 25000)) 336 if port not in _random_ports: 337 if _is_port_free(port, bound_sockets): 338 _random_ports.add(port) 339 _spawn_bound_port_holding_daemon( 340 port, bound_sockets, bind_timeout) 341 return port 342 if bound_sockets: 343 for held_socket in bound_sockets: 344 held_socket.close() 345 del bound_sockets[:] 346 347 # Give up. 348 raise NoFreePortFoundError() 349 350 351def _posix_get_port_from_port_server(portserver_address, pid): 352 # An AF_UNIX address may start with a zero byte, in which case it is in the 353 # "abstract namespace", and doesn't have any filesystem representation. 354 # See 'man 7 unix' for details. 355 # The convention is to write '@' in the address to represent this zero byte. 356 if portserver_address[0] == '@': 357 portserver_address = '\0' + portserver_address[1:] 358 359 try: 360 # Create socket. 361 if hasattr(socket, 'AF_UNIX'): 362 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 363 else: 364 # fallback to AF_INET if this is not unix 365 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 366 try: 367 # Connect to portserver. 368 sock.connect(portserver_address) 369 370 # Write request. 371 sock.sendall(('%d\n' % pid).encode('ascii')) 372 373 # Read response. 374 # 1K should be ample buffer space. 375 return sock.recv(1024) 376 finally: 377 sock.close() 378 except socket.error as error: 379 print('Socket error when connecting to portserver:', error, 380 file=sys.stderr) 381 return None 382 383 384def _windows_get_port_from_port_server(portserver_address, pid): 385 if portserver_address[0] == '@': 386 portserver_address = '\\\\.\\pipe\\' + portserver_address[1:] 387 388 try: 389 handle = _winapi.CreateFile( 390 portserver_address, 391 _winapi.GENERIC_READ | _winapi.GENERIC_WRITE, 392 0, 393 0, 394 _winapi.OPEN_EXISTING, 395 0, 396 0) 397 398 _winapi.WriteFile(handle, ('%d\n' % pid).encode('ascii')) 399 data, _ = _winapi.ReadFile(handle, 6, 0) 400 return data 401 except FileNotFoundError as error: 402 print('File error when connecting to portserver:', error, 403 file=sys.stderr) 404 return None 405 406 407def get_port_from_port_server(portserver_address, pid=None): 408 """Request a free a port from a system-wide portserver. 409 410 This follows a very simple portserver protocol: 411 The request consists of our pid (in ASCII) followed by a newline. 412 The response is a port number and a newline, 0 on failure. 413 414 This function is an implementation detail of pick_unused_port(). 415 It should not normally be called by code outside of this module. 416 417 Args: 418 portserver_address: The address (path) of a unix domain socket 419 with which to connect to the portserver. A leading '@' 420 character indicates an address in the "abstract namespace." 421 On systems without socket.AF_UNIX, this is an AF_INET address. 422 pid: The PID to tell the portserver to associate the reservation with. 423 If None, the current process's PID is used. 424 425 Returns: 426 The port number on success or None on failure. 427 """ 428 if not portserver_address: 429 return None 430 431 if pid is None: 432 pid = os.getpid() 433 434 if _winapi: 435 buf = _windows_get_port_from_port_server(portserver_address, pid) 436 else: 437 buf = _posix_get_port_from_port_server(portserver_address, pid) 438 439 if buf is None: 440 return None 441 442 try: 443 port = int(buf.split(b'\n')[0]) 444 except ValueError: 445 print('Portserver failed to find a port.', file=sys.stderr) 446 return None 447 _owned_ports.add(port) 448 return port 449 450 451# Legacy APIs. 452# pylint: disable=invalid-name 453Bind = bind 454GetPortFromPortServer = get_port_from_port_server 455IsPortFree = is_port_free 456PickUnusedPort = pick_unused_port 457# pylint: enable=invalid-name 458 459 460def main(argv): 461 """If passed an arg, treat it as a PID, otherwise we use getppid(). 462 463 A second optional argument can be a bind timeout in seconds that will be 464 used ONLY if no portserver is found. We attempt to leave a process around 465 holding the port open and bound with SO_REUSEADDR set for timeout seconds. 466 If the timeout bind was not possible, a warning is emitted to stderr. 467 468 #!/bin/bash 469 port="$(python -m portpicker $$ 1.23)" 470 test_my_server "$port" 471 472 This will pick a port for your script's PID and assign it to $port, if no 473 portserver was used, it attempts to keep a socket bound to $port for 1.23 474 seconds after the portpicker process has exited. This is a convenient hack 475 to attempt to prevent port reallocation during scripts outside of 476 portserver managed environments. 477 478 Older versions of the portpicker CLI ignore everything beyond the first arg. 479 Older versions also used getpid() instead of getppid(), so script users are 480 strongly encouraged to be explicit and pass $$ or your languages equivalent 481 to associate the port with the PID of the controlling process. 482 """ 483 # Our command line is trivial so I avoid an argparse import. If we ever 484 # grow more than 1-2 args, switch to a using argparse. 485 if '-h' in argv or '--help' in argv: 486 print(argv[0], 'usage:\n') 487 import inspect 488 print(inspect.getdoc(main)) 489 sys.exit(1) 490 pid=int(argv[1]) if len(argv) > 1 else os.getppid() 491 bind_timeout=float(argv[2]) if len(argv) > 2 else 0 492 port = _pick_unused_port(pid=pid, noserver_bind_timeout=bind_timeout) 493 if not port: 494 sys.exit(1) 495 print(port) 496 497 498if __name__ == '__main__': 499 main(sys.argv) 500