What's the easiest way to find an unused local port?
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.
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
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 comm
s 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 sort
ing without duplicates -u
.
Now we have a sorted list of open ports, that we can shuf
fle 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
inss
to get free UDP ports instead. - replace
shuf
withsort -n
if you prefer to get available ports numerically sorted instead of at random