Вопрос или проблема
Какой самый простой способ найти неиспользуемый локальный порт?
В данный момент я использую что-то подобное:
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
Это кажется довольно запутанным, так что я думаю, есть ли более простой способ, например, встроенная функция, которую я пропустил.
Мое решение – привязаться к порту 0, что означает запрос к ядру на выделение порта из диапазона ip_local_port_range. Затем закройте сокет и используйте этот номер порта в вашей конфигурации.
Это работает, потому что ядро, похоже, не переиспользует номера портов, пока в этом нет абсолютной необходимости. Последующие привязки к порту 0 будут выделять другой номер порта. Код на Python:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0))
addr = s.getsockname()
print(addr[1])
s.close()
Это возвращает просто номер порта, например, 60123
.
Запустите эту программу 10000 раз (вы должны запускать их одновременно), и вы получите 10000 различных номеров портов. Поэтому, я думаю, довольно безопасно использовать эти порты.
Если ваше приложение поддерживает это, вы можете попробовать передать порт 0 приложению. Если ваше приложение передаст это ядру, порт будет динамически выделен во время запроса и гарантированно не будет использоваться (выделение не удастся, если все порты уже используются).
В противном случае вы можете сделать это вручную. Скрипт в вашем ответе имеет состоявшуюся гонку, и единственный способ избежать этого – атомарно проверить, открыт ли он, попытавшись открыть его. Если порт используется, программа должна завершиться с ошибкой открытия порта.
Например, скажем, вы пытаетесь слушать с помощью 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
Однострочник
Я составил хороший однострочник, который быстро выполняет задачу, позволяя получить произвольное количество портов в произвольном диапазоне (здесь он разбит на 4 строки для удобства чтения):
comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) \
| shuf | head -n "$HOWMANY"
Построчно
comm
– это утилита, которая сравнивает строки в двух файлах, которые должны быть отсортированы в алфавитном порядке. Она выводит три колонки: строки, которые появляются только в первом файле, строки, которые появляются только во втором и общие строки. Указывая -23
, мы подавляем последние колонки и сохраняем только первую. Мы можем использовать это для получения разности двух множеств, выраженной как последовательность текстовых строк. Я узнал о comm
здесь.
Первый файл – это диапазон портов, из которых мы можем выбирать. seq
генерирует отсортированную последовательность чисел от $FROM
до $TO
. Результат сортируется в алфавитном порядке (вместо числового, чтобы соответствовать требованиям comm
) и передается comm
как первый файл с помощью подстановки процесса.
Второй файл – это отсортированный список портов, который мы получаем, вызывая команду ss
(с -t
, означающим TCP порты, -a
, означающим все – установленные и слушающие – и -n
, числовой – не пытаться разрешить, например, 22
в ssh
). Мы затем берем только четвертую колонку с awk
, которая содержит локальный адрес и порт. Мы используем cut
, чтобы разделить адрес и порт с помощью разделителя :
и оставить только последний (-f2
). Затем мы соответствуем требованию comm
, сортируя без дубликатов -u
.
Теперь у нас есть отсортированный список открытых портов, который мы можем shuf
сбросить, чтобы затем взять первые "$HOWMANY"
с помощью head -n
.
Пример
Получите три случайных открытых порта в частном диапазоне (49152-65535)
comm -23 <(seq 49152 65535 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 3
может вернуть, например
54930
57937
51399
Заметки
- замените
-t
на-u
вss
, чтобы получить свободные UDP порты. - замените
shuf
наsort -n
, если вы хотите получить доступные порты, отсортированные по числовому значению, вместо случайных.
Очевидно, что TCP соединения могут использоваться как дескрипторы файлов на Linux из Bash/Zsh. Следующая функция использует эту технику и должна работать быстрее, чем вызов netcat/telnet.
function EPHEMERAL_PORT() {
LOW_BOUND=49152
RANGE=16384
while true; do
CANDIDATE=$[$LOW_BOUND + ($RANDOM % $RANGE)]
(echo -n >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo $CANDIDATE
break
fi
done
}
Инструкции по использованию: Привяжите вывод к переменной и используйте в скриптах. Протестировано на Ubuntu 16.04
root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
#!/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” – покажет только порты с установленными соединениями. Мы должны использовать
“ss -lpna”, чтобы учитывать порты, которые слушают, тоже.
Автор: Chris Down
Вот кросс-платформенный, эффективный “однострочник”, который собирает все используемые порты и дает вам первый доступный с 3000 и выше:
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
}
'
Вы можете просто соединить все строки, чтобы получить это в одной строке. Если вы хотите получить первый доступный от другого номера порта, измените присвоение i
в цикле for
.
Это работает как на Mac, так и на Linux, поэтому необходима регулярка [:.]
.
Вот версия, которую я использую:
while
port=$(shuf -n 1 -i 49152-65535)
netstat -atun | grep -q "$port"
do
continue
done
echo "$port"
Команда shuf -n 1 -i 49152-65535
дает вам “случайный” порт в динамическом диапазоне. Если он уже используется, пробуется другой порт из этого диапазона.
Команда netstat -atun
перечисляет все (-a) TCP (-t) и UDP (-u) порты, не тратя время на определение имен хостов (-n).
На Linux вы могли бы сделать что-то вроде этого:
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}'
Чтобы найти первый свободный порт выше 1080. Обратите внимание, что ssh -D
будет связываться с интерфейсом петли, так что теоретически вы можете повторно использовать порт 1080, если сокет связан с другим адресом. Другой способ – действительно попытаться привязаться к нему:
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'
Если у вас есть Python, я бы сделал так:
port="$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1])')";
echo "Неиспользуемый порт: $port"
Еще один вариант, хотя и запоздалый. Этот однострочник даже работает с busybox
:
for p in $(seq 3000 4000); do ss -tlnH | tr -s ' ' | cut -d" " -sf4 | grep -q "${p}$" || echo "${p}"; done | head -n 1
Измените seq 3000 4000
на shuf -i 3000-4000
, чтобы получить случайный порт.
Увеличьте число после head -n
, чтобы вернуть более одного свободного порта.
Это требует, чтобы ss
был установлен. Если он недоступен, легко изменить ss -tlnH
на netstat -tln | tail -n +3
, чтобы использовать netstat от busybox.
Еще один подход к этой старой проблеме:
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 '%d\n' "${free_ports[@]}"
}
Этот код делает чисто переносимый способ использования netstat
, egrep
, awk
и т.д. Обратите внимание, что всего один вызов делается к внешним командам, чтобы в начале получить список используемых портов. Можно запросить один или несколько свободных портов:
:; random_free_tcp_port
2070
:; random_free_tcp_port 2
2073
2114
и начинать с произвольного порта:
:; random_free_tcp_port 2 10240
10245
10293
Это часть функции в моем .bashrc, которая динамически создает SSH туннели и пытается использовать любой порт из диапазона:
lps=( 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 )
lp=null
# найти свободный прослушивающий порт
for port in ${lps[@]}; do
lsof -i -n -P |grep LISTEN |grep -q ":${port}"
[ $? -eq 1 ] && { lp=$port; break; }
done
[ "$lp" = "null" ] && { echo "нет доступных локальных портов"; return 2; }
return $port
Ваши результаты могут различаться
while port=$(shuf -n1 -i $(cat /proc/sys/net/ipv4/ip_local_port_range | tr '\011' '-'))
netstat -atun | grep -q ":$port\s" ; do
continue
done
echo $port
Моя комбинация из других ответов выше. Получите это:
С помощью shuf -n1
мы берем одно случайное число из диапазона (-i) в /proc/sys/net/ipv4/ip_local_port_range. shuf
требует синтаксиса с дефисом, поэтому мы используем tr
, чтобы изменить табуляцию на дефис.
Далее мы используем netstat
, чтобы показать все (-a) tcp и udp (-u -t) соединения в числах (-n), если мы находим наш случайный порт $port в этом (начиная со знака : и заканчивая пробелом (\s), нам нужен другой порт и так продолжается. В противном случае (grep -q возвращает код > 0), мы выходим из цикла while, и $port установлен.
Моя версия… функция пытается найти n
последовательных свободных портов:
#!/bin/bash
RANDOM=$$
# Основано на
# 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
# Спать от 100 до 499 мс
sleep "0.$((100+$RANDOM%399))"
local start="`shuf -i $lower_port-$(($upper_port-$n-1)) -n 1`"
local end=$(($start+$n-1))
# создаем фильтр ss для $n последовательных портов, начиная с $start
local filter=""
local ports=$(seq $start $end)
for p in $ports ; do
filter="$filter or sport = $p"
done
# если ни один из портов не используется, вернуть их
if [ "$(ss -tHn "${filter# or}" | wc -l)" = "0" ] ; then
echo "$ports"
return 0
fi
done
echo "Не удалось найти $n последовательных портов">&2
return 1
}
ports=$(find_n_ports 3)
[ $? -ne 0 ] && exit 1
exit 0
Еще один ответ для macOS, основанный на этом и этом ответе:
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
Вы можете изменить 1
в head -n 1
на другое число (или переменную аргумента скрипта), если хотите получить несколько портов.
shuf
доступен как gshuf
через пакет coreutils
в Homebrew.
Код диапазона портов взят из этого ответа на этот вопрос на Stack Overflow:
Этот скрипт работает у меня на macOS
#!/usr/bin/env bash
set -u # выход при неопределенной переменной
bash -c 'set -o pipefail' # код возврата первой команды, которая завершится неудачей в конвейере
# Печатает случайный открытый порт. Вдохновлено
# https://superuser.com/a/1041677/537059
CHECK="do while"
while [[ ! -z $CHECK ]]; do
PORT=$(((RANDOM % 60000) + 1025))
# Совпадение с концом границы слова, чтобы избежать ложных срабатываний,
# например, 2000 совпадает с 20000
# https://stackoverflow.com/a/34074458/639133
CHECK=$(netstat -an -ptcp | grep LISTEN | grep -r "${PORT}\b")
done
echo ${PORT}
Мне понравился ответ w00t, но я не хотел, чтобы один и тот же свободный порт выбирался каждый раз. Добавив инкрементное обновление, это будет случайным образом выбирать свободный порт между 3000 и 13000 (но если все они заняты, он будет поочередно искать с этого случайно выбранного порта):
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
}
'
Еще один ответ. Он очень похож на несколько предложенных, но некоторые из них действительно не работают для меня. Некоторые не говорят мне, какой свободный порт я мог бы использовать. Некоторые дают мне порт, который на самом деле не свободен. А другие используют перемешивание, которое мне не нужно.
read first last < /proc/sys/net/ipv4/ip_local_port_range
# или определите свой собственный диапазон, например:
# first=9000; last=10000
for port in $(seq $first $last); do
ss -atun | grep -q ":$port " || break
done
echo $port
Или как длинный однострочник:
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
Мой подход, похожий на несколько идей здесь. Случайным образом выбирает из диапазона и проверяет с помощью nc
# Пытается найти свободный порт случайным образом из заданного диапазона
random_free_port() {
local max_attempts=5
local low_range=8432
local range=200
for i in $(seq "${max_attempts}"); do
# Выбираем случайный порт между 8432-8632
local port="$(($low_range + $RANDOM % $range))"
# Проверяем, не открыт ли порт
if ! nc -z -w 0 localhost 8433; then
echo "Найден свободный порт $port" 1>&2
echo "${port}"
return
fi
done
echo "Не удалось найти свободный порт после 5 попыток" 1>&2
return 1
}
#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];
}
/* Создаем сокет. */
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket");
exit (EXIT_FAILURE);
}
/* Присваиваем сокету имя. */
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("Порт - %d\n",ntohs(name.sin_port));
return sock;
}
Компиляция:
gcc getport.c -o getport
Использование: ./getport [адрес]
Вывод:
./getport
Порт - 31725
./getport 127.0.0.1
Порт - 32064
./getport 1.2.3.4
bind: Невозможно назначить запрашиваемый адрес
Ответ или решение
Создание приложения или сервиса, который требует использования свободного локального порта, может представлять определенные трудности, особенно если необходимо избежать конфликтов с уже занятой сетевой активностью. В данной ситуации мы рассмотрим несколько методов для нахождения неиспользуемого локального порта.
1. Использование порта 0
Наиболее простым и эффективным способом является использование порта 0
. Это запрос к операционной системе, которая автоматически назначит свободный порт из диапазона, определенного в ip_local_port_range
. После этого вы можете закрыть сокет и использовать полученный номер порта в вашей конфигурации.
Пример на Python:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 0)) # Привязываем сокет к порту 0
addr = s.getsockname()
print(addr[1]) # Выводим номер порта
s.close()
2. Использование утилиты ss
или netstat
Если вы предпочитаете командную строку, можно воспользоваться утилитой ss
(или netstat
) для получения списка используемых портов и нахождения свободного.
Пример в Bash:
while true; do
PORT=$(shuf -i 49152-65535 -n 1) # Генерация случайного порта
if ! ss -tln | grep -q ":$PORT"; then # Проверка, свободен ли порт
echo "Используйте порт: $PORT"
break
fi
done
3. Подборка свободного порта в заданном диапазоне
Вы можете самостоятельно перебрать порты в определенном диапазоне, чтобы найти свободный. Например, используем диапазон приписываемых портов 49152-65535
:
Пример:
read lower upper < /proc/sys/net/ipv4/ip_local_port_range
for port in $(seq $lower $upper); do
if ! ss -tln | grep -q ":$port"; then
echo "Найден свободный порт: $port"
break
fi
done
4. Кроссплатформенное решение
Вот кроссплатформенное решение с использованием awk
и netstat
, которое найдет первый свободный порт:
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
}
'
Заключение
Таким образом, для нахождения свободного локального порта вы можете использовать один из описанных выше методов — выбирать тот, который больше соответствует вашим требованиям и окружению. Использование ядром системы порта 0 является наиболее простым и надежным способом, а перебор портов — хорошим вариантом, если требуется более детальный контроль.