Qual é a maneira mais fácil de encontrar uma porta local não utilizada?

52

Qual é a maneira mais fácil de encontrar uma porta local não utilizada?

Atualmente estou usando algo semelhante a este:

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

Parece terrivelmente rotativo, então estou me perguntando se há um caminho mais simples, como um embutido, que eu perdi.

mybuddymichael
fonte
2
Por que você quer fazer isso? É inerentemente atrevido (e ineficiente - e menos adicionado -nao netstat e a um grep mais seletivo). A maneira de fazer isso é tentar abrir uma porta no modo que você precisar e tentar outra se ela não estiver disponível.
Mat
11
@ Mat Estou tentando encontrar automaticamente uma porta aberta para usar ssh -Dcomo um servidor SOCKS.
Mybuddymichael
Pergunta semelhante: stackoverflow.com/questions/13308144/… || POSIX: stackoverflow.com/questions/913501/…
Ciro Santilli escreveu:

Respostas:

24

Se o seu aplicativo suportar, você pode tentar passar a porta 0 para o aplicativo. Se seu aplicativo passar isso para o kernel, a porta será alocada dinamicamente no momento da solicitação e é garantida que não esteja em uso (a alocação falhará se todas as portas já estiverem em uso).

Caso contrário, você pode fazer isso manualmente. O script na sua resposta tem uma condição de corrida, a única maneira de evitá-lo é verificar atomicamente se está aberto, tentando abri-lo. Se a porta estiver em uso, o programa deve sair com uma falha ao abrir a porta.

Por exemplo, diga que você está tentando ouvir com o 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
Chris Down
fonte
11
@Lekensteyn: Onde você vê uma condição de corrida aqui?
Chris Baixo
11
Essa porta tenta usar a primeira porta disponível. Quando você tem dois processos simultâneos, a porta que acabou de ser verificada pode ser reutilizada. Relendo sua resposta, parece que você sugere tentar novamente a ligação em uma porta disponível até que todas as portas estejam esgotadas. Supondo que o programa em questão possa distinguir entre "porta em uso" e outros erros, deve ser bom (embora a randomização ainda o torne melhor por imprevisibilidade).
Lekensteyn
11
@Lekensteyn A ligação bem sucedida da porta resulta no retorno do EADDRINUSE do kernel se você tentar usá-lo novamente, não é possível que "a porta que acabou de ser verificada possa ser reutilizada".
Chris Baixo
Sim, assumi erroneamente que você sairia do loop e usaria $portno programa real como em while ...; done; program --port $port.
Lekensteyn
Na página do manual: -p source_port Especifica a porta de origem que a nc deve usar, sujeita a restrições e disponibilidade de privilégios. É um erro usar esta opção em conjunto com a opção -l.
monksy
54

Minha solução é ligar a porta 0, que solicita ao kernel que aloque uma porta a partir do ip_local_port_range. Em seguida, feche o soquete e use esse número de porta na sua configuração.

Isso funciona porque o kernel não parece reutilizar números de porta até que seja absolutamente necessário. As ligações subsequentes à porta 0 alocam um número de porta diferente. Código Python:

import socket

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

Isso fornece apenas um número de porta, por exemplo. 60123.

Execute este programa 10.000 vezes (você deve executá-los simultaneamente) e obterá 10.000 números de porta diferentes. Portanto, acho que é bastante seguro usar as portas.

Mark Theunissen
fonte
20
Aqui está um one-liner (válido com Python 2 e Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn 2/14
4
Fiz o experimento mencionado e nem todos os resultados foram únicos. Meu histograma era:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor
2
Existe uma maneira fácil de adicionar uma verificação de que a porta não está bloqueada por um firewall ou apenas pesquisar portas abertas?
Mark Lakata
11
@dshepherd Eu acredito que você terá portas diferentes se não fechar a anterior (e feche todas de uma só vez).
Franklin Yu
11
Um liner para Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu
12

One-liner

Eu montei um belo one-liner que serve rapidamente a esse objetivo, permitindo pegar um número arbitrário de portas em um intervalo arbitrário (aqui está dividido em 4 linhas para facilitar a leitura):

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"

Linha por linha

commé um utilitário que compara linhas em dois arquivos que devem aparecer classificados em ordem alfabética. Ele produz três colunas: linhas que aparecem apenas no primeiro arquivo, linhas que aparecem apenas no segundo arquivo e linhas comuns. Ao especificar -23, suprimimos as últimas colunas e mantemos apenas a primeira. Podemos usar isso para obter a diferença de dois conjuntos, expressos como uma sequência de linhas de texto. Eu aprendi sobre comm aqui .

O primeiro arquivo é o intervalo de portas que podemos selecionar. seqproduz uma sequência classificada de números de $FROMa $TO. O resultado é classificado em ordem alfabética (em vez de numericamente) e canalizado para commo primeiro arquivo usando a substituição do processo .

O segundo arquivo é a lista ordenada de portas, que obtemos chamando o sscomando (com -tas portas TCP intencionados, -aque significa que todos - estabelecida e ouvir - e -nnumérico - não tentar resolver, por exemplo, 22a ssh). Em seguida, escolhemos apenas a quarta coluna com awk, que contém o endereço local e a porta. Usamos cutpara dividir endereço e porta com o :delimitador e manter apenas o último ( -f2). ssTambém produzimos um cabeçalho, do qual nos livramos do grepping para seqüências não vazias de números que não são maiores que 5. Em seguida, cumprimos commo requisito de sorting sem duplicatas -u.

Agora temos uma lista ordenada de portas abertas, que podemos shuffle para, em seguida, pegar o primeiro "$HOWMANY"aqueles com head -n.

Exemplo

Pegue as três portas abertas aleatórias no intervalo privado (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

poderia retornar por exemplo

54930
57937
51399

Notas

  • mudar -tcom -uno sspara obter as portas UDP gratuitos vez.
  • substitua shufpor sort -nse você preferir obter as portas disponíveis ordenadas numericamente em vez de aleatoriamente
stefanobaghino
fonte
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

Créditos a Chris Down

Stefan Leisten
fonte
6

Aparentemente, as conexões TCP podem ser usadas como descritores de arquivo no linux a partir do bash / zsh. A função a seguir usa essa técnica e deve ser mais rápida do que chamar 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
}

Instruções de uso: Ligue a saída a uma variável e use em scripts. Testado no Ubuntu 16.04

root@ubuntu:~> EPHEMERAL_PORT
59453
root@ubuntu:~> PORT=$(EPHEMERAL_PORT)
Sandeep
fonte
Funciona com ksh93também.
precisa saber é o seguinte
Se você alterar UPORT para 32768, ainda poderá obter o EG 35835. RANDOM retorna um número em [0,32767]. Modificar isso por um número maior que o máximo não tem efeito. Você quer algo parecido $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs 04/01
Caso contrário, muito legal!
lxs 04/01
Isso envia \npara qualquer porta de escuta embora :) eu sugiro adicionar -n. Isso ainda tentará abrir uma conexão, mas não enviará nada, mas será desconectado imediatamente.
stefanct 14/02
4

Aqui está um "oneliner" eficiente, multiplataforma, que consome todas as portas em uso e oferece o primeiro disponível a partir de 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
  }
'

Você pode simplesmente juntar todas as linhas para tê-lo em uma linha. Se você deseja obter o primeiro disponível a partir de um número de porta diferente, altere a atribuição para ino forloop.

Funciona em Mac e Linux, e é por isso que o [:.]regex é necessário.

w00t
fonte
Por que -ae não -tapenas olhar para os soquetes TCP (6)?
stefanct 14/02
E enquanto estamos nisso, analisar a saída de ss -Htnlpode ser melhor (e mais rápido! - não que eu me importe com isso: P).
stefanct 14/02
@stefanct O BSD netstat não possui -t, pelo menos aquele fornecido pela Apple e sstambém não está presente no macOS. netstat -alnaté funciona no Solaris.
w00t
3

No Linux, você pode fazer algo como:

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

Para encontrar a primeira porta livre acima de 1080. Observe que isso ssh -Dseria vinculado à interface de loopback; portanto, em teoria, você poderia reutilizar a porta 1080 se um soquete a vincular a outro endereço. Outra maneira seria realmente tentar vinculá-lo:

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'
Stéphane Chazelas
fonte
No entanto, isso envolve uma condição de corrida entre tentar abrir a porta e realmente usá-la.
Chris baixo
@ ChrisDown, De fato, mas com ssh -D, não vejo melhor opção. A -O forwardopção de sshnão retorna um erro quando o encaminhamento falha.
Stéphane Chazelas
3

Esta é a versão que eu uso:

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

echo "$port"

O comando shuf -n 1 -i 49152-65535fornece uma porta "aleatória" no intervalo dinâmico. Se já estiver sendo usado, outra porta nesse intervalo será tentada.

O comando netstat -atunlista todas as portas (-a) TCP (-t) e UDP (-u) sem perder tempo para determinar os nomes de host (-n).

pfo
fonte
1

Isso faz parte de uma função que tenho no meu .bashrc, que cria dinamicamente túneis SSH e tenta usar qualquer porta em um intervalo:

   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

mills013
fonte
1

Ainda outra corrida neste velho cavalo de hobby:

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[@]}"
}

Este código faz uso puramente portátil de netstat, egrep, awk, e al. Observe que apenas a chamada é emitida para comandos externos, para obter uma lista das portas tomadas no início. Pode-se solicitar uma ou mais portas gratuitas:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

e comece em uma porta arbitrária:

:;  random_free_tcp_port 2 10240
10245
10293
solidsnack
fonte
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

Minha combinação de outras respostas acima. Pegue:

Com shuf -n1, pegamos um número aleatório do intervalo (-i) em / proc / sys / net / ipv4 / ip_local_port_range. Como o shuf precisa da sintaxe com traço, usamos tr para alterar a guia em um traço.

Em seguida, use o netstat para nos mostrar todas as conexões (-a) tcp e udp (-u -t) em números (-n), se encontrarmos nossa porta aleatória $ port nessa (inicie com a: e termine com w whitespace ( \ s) então precisamos de uma outra porta e então continue.Else (grep -q tem um código de retorno> 0, deixamos o loop while e $ port estão definidas.

Winfried Münch
fonte
1

Se você tem python por aí, eu faria o seguinte:

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

Minha opinião ... a função tenta encontrar nportas livres consecutivas:

#!/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
stefanct
fonte