Какой самый простой способ найти неиспользуемый локальный порт?


52

Какой самый простой способ найти неиспользуемый локальный порт?

В настоящее время я использую что-то похожее на это:

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

Это кажется ужасно окольным, поэтому мне интересно, есть ли более простой путь, такой как встроенный, который я пропустил.


2
Почему ты хочешь это сделать? Это по своей природе очень нехорошо (и неэффективно - и меньше всего добавляет -nк netstat и более избирательному grep). Чтобы сделать это, попробуйте открыть порт в любом нужном вам режиме и попробуйте другой, если он недоступен.
Мат

1
@ Mat Я пытаюсь автоматически найти открытый порт для использования ssh -Dв качестве сервера SOCKS.
mybuddymichael

Ответы:


24

Если ваше приложение поддерживает это, вы можете попробовать передать порт 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

1
@Lekensteyn: Где вы видите здесь условия гонки?
Крис Даун

1
Этот порт пытается использовать первый доступный порт. Если у вас есть два одновременных процесса, то только что проверенный порт может быть использован повторно. Перечитывая ваш ответ, кажется, что вы предлагаете повторить привязку к свободному порту, пока все порты не будут исчерпаны. Предполагая, что рассматриваемая программа может различать «используемый порт» и другие ошибки, все должно быть в порядке (хотя рандомизация все же сделает ее лучше для непредсказуемости).
Лекенштейн

1
@Lekensteyn Успешное связывание портов приводит к тому, что ядро ​​возвращает EADDRINUSE, если вы попытаетесь использовать его снова, невозможно, чтобы «только что проверенный порт мог быть повторно использован».
Крис Даун

Да, я ошибочно предположил, что вы выйдете из цикла и будете использовать его $portв реальной программе, как в while ...; done; program --port $port.
Лекенштейн

На странице man: -p source_port Указывает исходный порт, который должен использовать nc, с учетом ограничений привилегий и доступности. Ошибочно использовать эту опцию в сочетании с опцией -l.
монах

54

Мое решение состоит в том, чтобы привязать к порту 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,

Запустите эту программу 10 000 раз (вы должны запустить их одновременно), и вы получите 10 000 различных номеров портов. Поэтому я думаю, что использовать порты довольно безопасно.


20
Вот одна python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
строка

4
Я провел упомянутый эксперимент, и не все результаты были уникальными. Моя гистограмма была:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
Букзор

2
Есть ли простой способ добавить в проверку, что порт не заблокирован брандмауэром, или, скорее, ищет только открытые порты?
Марк Лаката

1
@dshepherd Я полагаю, что вы получите другие порты, если не закроете предыдущий (и, наконец, закроете их все сразу).
Франклин Ю

1
Один лайнер для Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Франклин Ю

12

Один лайнер

Я собрал хороший однострочный текст, который быстро служит цели, позволяя захватывать произвольное количество портов в произвольном диапазоне (здесь он разделен на 4 строки для удобства чтения):

comm -23 \
<(seq "$FROM" "$TO" | sort) \
<(ss -tan | awk '{print $4}' | cut -d':' -f2 | grep '[0-9]\{1,5\}' | sort -u) \
| shuf | head -n "$HOWMANY"

Построчно

commутилита, которая сравнивает строки в двух файлах, которые должны быть отсортированы по алфавиту. Он выводит три столбца: строки, которые появляются только в первом файле, строки, которые появляются только во втором файле, и общие строки. Указывая, -23мы подавляем последние столбцы и оставляем только первый. Мы можем использовать это, чтобы получить разницу двух наборов, выраженную в виде последовательности текстовых строк. Я узнал об этом comm здесь .

Первый файл - это диапазон портов, из которых мы можем выбирать. seqпроизводит отсортированную последовательность чисел от $FROMдо $TO. Результат сортируется по алфавиту (а не по номерам) и передается в commкачестве первого файла с использованием процесса подстановки .

Второй файл - это отсортированный список портов, который мы получаем, вызывая ssкоманду (имея в -tвиду TCP-порты, то -aесть все установленные и прослушиваемые - и -nчисловые - не пытайтесь разрешить, скажем, 22к ssh). Затем мы выбираем только четвертый столбец с awk, который содержит локальный адрес и порт. Мы используем cutдля разделения адреса и порта с :разделителем и оставляем только последний ( -f2). ssтакже выведите заголовок, от которого мы избавляемся, grepвыполняя команду ping для непустых последовательностей чисел, которые не длиннее 5. Затем мы commвыполняем требования, используя sortбез дубликатов -u.

Теперь у нас есть отсортированный список открытых портов, что мы можем shufFLE , чтобы затем захватить первые "$HOWMANY"те , с head -n.

пример

Захватите три случайных открытых порта в частном диапазоне (49152-65535)

comm -23 <(seq 49152 65535 | sort) <(ss -tan | awk '{print $4}' | cut -d':' -f2 | grep "[0-9]\{1,5\}" | sort -u) | shuf | head -n 3

может вернуться к примеру

54930
57937
51399

Примечания

  • переключаться -tс -uв , ssчтобы получить свободные порты UDP вместо этого.
  • замените shufна, sort -nесли вы предпочитаете, чтобы доступные порты сортировались по номерам вместо случайных

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

Кредиты Крису Дауну


6

Очевидно, что TCP-соединения могут использоваться в качестве файловых дескрипторов в linux из bash / zsh. Следующая функция использует эту технику и должна быть быстрее, чем вызов 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
}

Инструкции по использованию: связать вывод с переменной и использовать в сценариях. Проверено на Ubuntu 16.04

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

Работает ksh93также.
fpmurphy

Если вы измените UPORT на 32768, вы все равно можете получить EG 35835. RANDOM возвращает число в [0,32767]. Модификация этого числа большим, чем максимальное, не имеет никакого эффекта. Вы хотите что-то вроде $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
1

В остальном довольно круто, хотя!
1

Это отправляет \nна любой порт прослушивания, хотя :) Я бы предложил добавить -n. При этом все равно будет пытаться открыть соединение, но ничего не будет отправлено, но будет немедленно отключено.
Stefanct

4

Вот кроссплатформенный, эффективный «oneliner», который забирает все используемые порты и дает вам первый доступный от 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, поэтому необходимо [:.]регулярное выражение.


Почему бы -aи не -tпросто посмотреть на сокеты TCP (6)?
Stefanct

И пока мы это делаем, анализ выходных данных ss -Htnlможет быть лучше (и быстрее! - не то, чтобы я заботился об этом: P).
Stefanct

@stefanct BSD netstat не имеет -t, по крайней мере, тот, который поставляется с Apple, и ssтакже отсутствует в macOS. netstat -alnдаже работает на солярисе.
w00t

3

В 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'

Это, однако, связано с состоянием гонки между попыткой открыть порт и его фактическим использованием.
Крис Даун

@ChrisDown, действительно, но с ssh -D, я не вижу лучшего варианта. -O forwardВариант sshне возвращает ошибку , когда вперед неудачи.
Стефан Шазелас

3

Это версия, которую я использую:

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).


1

Это часть функции, которая есть в моем .bashrc, которая динамически создает SSH-туннели и пытается использовать любой порт в диапазоне:

   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


1

Еще один бег на этой старой лошади-хобби:

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

1
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 в этом (начинаем с: и заканчиваем w пробелами ( \ s) тогда нам нужен другой порт и так далее. В противном случае (grep -q имеет код возврата> 0, мы оставили цикл while и $ port установлен.


1

Если у вас есть Python, я бы сделал это:

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


0

Мой взгляд на это ... функция пытается найти nпоследовательные свободные порты:

#!/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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.