วิธีที่ง่ายที่สุดในการค้นหาพอร์ตในเครื่องที่ไม่ได้ใช้คืออะไร


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 ที่เลือกมากกว่า) วิธีที่จะทำคือลองและเปิดพอร์ตในโหมดใดก็ตามที่คุณต้องการและลองใช้พอร์ตอื่นหากไม่พร้อมใช้งาน
Mat

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: คุณเห็นสภาพการแข่งขันที่นี่ที่ไหน?
Chris Down

1
พอร์ตนี้พยายามใช้พอร์ตแรกที่มีอยู่ เมื่อคุณมีกระบวนการที่เกิดขึ้นพร้อมกันสองกระบวนการพอร์ตที่เพิ่งตรวจสอบอาจถูกนำมาใช้ซ้ำ อ่านคำตอบของคุณอีกครั้งดูเหมือนว่าคุณแนะนำให้ลองทำการผูกบนพอร์ตที่มีอยู่จนกว่าพอร์ตทั้งหมดจะหมด สมมติว่าโปรแกรมที่เป็นปัญหาสามารถแยกแยะระหว่าง "พอร์ตที่ใช้งาน" และข้อผิดพลาดอื่น ๆ ก็ควรจะดี (แม้ว่าการสุ่มจะทำให้ดีขึ้นสำหรับการคาดเดาไม่ได้)
Lekensteyn

1
@Lekensteyn การเชื่อมต่อพอร์ตที่ประสบความสำเร็จส่งผลให้เคอร์เนลส่งคืน EADDRINUSE หากคุณลองใช้อีกครั้งมันเป็นไปไม่ได้ที่ "พอร์ตที่เพิ่งตรวจสอบอาจถูกนำมาใช้ซ้ำ"
Chris Down

ใช่ฉันผิดคิดว่าคุณจะออกจากวงและใช้ในโครงการที่เกิดขึ้นจริงในขณะที่$port while ...; done; program --port $port
Lekensteyn

จากหน้า man: -p source_port ระบุพอร์ตต้นทาง nc ที่ควรใช้ภายใต้ข้อ จำกัด สิทธิ์และความพร้อมใช้งาน มันเป็นข้อผิดพลาดในการใช้ตัวเลือกนี้ร่วมกับตัวเลือก -l
พระสงฆ์

54

วิธีแก้ปัญหาของฉันคือผูกเข้ากับพอร์ต 0 ซึ่งขอให้เคอร์เนลจัดสรรพอร์ตจาก ip_local_port_range จากนั้นปิดซ็อกเก็ตและใช้หมายเลขพอร์ตนั้นในการกำหนดค่าของคุณ

วิธีนี้ใช้งานได้เนื่องจากเคอร์เนลดูเหมือนจะไม่ใช้หมายเลขพอร์ตซ้ำจนกว่าจะได้ การโยงที่ตามมากับพอร์ต 0 จะจัดสรรหมายเลขพอร์ตอื่น รหัสหลาม:

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 2 และ Python 3):python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
Lekensteyn

4
ฉันใช้การทดสอบที่กล่าวถึง แต่ผลลัพธ์ทั้งหมดนั้นไม่ซ้ำกัน ฮิสโตแกรมของฉันคือ:{ 1: 7006, 2: 1249, 3: 151, 4: 8, 5: 1, 6: 1}
bukzor

2
มีวิธีง่ายๆในการเพิ่มการตรวจสอบว่าพอร์ตไม่ได้ถูกบล็อกโดยไฟร์วอลล์หรือค้นหาเฉพาะพอร์ตที่เปิดอยู่ใช่หรือไม่
Mark Lakata

1
@dshepherd ฉันเชื่อว่าคุณจะได้รับพอร์ตที่แตกต่างกันหากคุณไม่ปิดพอร์ตก่อนหน้า (และปิดพอร์ตทั้งหมดในครั้งเดียวในที่สุด)
Franklin Yu

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's ความต้องการโดยไอเอ็นจีได้โดยไม่ซ้ำกัน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

มอบเครดิตให้กับ Chris Down


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

นี่คือ cross-platform, "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 ซึ่งเป็นเหตุผลว่าทำไมจึง[:.]จำเป็นต้องมี regex


ทำไม-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'

อย่างไรก็ตามสิ่งนี้เกี่ยวข้องกับสภาวะการแย่งชิงระหว่างการพยายามเปิดพอร์ตและใช้งานจริง
Chris Down

@ChrisDown แน่นอน แต่ด้วยssh -Dฉันไม่เห็นตัวเลือกที่ดีกว่านี้อีกแล้ว -O forwardตัวเลือกในการsshไม่ส่งกลับข้อผิดพลาดเมื่อไปข้างหน้าล้มเหลว
Stéphane 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

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) หากเราพบพอร์ตสุ่มพอร์ตของเรา $ ในสิ่งนี้ (เริ่มต้นด้วย: และลงท้ายด้วย w whitespace ( \ s) เราต้องใช้พอร์ตอื่นและดำเนินการต่ออื่น ๆ (grep -q มี returncode> 0 เราออกจากห่วงขณะที่และ $ พอร์ตถูกตั้งค่า


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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.