查找未使用的本地端口的最简单方法是什么?


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
你为什么要这么做?它具有固有的灵活性(效率低下-至少会增加-nnetstat和更具选择性的grep)。这样做的方法是尝试以所需的任何模式打开端口,如果端口不可用,则尝试另一个端口。
2012年

1
@Mat我正在尝试自动找到一个开放端口以ssh -D用作SOCKS服务器。
mybuddymichael 2012年

Answers:


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
该端口尝试使用第一个可用端口。当您有两个并发进程时,刚刚检查过的端口可能会被重用。重新阅读您的答案,似乎您建议在可用端口上重试绑定,直到所有端口用尽。假设所讨论的程序可以区分“使用中的端口”和其他错误,那么它应该很好(尽管随机化仍然可以使它具有更好的不可预测性)。
Lekensteyn 2014年

1
@Lekensteyn成功的端口绑定会导致内核在您再次尝试使用EADDRINUSE时返回EADDRINUSE,因此“刚刚检查的端口可能会被重用”。
克里斯·

是的,我错误地认为您将退出循环并$port在那样的实际程序中使用while ...; done; program --port $port
Lekensteyn 2014年

在手册页中:-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

运行该程序1万次(您应同时运行这些程序),您将获得10000个不同的端口号。因此,我认为使用端口非常安全。


20
这是一个单行代码(适用于Python 2和Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn 2014年

4
我进行了上述实验,并非所有结果都是唯一的。我的直方图是:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor 2015年

2
有没有一种简单的方法可以添加检查端口是否未被防火墙阻止,或者仅搜索打开的端口?
Mark Lakata

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数字端口-请勿尝试解析22ssh)。然后,我们仅选择带有的第四列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-uin 切换ss以获得免费的UDP端口。
  • 更换shufsort -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

归功于Chris Down


6

显然,在bash / zsh中,TCP连接可以用作 Linux上的文件描述符。以下函数使用该技术,并且应该比调用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,仍然可以获得EG35835。RANDOM返回[0,32767]中的数字。将此数值修改为大于最大值的数值无效。你想要类似的东西$[$LPORT + ($RANDOM % ($UPORT-$LPORT))]
lxs

否则虽然很酷!
lxs

\n尽管这会发送到任何侦听端口:)我建议添加-n。这仍将尝试打开连接,但不发送任何内容,而是立即断开连接。
stefanct

4

这是一个跨平台的,高效的“一体式”,它吞噬了所有使用中的端口,并为您提供了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
  }
'

您可以简单地将所有行合并到一行。如果要从其他端口号获得第一个可用的,请ifor循环中将分配更改为。

它可以在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绑定到另一个地址,则可以重用端口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,当正向失败不会返回一个错误。
斯特凡Chazelas

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

青年汽车


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

此代码纯粹便携使用netstategrepawk,和人。请注意,仅对外部命令发出调用,以在开始时获取已占用端口的列表。一个可以请求一个或多个空闲端口:

:;  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,我们从/ proc / sys / net / ipv4 / ip_local_port_range的范围(-i)中取一个随机数。shuf需要带破折号的语法,因此我们使用tr来更改破折号中的制表符。

下一步,如果我们在其中找到我们的随机端口$ port(以:开头,以w空格结尾( \ s),那么我们需要另一个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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.