需求

最近有个运维需求,需要测试客户端的UDP端口与服务器的连通性。

需求也很简单:客户端测往服务端发UDP包,服务端收到包后响应客户端,当客户端能收到服务端的响应则可断定端口是可达的。但是客户端需要测试的端口范有很多,是个很大的端口范围。

思路

虽然nc等工具可以测试端口,但是面对多端口测试场景,就显得捉襟见肘了,因此就想到使用Python的socket编程来自己写一个工具来实现这个功能。

具体思路如下:

  1. 由客户端指定本地端口范围,也可以不指定,不指定则交由系统使用随机端口
  2. 当指定了本地端口,循环结束后停止运行,并输出统计结果
  3. 服务端为固定端口
  4. 如果需要服务端也需要变更为端口范围,需要有一个额外的线程来控制协商端口号,因为涉及到系统可能会存在端口被占用、端口不通等异常情况需要处理,因此简单的一个循环不能解决问题,可以参考下一篇用Python写一个UDP端口测试工具(二)

show you the code

服务端

#!/usr/bin/env python                                                                                                                                                                                                                                                         

from __future__ import print_function

import socket
import sys
import signal
import os

def h():
    print(""" usage:""")
    print("""   this_program <listen_port>""")
    print("""   listen_port is not specified, default is 4000""")

    print()
    print(" examples:")
    print("   ./udpserver.py 4000")
    print()

def signal_handler(signal, frame):
    os._exit(0)

signal.signal(signal.SIGINT, signal_handler)

if len(sys.argv)>1:
    try:
        port = int(sys.argv[1])
    except:
        h()
        exit()
else:
    port = 4000

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))

while True:
    recv_data,addr = sock.recvfrom(65536)
    sock.sendto(recv_data, addr)

客户端

#!/usr/bin/env python

from __future__ import print_function

import socket
import sys
import time
import string
import random
import signal
import sys
import os

INTERVAL = 1000  #unit ms
LEN =64
IP=""
PORT=0
SRC_PORT_RANGE=False

count=0
count_of_received=0
rtt_sum=0.0
rtt_min=99999999.0
rtt_max=0.0

def signal_handler(signal, frame):
    if count!=0 and count_of_received!=0:
        print('')
        print('--- ping statistics ---')
    if count!=0:
        print('%d packets transmitted, %d received, %.2f%% packet loss'%(count,count_of_received, (count-count_of_received)*100.0/count))
    if count_of_received!=0:
        print('rtt min/avg/max = %.2f/%.2f/%.2f ms'%(rtt_min,rtt_sum/count_of_received,rtt_max))
    os._exit(0)

def random_string(length):
    return ''.join(random.choice(string.ascii_letters+ string.digits ) for m in range(length))

def h():
    print(""" usage:""")
    print("""   this_program <dest_ip> <dest_port>""")
    print("""   this_program <dest_ip> <dest_port> "<options>" """)

    print()
    print(""" options:""")
    print("""   LEN             the length of payload, unit:byte""")
    print("""   INTERVAL        the seconds waited between sending each packet, as well as the timeout for reply packet, unit: ms""")
    print("""   SRC_PORT_RANGE  the source port range, will be stoped when end of loop""")

    print()
    print(" examples:")
    print("   ./udpclient.py 44.55.66.77 4000")
    print('   ./udpclient.py 44.55.66.77 4000 "LEN=400;INTERVAL=2000;SRC_PORT_RANGE=\'20000:30000\'"')
    print("   ./udpclient.py fe80::5400:ff:aabb:ccdd 4000")
    print()

if len(sys.argv) != 3 and len(sys.argv)!=4 :
    h()
    exit()

IP=socket.gethostbyname(sys.argv[1])
PORT=int(sys.argv[2])

is_ipv6=0;

if IP.find(":")!=-1:
    is_ipv6=1;

if len(sys.argv)==4:
    try:
        exec(sys.argv[3])
    except:
        h()
        exit()

signal.signal(signal.SIGINT, signal_handler)

print("UDPping %s via port %d with %d bytes of payload"% (IP,PORT,LEN))
sys.stdout.flush()

if SRC_PORT_RANGE:
    start, end = SRC_PORT_RANGE.split(':')
    start = int(start)
    end = int(end)

while True:
    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    if SRC_PORT_RANGE:
        if start > end:
            signal_handler(None,None)
            break
        try:
            sock.bind(('0.0.0.0', start))
        except:
            print("%s already in use" % start)
            start += 1
            continue
        start += 1
    payload= random_string(LEN)
    sock.sendto(payload.encode(), (IP, PORT))
    time_of_send=time.time()
    deadline = time.time() + INTERVAL/1000.0
    received=0
    rtt=0.0
    retrans=0
    timeout=0.5

    while True:
        if retrans >= 3:
            print("%s packet loss, retrans %s times, " % (time.strftime("%Y-%m-%d %H:%M:%S"), retrans),"%s:%s" % sock.getsockname(), "-> %s:%s" % (IP, PORT))
            sys.stdout.flush()
            break

        sock.settimeout(timeout)
        
        try:
            recv_data,addr = sock.recvfrom(65536)
            if recv_data== payload.encode() and addr[1]==PORT:
                rtt=((time.time()-time_of_send)*1000)
                print("%s:%s"%sock.getsockname(),"-> %s:%s"%(IP, PORT),"Reply from","%s:%s"%(IP,PORT),"seq=%d"%count, "time=%.2f"%(rtt),"ms")
                sys.stdout.flush()
                received=1
                break
        except socket.timeout:
            sock.sendto(payload.encode(), (IP, PORT))
            retrans+=1
            timeout=timeout*2
        except Exception as e:
            pass
    count+=    1
    if received==1:
        count_of_received+=1
        rtt_sum+=rtt
        rtt_max=max(rtt_max,rtt)
        rtt_min=min(rtt_min,rtt)
    else:
        #print("Request timed out")
        sys.stdout.flush()

    time_remaining=deadline-time.time()
    if(time_remaining>0):
        time.sleep(time_remaining)

参考项目:https://github.com/wangyu-/UDPping/

标签: none

添加新评论