What's the easiest way to find an unused local port?

What’s the easiest way to find an unused local port?

Currently I’m using something similar to this:

port=$RANDOM
quit=0

while [ "$quit" -ne 1 ]; do
  netstat -a | grep $port >> /dev/null
  if [ $? -gt 0 ]; then
    quit=1
  else
    port=`expr $port + 1`
  fi
done

It feels awfully roundabout, so I’m wondering if there’s a more simple path such as a builtin that I’ve missed.

Asked By: mybuddymichael

||

If your application supports it, you can try passing port 0 to the application. If your application passes this to the kernel, the port will be dynamically allocated at request time, and is guaranteed not to be in use (allocation will fail if all ports are already in use).

Otherwise, you can do this manually. The script in your answer has a race condition, the only way to avoid it is to atomically check if it is open by trying to open it. If the port is in use, the program should quit with a failure to open the port.

For example, say you’re trying to listen with GNU netcat.

#!/bin/bash
read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
while :; do
    for (( port = lower_port ; port <= upper_port ; port++ )); do
        nc -l -p "$port" 2>/dev/null && break 2
    done
done
Answered By: Chris Down

This is part of a function I have in my .bashrc, which dynamically creates SSH tunnels and tries to use any port in a range:

   lps=( 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 )
   lp=null

   # find a free listening port
   for port in ${lps[@]}; do
      lsof -i -n -P |grep LISTEN |grep -q ":${port}"
      [ $? -eq 1 ] && { lp=$port; break; }
   done
   [ "$lp" = "null" ] && { echo "no free local ports available"; return 2; }
   return $port

YMMV

Answered By: mills013

On Linux, you could do something like:

ss -tln | 
  awk 'NR > 1{gsub(/.*:/,"",$4); print $4}' |
  sort -un |
  awk -v n=1080 '$0 < n {next}; $0 == n {n++; next}; {exit}; END {print n}'

To find the first free port above 1080. Note that ssh -D would bind on the loopback interface, so in theory you could reuse port 1080 if a socket has it bound on another address. Another way would be to actually try and bind it:

perl -MSocket -le 'socket S, PF_INET, SOCK_STREAM,getprotobyname("tcp");
  $port = 1080;
  ++$port until bind S, sockaddr_in($port,inet_aton("127.1"));
  print $port'
Answered By: Stéphane Chazelas

My solution is to bind to port 0, which asks the kernel to allocate a port from it’s ip_local_port_range. Then, close the socket and use that port number in your configuration.

This works because the kernel doesn’t seem to reuse port numbers until it absolutely has to. Subsequent binds to port 0 will allocate a different port number. Python code:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0))
addr = s.getsockname()
print(addr[1])
s.close()

This gives just a number of a port, eg. 60123.

Run this program 10 000 times (you should run these concurrently), and you’ll get 10 000 different port numbers. Therefore, I think it’s pretty safe to use the ports.

Answered By: Mark Theunissen
#!/bin/bash
read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while :
do
        PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
        ss -lpna | grep -q ":$PORT " || break
done
echo $PORT

"ss -lpn" – will show only ports with established connectios. We should use

"ss -lpna" to consider listening ports too

Credits to Chris Down

Answered By: Stefan Leisten

Apparently TCP connections can be used as file descriptors on linux from with in bash/zsh. The following function uses that technique and should be faster than invoking netcat/telnet.

function EPHEMERAL_PORT() {
    LOW_BOUND=49152
    RANGE=16384
    while true; do
        CANDIDATE=$[$LOW_BOUND + ($RANDOM % $RANGE)]
        (echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo $CANDIDATE
            break
        fi
    done
}

Usage instructions: Bind the output to a variable and use in scripts. Tested on Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Answered By: Sandeep

Here’s a cross-platform, efficient “oneliner” that slurps up all in-use ports and gives you the first available one from 3000 onwards:

netstat -aln | awk '
  $6 == "LISTEN" {
    if ($4 ~ "[.:][0-9]+$") {
      split($4, a, /[:.]/);
      port = a[length(a)];
      p[port] = 1
    }
  }
  END {
    for (i = 3000; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i
  }
'

You can simply join all lines to have it on one line. If you want to get the first available from a different port number, change the assignment to i in the for loop.

It works on both Mac and Linux, which is why the [:.] regex is needed.

Answered By: w00t

Yet another run at this old hobby horse:

function random_free_tcp_port {
  local ports="${1:-1}" interim="${2:-2048}" spacing=32
  local free_ports=( )
  local taken_ports=( $( netstat -aln | egrep ^tcp | fgrep LISTEN |
                         awk '{print $4}' | egrep -o '[0-9]+$' |
                         sort -n | uniq ) )
  interim=$(( interim + (RANDOM % spacing) ))

  for taken in "${taken_ports[@]}" 65535
  do
    while [[ $interim -lt $taken && ${#free_ports[@]} -lt $ports ]]
    do
      free_ports+=( $interim )
      interim=$(( interim + spacing + (RANDOM % spacing) ))
    done
    interim=$(( interim > taken + spacing
                ? interim
                : taken + spacing + (RANDOM % spacing) ))
  done

  [[ ${#free_ports[@]} -ge $ports ]] || return 2

  printf '%dn' "${free_ports[@]}"
}

This code makes purely portable use of netstat, egrep, awk, &al. Note that only one call is issued to external commands, to get a list of ports in use at the beginning. One can request one or more free ports:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

and start at an arbitrary port:

:;  random_free_tcp_port 2 10240
10245
10293
Answered By: solidsnack

One-liner

I’ve put together a nice one-liner that quickly serves the purpose, allowing to grab an arbitrary number of ports in an arbitrary range (here it’s divided in 4 lines for readability):

comm -23 
<(seq "$FROM" "$TO" | sort) 
<(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) 
| shuf | head -n "$HOWMANY"

Line by line

comm is a utility that compares lines in two files that must appear sorted alphabetically. It outputs three columns: lines that appear only in the first file, lines that only appear in the second one and common lines. By specifying -23 we suppress the latter columns and only keep the first one. We can use this to obtain the difference of two sets, expressed as a sequence of text lines. I learned about comm here.

The first file is the range of ports that we can select from. seq produces a sorted sequence of numbers from $FROM to $TO. The result is sorted alphabetically (instead of numerically, in order to comply with comms requirement) and piped to comm as the first file using process substitution.

The second file is the sorted list of ports, that we obtain by calling the ss command (with -t meaning TCP ports, -a meaning all – established and listening – and -n numeric – don’t try to resolve, say, 22 to ssh). We then pick only the fourth column with awk, which contains the local address and port. We use cut to split address and port with the : delimiter and keep only the latter (-f2). We then comply with comm‘s requirement by sorting without duplicates -u.

Now we have a sorted list of open ports, that we can shuffle to then grab the first "$HOWMANY" ones with head -n.

Example

Grab the three random open ports in the private range (49152-65535)

comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 3

could return for example

54930
57937
51399

Notes

  • switch -t with -u in ss to get free UDP ports instead.
  • replace shuf with sort -n if you prefer to get available ports numerically sorted instead of at random
Answered By: stefanobaghino

This is the version I use:

while
  port=$(shuf -n 1 -i 49152-65535)
  netstat -atun | grep -q "$port"
do
  continue
done

echo "$port"

The command shuf -n 1 -i 49152-65535 gives you a “random” port in the dynamic range. If it is used already, another port in that range is tried.

The command netstat -atun lists all (-a) TCP (-t) and UDP (-u) ports without wasting time to determine hostnames (-n).

Answered By: pfo
while port=$(shuf -n1 -i $(cat /proc/sys/net/ipv4/ip_local_port_range | tr '11' '-'))
netstat -atun | grep -q ":$ports" ; do
    continue
done
echo $port

My combination from other answers above. Get it:

With shuf -n1 we take one random number from the range (-i) in /proc/sys/net/ipv4/ip_local_port_range. shuf need the syntax with dash, so we use tr to change the tab in a dash.

Next wie use netstat to show us all (-a) tcp and udp (-u -t) connections in numbers(-n), if we find our random port $port in this (start with a : and end with w whitespace (s) then we need a other Port and so continue. Else (grep -q has a returncode > 0 we left the while loop and $port is set.

Answered By: Winfried Münch

If you have python lying around, I’d do this:

port="$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1])')";
echo "Unused Port: $port"
Answered By: MatrixManAtYrService

My take on it… the function tries to find n consecutive free ports:

#!/bin/bash

RANDOM=$$

# Based on 
# https://unix.stackexchange.com/a/55918/41065
# https://unix.stackexchange.com/a/248319/41065
find_n_ports () {
    local n=$1
    RANDOM=$$
    read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
    local tries=10
    while [ $((tries--)) -gt 0 ]; do
        # Sleep for 100 to 499 ms
        sleep "0.$((100+$RANDOM%399))"
        local start="`shuf -i $lower_port-$(($upper_port-$n-1)) -n 1`"
        local end=$(($start+$n-1))
        # create ss filter for $n consecutive ports starting with $start
        local filter=""
        local ports=$(seq $start $end)
        for p in $ports ; do
            filter="$filter or sport = $p"
        done
        # if none of the ports is in use return them
        if [ "$(ss -tHn "${filter# or}" | wc -l)" = "0" ] ; then
            echo "$ports"
            return 0
        fi
    done
    echo "Could not find $n consecutive ports">&2
    return 1
}

ports=$(find_n_ports 3)
[ $? -ne 0 ] && exit 1
exit 0
Answered By: stefanct

Yet another answer for macOS, based on this and this answer:

lower_port=`sysctl -n net.inet.ip.portrange.first`
upper_port=`sysctl -n net.inet.ip.portrange.last`

comm -23 
<(seq "$lower_port" "$upper_port" | sort) 
<(sudo lsof -iTCP -n -P -sTCP:LISTEN -t | sort -u) 
| gshuf | head -n 1

You can change the 1 in head -n 1 to another number (or a script argument variable) if you want multiple ports.

shuf is available as gshuf via the coreutils Homebrew package.

The port range code is from this answer on this Stack Overflow question:

Answered By: Kenny Evitt

This script works for me on macOS

#!/usr/bin/env bash
set -u                    # exit on undefined variable
bash -c 'set -o pipefail' # return code of first cmd to fail in a pipeline

# Print random open port. Inspired by
# https://superuser.com/a/1041677/537059

CHECK="do while"

while [[ ! -z $CHECK ]]; do
  PORT=$(((RANDOM % 60000) + 1025))
  # Match end of word boundary to avoid false positives,
  # e.g. 2000 matching 20000
  # https://stackoverflow.com/a/34074458/639133
  CHECK=$(netstat -an -ptcp | grep LISTEN | grep -r "${PORT}b")
done

echo ${PORT}
Answered By: mozey

Another one, albeit late to the party. This one-liner even works with busybox:

for p in $(seq 3000 4000); do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1

Change seq 3000 4000 for shuf -i 3000-4000 to get a random port.

Increase the number after head -n to return more than one free port.

This does require ss to be installed. If it’s not available, it’s trivial to change ss -tlnH to netstat -tln | tail -n +3 to use busybox’s netstat instead.

Answered By: garethTheRed

I liked w00t’s answer, but I didn’t want to have the same free port chosen upon each invocation. Adding an incremental update, this will select a free port at random between 3,000 to 13,000 (but if all of those are taken, it will incrementally search from that randomly selected port):

netstat -aln | awk '
  $6 == "LISTEN" {
    if ($4 ~ "[.:][0-9]+$") {
      split($4, a, /[:.]/);
      port = a[length(a)];
      p[port] = 1
    }
  }
  END {
    srand();
    start = int(3000 + rand()*10000);
    for (i = start; i < 65000 && p[i]; i++){};
    if (i == 65000) {exit 1};
    print i
  }
'
Answered By: firebush

Yet one more answer. It is very similar to several of the provided ones, but some of these don’t actually work for me. Some don’t tell me which free port I could use. Some give me a port which is not really free. And others use shuffle which I don’t need.

read first last < /proc/sys/net/ipv4/ip_local_port_range
# or define your own range, like:
# first=9000; last=10000

for port in $(seq $first $last); do
  ss -atun | grep -q ":$port " || break
done

echo $port

Or as a long one-liner:

read first last < /proc/sys/net/ipv4/ip_local_port_range; for port in $(seq $first $last); do ss -atun | grep -q ":$port " || break; done; echo $port
Answered By: mivk

My take, similar to multiple ideas here. Randomly picks form a range and checks with nc

# Tries to find a free port randomly from a given range
random_free_port() {
  local max_attempts=5
  local low_range=8432
  local range=200
  for i in $(seq "${max_attempts}"); do
    # Pick a random port between 8432-8632
    local port="$(($low_range + $RANDOM % $range))"
    # Check if the port is not open
    if ! nc -z -w 0 localhost 8433; then
      echo "Found free port $port" 1>&2
      echo "${port}"
      return
    fi
  done
  echo "Unable to find a free port after 5 attempts" 1>&2
  return 1
}
Answered By: Keymon
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int
main (int argc, char** argv)
{
  int sock;
  struct sockaddr_in name;
  char *addr;

  if(argc<2) {
     addr="0.0.0.0";
  } else {
     addr=argv[1];
  }
  /* Create the socket. */
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }

  /* Give the socket a name. */
  name.sin_family = AF_INET;
  name.sin_port = htons (0);
  name.sin_addr.s_addr = inet_addr(addr);
  if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
    {
      perror ("bind");
      exit (EXIT_FAILURE);
    }

  int len = sizeof(name);
  int sn = getsockname(sock, (struct sockaddr *)&name, &len);
  printf("Port is %dn",ntohs(name.sin_port));
  return sock;
}

Compile:
gcc getport.c -o getport

Usage: ./getport [address]

Ouput:
./getport
Port is 31725
./getport 127.0.0.1
Port is 32064
./getport 1.2.3.4
bind: Cannot assign requested address
Answered By: Zibri