Який найпростіший спосіб знайти місцевий порт, що не використовується?


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 і більш селективне задоволення). Спосіб зробити це - спробувати відкрити порт у будь-якому режимі, який вам потрібен, і спробуйте інший, якщо він недоступний.
Мат

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.
Лекенштейн

Зі довідкової сторінки: -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}
bukzor

2
Чи є простий спосіб додати в чек, що порт не блокується брандмауером, а точніше, лише шукає відкриті порти?
Марк Лаката

1
@dshepherd Я вважаю, що ви отримаєте різні порти, якщо не закриєте попередній (і закриєте їх одразу нарешті).
Франклін Ю

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

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також виводимо заголовок, від якого ми позбавляємося за допомогою grepping за не порожні послідовності чисел, що не перевищують 5. Потім ми виконуємо commвимоги sorting без дублікатів -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))].
lxs

Інакше досить круто, хоча!
lxs

Це надсилається \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навіть працює над Solaris.
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) тоді нам потрібен інший порт і так продовжуємо. Else (grep -q має зворотний код> 0, ми залишили цикл while і $ порт встановлений.


1

Якщо у вас лежить пітон, я б це зробив:

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.