Cách dễ nhất để tìm một cổng địa phương không sử dụng là gì?


52

Cách dễ nhất để tìm một cổng địa phương không sử dụng là gì?

Hiện tại tôi đang sử dụng một cái gì đó tương tự như thế này:

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

Nó cảm thấy hết sức vòng vo, vì vậy tôi tự hỏi liệu có một con đường đơn giản hơn như tích hợp mà tôi đã bỏ lỡ.


2
Tại sao bạn muốn làm điều đó? Nó vốn dĩ không phù hợp (và không hiệu quả - và ít nhất là thêm -nvào netstat và một grep chọn lọc hơn). Cách thực hiện là thử và mở một cổng ở bất kỳ chế độ nào bạn cần và thử một cổng khác nếu không có sẵn.
Mat

1
@Mat Tôi đang cố gắng tự động tìm một cổng mở để sử dụng với ssh -Dtư cách là máy chủ SOCKS.
mybuddymichael

Câu trả lời:


24

Nếu ứng dụng của bạn hỗ trợ nó, bạn có thể thử chuyển cổng 0 cho ứng dụng. Nếu ứng dụng của bạn chuyển cái này đến kernel, cổng sẽ được cấp phát động tại thời điểm yêu cầu và được đảm bảo không sử dụng (phân bổ sẽ thất bại nếu tất cả các cổng đã được sử dụng).

Nếu không, bạn có thể làm điều này bằng tay. Kịch bản trong câu trả lời của bạn có một điều kiện chủng tộc, cách duy nhất để tránh nó là kiểm tra nguyên tử nếu nó được mở bằng cách cố gắng mở nó. Nếu cổng đang được sử dụng, chương trình sẽ thoát khi không mở được cổng.

Ví dụ: giả sử bạn đang cố nghe bằng 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: Bạn thấy tình trạng đua ở đâu?
Chris Down

1
Cổng này cố gắng sử dụng cổng đầu tiên có sẵn. Khi bạn có hai quy trình đồng thời, thì cổng vừa được kiểm tra có thể được sử dụng lại. Đọc lại câu trả lời của bạn, có vẻ như bạn đề nghị thử lại ràng buộc trên một cổng có sẵn cho đến khi tất cả các cổng đã hết. Giả sử rằng chương trình đang được đề cập có thể phân biệt giữa "cổng đang sử dụng" và các lỗi khác, thì nó vẫn ổn (mặc dù ngẫu nhiên vẫn sẽ giúp nó tốt hơn khi không thể đoán trước).
Lekensteyn

1
@Lekensteyn Kết quả liên kết cổng thành công trong kernel trả về EADDRINUSE nếu bạn thử và sử dụng lại, không thể "cổng vừa được kiểm tra có thể được sử dụng lại".
Chris Down

Có, tôi đã sai khi cho rằng bạn sẽ thoát khỏi vòng lặp và sử dụng $porttrong chương trình thực tế như trong while ...; done; program --port $port.
Lekensteyn

Từ trang man: -p source_port Chỉ định cổng nguồn nc nên sử dụng, tuân theo các hạn chế đặc quyền và tính khả dụng. Đó là một lỗi khi sử dụng tùy chọn này kết hợp với tùy chọn -l.
tu sĩ

54

Giải pháp của tôi là liên kết với cổng 0, yêu cầu kernel phân bổ một cổng từ đó là ip_local_port_range. Sau đó, đóng ổ cắm và sử dụng số cổng đó trong cấu hình của bạn.

Điều này hoạt động vì kernel dường như không sử dụng lại số cổng cho đến khi nó hoàn toàn phải. Các liên kết tiếp theo với cổng 0 sẽ phân bổ một số cổng khác. Mã Python:

import socket

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

Điều này chỉ cung cấp một số cổng, ví dụ. 60123.

Chạy chương trình này 10 000 lần (bạn nên chạy đồng thời các chương trình này) và bạn sẽ nhận được 10 000 số cổng khác nhau. Vì vậy, tôi nghĩ rằng nó khá an toàn để sử dụng các cổng.


20
Đây là một lớp lót (hợp lệ với Python 2 và Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn

4
Tôi đã chạy thử nghiệm được đề cập, và không phải tất cả các kết quả là duy nhất. Biểu đồ của tôi là:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor

2
Có cách nào dễ dàng để thêm vào kiểm tra rằng cổng không bị chặn bởi tường lửa hay chỉ tìm kiếm các cổng mở?
Đánh dấu Lakata

1
@dshepherd Tôi tin rằng bạn sẽ nhận được các cổng khác nhau nếu bạn không đóng cổng trước đó (và đóng tất cả chúng cùng một lúc).
Franklin Yu

1
Một lớp lót cho Ruby 2.3.1:ruby -e 'puts Addrinfo.tcp("", 0).bind { |s| s.local_address.ip_port }'
Franklin Yu

12

Lót

Tôi đã kết hợp một lớp lót đẹp, nhanh chóng phục vụ mục đích, cho phép lấy một số cổng tùy ý trong một phạm vi tùy ý (ở đây được chia thành 4 dòng để dễ đọc):

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"

Từng dòng

commlà một tiện ích so sánh các dòng trong hai tệp phải xuất hiện được sắp xếp theo thứ tự abc. Nó xuất ra ba cột: các dòng chỉ xuất hiện trong tệp đầu tiên, các dòng chỉ xuất hiện trong cột thứ hai và các dòng chung. Bằng cách chỉ định -23chúng tôi triệt tiêu các cột sau và chỉ giữ cột đầu tiên. Chúng ta có thể sử dụng điều này để có được sự khác biệt của hai bộ, được biểu thị dưới dạng một chuỗi các dòng văn bản. Tôi đã học về comm đây .

Tệp đầu tiên là phạm vi cổng mà chúng ta có thể chọn. seqtạo ra một chuỗi các số được sắp xếp từ $FROMđến $TO. Kết quả được sắp xếp theo thứ tự abc (thay vì số) và được chuyển commthành tập tin đầu tiên sử dụng thay thế quy trình .

Tệp thứ hai là danh sách các cổng được sắp xếp mà chúng ta có được bằng cách gọi sslệnh (với -tnghĩa là các cổng TCP, -anghĩa là tất cả - được thiết lập và lắng nghe - và -nsố - không cố gắng giải quyết, nói, 22thành ssh). Sau đó chúng tôi chỉ chọn cột thứ tư với awk, chứa địa chỉ và cổng cục bộ. Chúng tôi sử dụng cutđể phân tách địa chỉ và cổng với :dấu phân cách và chỉ giữ lại cái sau ( -f2). sscũng xuất ra một tiêu đề mà chúng ta loại bỏ bằng grepping cho các chuỗi số không trống không dài hơn 5. Sau đó, chúng tôi tuân thủ commyêu cầu của chúng bằng cách nhập sortmà không trùng lặp -u.

Bây giờ chúng ta có một danh sách sắp xếp các cổng mở, mà chúng ta có thể shufchạy đến sau đó lấy các cổng đầu tiên "$HOWMANY"với head -n.

Thí dụ

Lấy ba cổng mở ngẫu nhiên trong phạm vi riêng tư (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

có thể trở lại ví dụ

54930
57937
51399

Ghi chú

  • chuyển -tvới -utrong ssđể có được cổng UDP miễn phí thay thế.
  • thay thế shufbằng sort -nnếu bạn muốn có được các cổng có sẵn được sắp xếp bằng số thay vì ngẫu nhiê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

Tín dụng cho Chris Down


6

Rõ ràng các kết nối TCP có thể được sử dụng làm mô tả tệp trên linux từ trong bash / zsh. Hàm sau sử dụng kỹ thuật đó và sẽ nhanh hơn gọi 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
}

Hướng dẫn sử dụng: Liên kết đầu ra với một biến và sử dụng trong các tập lệnh. Đã thử nghiệm trên Ubuntu 16.04

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

Làm việc với ksh93cũng.
fpmurphy

Nếu bạn thay đổi LÊN thành 32768, bạn vẫn có thể nhận được EG 35835. RANDOM trả về một số trong [0,32767]. Sửa đổi điều này bằng một số lớn hơn tối đa không có hiệu lực. Bạn muốn một cái gì đó như $[$LPORT + ($RANDOM % ($UPORT-$LPORT))].
lxs

Nếu không thì khá tuyệt!
lxs

Điều này sẽ gửi \nđến bất kỳ cổng nghe nào :) Tôi muốn đề xuất thêm -n. Điều này vẫn sẽ cố gắng để mở một kết nối nhưng không gửi bất cứ điều gì nhưng ngay lập tức ngắt kết nối.
stefanct

4

Đây là một "oneliner" đa nền tảng, hiệu quả, làm mờ tất cả các cổng đang sử dụng và cung cấp cho bạn cổng đầu tiên có sẵn từ 3000 trở đi:

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

Bạn chỉ có thể tham gia tất cả các dòng để có nó trên một dòng. Nếu bạn muốn có sẵn cái đầu tiên từ một số cổng khác, hãy thay đổi gán thành itrong forvòng lặp.

Nó hoạt động trên cả Mac và Linux, đó là lý do tại sao [:.]regex là cần thiết.


Tại sao -avà không -tchỉ nhìn vào ổ cắm TCP (6)?
stefanct

Và trong khi chúng ta đang ở đó, phân tích đầu ra của ss -Htnlcó thể tốt hơn (và nhanh hơn! - không phải là tôi quan tâm đến điều đó: P).
stefanct

@stefanct BSD netstat không có -t, ít nhất là cái mà Apple gửi kèm, và sscũng không có trên macOS. netstat -alnthậm chí hoạt động trên Solaris.
w00t

3

Trên Linux, bạn có thể làm một cái gì đó như:

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

Để tìm cổng miễn phí đầu tiên trên 1080. Lưu ý rằng ssh -Dsẽ liên kết trên giao diện loopback, vì vậy về mặt lý thuyết, bạn có thể sử dụng lại cổng 1080 nếu ổ cắm bị ràng buộc ở địa chỉ khác. Một cách khác là thực sự cố gắng và ràng buộc nó:

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'

Tuy nhiên, điều này liên quan đến một điều kiện chạy đua giữa việc cố gắng mở cổng và thực sự sử dụng nó.
Chris Xuống

@ChrisDown, Thật vậy, nhưng với ssh -D, tôi không thể thấy bất kỳ tùy chọn nào tốt hơn. Các -O forwardtùy chọn sshkhông trả lại một lỗi khi chuyển tiếp thất bại.
Stéphane Chazelas

3

Đây là phiên bản tôi sử dụng:

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

echo "$port"

Lệnh shuf -n 1 -i 49152-65535cung cấp cho bạn một cổng "ngẫu nhiên" trong phạm vi động. Nếu nó đã được sử dụng, một cổng khác trong phạm vi đó sẽ được thử.

Lệnh netstat -atunliệt kê tất cả các cổng (-a) TCP (-t) và UDP (-u) mà không lãng phí thời gian để xác định tên máy chủ (-n).


1

Đây là một phần của hàm tôi có trong .bashrc, nó tự động tạo các đường hầm SSH và cố gắng sử dụng bất kỳ cổng nào trong một phạm vi:

   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

Một cuộc chạy đua khác tại sở thích cũ này:

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

Mã này tận dụng hoàn toàn di động của netstat, egrep, awk, & al. Lưu ý rằng chỉ có cuộc gọi được cấp cho các lệnh bên ngoài, để có được danh sách các cổng đã thực hiện ở đầu. Người ta có thể yêu cầu một hoặc nhiều cổng miễn phí:

:;  random_free_tcp_port
2070
:;  random_free_tcp_port 2
2073
2114

và bắt đầu tại một cổng tùy ý:

:;  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

Sự kết hợp của tôi từ các câu trả lời khác ở trên. Hiểu rồi:

Với shuf -n1, chúng tôi lấy một số ngẫu nhiên từ phạm vi (-i) trong / Proc / sys / net / ipv4 / ip_local_port_range. shuf cần cú pháp với dấu gạch ngang, vì vậy chúng tôi sử dụng tr để thay đổi tab trong dấu gạch ngang.

Wie tiếp theo sử dụng netstat để hiển thị cho chúng tôi tất cả các kết nối (-a) tcp và udp (-u -t) trong các số (-n), nếu chúng tôi tìm thấy cổng $ port ngẫu nhiên của chúng tôi trong này (bắt đầu bằng a: và kết thúc bằng khoảng trắng w ( \ s) sau đó chúng ta cần một Cổng khác và tiếp tục. Khác (grep -q có mã trả về> 0 chúng tôi rời khỏi vòng lặp while và cổng $ được đặt.


1

Nếu bạn có con trăn nằm xung quanh, tôi sẽ làm điều này:

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


0

Tôi đảm nhận ... chức năng này cố gắng tìm ncác cổng miễn phí liên tiếp:

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.