需求

最近有个运维需求,需要测试客户端的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/

简介

抓包是分析网络协议、问题排查利器,tcpdump是Linux下的一款抓包工具。

分片

首先了解一个概念:MTU(最大传输单元),MTU是定义网络中报文的最大尺寸。如果报文大小超过MTU,则网络栈/网卡会自动将包拆分成多个分片进行发送,保证每个分片都小于MTU。

TCP协议会在三次握手时协商确认MSS(最大报文段长度),MSS选项是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。因此TCP报文通常不会被分片。

UDP协议面向无连接,没有协商等机制,依赖MTU来进行分片传输。当发送的数据大于MTU时,由网络栈/网卡会自动将包拆分成多个分片进行发送。

- 阅读剩余部分 -

import zipfile
import tools
import StringIO
import os


def zip_add_dir(handle, dirname):
    for root, dirs, fs in os.walk(dirname):
       for f in fs:
           if f.endswith(".py"):
               handle.write(os.path.join(root, f))

def pack():
    # toolname为项目名称,打包后会生成的文件
    toolname='ivrtool'
    buf_zip = StringIO.StringIO()
    buf_zip.write("#!/bin/env  python2\n")
                
    fzip= zipfile.ZipFile(buf_zip, "w")
    # 项目必须要有__main__.py,为执行入口
    fzip.write("__main__.py")
    # tools 为py源码目录,如有多个,需要都加入
    zip_add_dir(fzip, "tools")
    fzip.close()
    buf_zip.seek(0)
    handle=open(toolname, "w")
    while True:
        buf = buf_zip.read(1024)
        if not buf:
            break
        handle.write(buf)
    handle.close()
    buf_zip.close()
    print "%s pack success" % toolname

if __name__ == "__main__":
    pack()

我们知道在Windows下,默认文本文件的换行符为CRLF,即\r\n,人们称之为dos换行符。

这些文件在*nix(unix,Linux)系统中使用往往会报错,通常需要将dos换行符转换为*nix下能正常使用的unix换行符。

我们常常会使用dos2unix这个工具来完成转换工作,我们常常会遇到某些系统没有安装dos2unix工具,那么有没有其它工具可以实现相同效果呢?答案是有的,下面分享两个替代dos2unix的方法。

前置知识

  • dos换行符是\r\n,十六进制数值是:0D0A
  • unix换行符是\n,十六进制数值是:0A

方法一、使用sed替换法

sed -i 's/\r//g' path_to_file

sed -i 's/\x0d$//g' path_to_file

# 这两种写法都是一样的原理,都是把dos换行符\\r替换为空,即删除

方法二、cat+sed替换法

cat -vE path_to_file | sed 's/\^M\$$//g' > path_to_tmp_file
mv path_to_tmp_file path_to_file

# 这个方法是先把文件的换行符通过^M的方式显示出来,再把^M符号替换删除

方法三、vim替换法

vim path_to_file
:%s/^M//g
# 其中^M的输入方式为:先按Ctrl+V、再按Ctrl+M。这个方法只有当打开文件行尾显示^M时才有效,dos换行符和unix换行符混用的文件打开时会显示^M。

方法四、vim set ff法

vim path_to_file
:set ff=unix
:wq

# 这个是方法三的一个补充,用于纯dos文件

项目中使用到了RabbitMQ,使用了大量的一次性队列,然而没有设置自动过期、自动删除等特性。长期运行导致了大量的队列产生,非常影响性能及问题排查效率。这里收集了一些可以批量删除队列的方法,供参考。

方法一:设置过期策略

优点:操作简单,可针对有规律的队列进行策略设置
缺点:想不到有什么缺点

# 设置规则
rabbitmqctl set_policy delete_gen "amq.gen-.*" '{"expires":1}' --apply-to queues

# 取消规则
rabbitmqctl clear_policy delete_gen

# 如果要作用于所有队列
rabbitmqctl set_policy delete_all ".*" '{"expires":1}' --apply-to queues

方法二:重置数据库

优点:简单,删除全部队列
缺点:粗暴

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app

方法三:删除vhost

优点:删除一个vhost的所有队列,包括Exchange
缺点:仅适用需要删除一个vhost的场景

curl -i -XDELETE http://USERNAME:[email protected]:15672/api/vhosts/VHOST_NAME
# 例子
curl -i -XDELETE http://admin:[email protected]:15672/api/vhosts/%2F

方法四:通过HTTP API删除

优点:HTTP API灵活
缺点:一次删一个

curl -i -XDELETE http://USERNAME:PASSWORD@HOST:PORT/api/queues/VHOST/QUEUE_NAME
# 例子:
curl -i -XDELETE http://admin:[email protected]:15672/api/queues/%2F/test_queue

方法五:使用rabbitmqadmin工具

优点:使用方便
缺点:底层也是使用HTTP API实现的

rabbitmqadmin --host=HOST --port=15672 --ssl --vhost=VHOST --username=USERNAME --password=PASSWORD delete queue name=QUEUE_NAME