Py学习  »  Python

PYSCTP——基于Python的SCTP协议栈

重舟 • 6 年前 • 1041 次点击  

一 SCTP协议简介

通信领域常用的信令传输协议Sigtran(Signaling Transport Protocol)协议簇是IETF的Sigtran工作组制定的PSTN信令与IP互通的规范。该协议簇支持通过IP网络传输传统电路交换网SCN(Switched Circuit Network)信令。

Sigtran协议簇可以分为PSTN信令适配协议和通用信令传输协议两大类。前者是针对SCN中现存的各种信令协议而产生的信令适配协议,例如M2UA(No.7 MTP2-User Adaptation Layer)、M3UA(No.7 MTP3-User Adaptation Layer)以及IUA(ISDN Q.921-User Adaptation Layer)等等。

而后者通用信令传输协议便是今天介绍的主角SCTP(Stream Control Transmission Protocol,流控制传输协议),该协议的主要目的是实现PSTN信令在IP网上高效、可靠的传输。

IP网络上的传输协议大家比较熟悉的是UDP和TCP协议。UDP协议的特点是无连接的传输协议,传输质量不能保证。TCP协议的特点是有连接的传输协议,但是实时性差,无法支持多归属,容易受到DoS攻击。与之相比,SCTP协议是面向连接的基于分组的可靠实时传输协议,具有高可靠性、优实时性、多归属支持、攻击防备等多种优点。

TCP协议与SCTP协议的特性比较

二 PYSCTP简介与安装

PYSCTP是基于Python的SCTP协议栈,用于建立偶联和发送SCTP数据包。

PYSCTP的Github地址

下载以后是一个名为pysctp-master.zip的压缩文件包,文件内容如下。

pysctp-master.zip的文件内容

PYSCTP安装命令为:sudo python setup.py install

三 SCTP偶联的建立

假设需要建立SCTP偶联的服务器上的M2UA配置如下,其中XX.XX.14.109和XX.XX.15.109是服务器的本地IP地址。

然后ASP(Application Server Process)上配置的需要建立偶联的对端地址是XX.XX.217.43。这个XX.XX.217.43就是安装并计划运行PYSCTP的另一台服务器地址。

Server端的M2UA配置

在XX.XX.217.43服务器的PYSCTP安装目录下增加一个名为client.py的文件,代码内容如下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# client.py - Create SCTP association
import _sctp
from sctp import *

server = 'XX.XX.14.109'
sctpport = 2904

if _sctp.getconstant("IPPROTO_SCTP") != 132:
    raise "getconstant failed"
client_sock = sctpsocket(socket.AF_INET, socket.SOCK_STREAM, None)

saddr = (server, sctpport)

print "SCTP", saddr, " ----------------------------------------------"

client_sock.initparams.max_instreams = 3
client_sock.initparams.num_ostreams = 3
client = 'XX.XX.217.43'
cport = 2904
caddr = (client, cport)

client_sock.bindx([caddr])
client_sock.events.clear()
client_sock.events.data_io = 1
client_sock.connect(saddr)

while 1:
    while True:
        data = raw_input('INPUT: ')
        if not data:
            break
        client_sock.sctp_send(data)
        fromaddr, flags, msg, notif = client_sock.sctp_recv(1024)
        print '%s__' %msg
    break

client_sock.close()

执行client.py以后即可以与服务器端建立偶联 。

colvin@ubuntu:~/git/pysctp$ sudo python client.py

SCTP  ('XX.XX.14.109', 2904)  ----------------------------------------------

INPUT:

服务器侧可以看到有新的偶联建立 。

# vi sctp open

                Open Associations               

-------------------------------------------------

...

AssocID: 2  Prim. Destination Address: XX.XX.217.43 Port: 2904

如果需要建立偶联的服务器被配置成了clinet端,PYSCTP对应的server.py代码内容如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# server.py - Create SCTP association
import socket
import sctp
from time import ctime

HOST = ''
PORT = 3868
BUFSIZE = 1024
ADDR = (HOST, PORT)

socket_serv = sctp.sctpsocket(socket.AF_INET, socket.SOCK_STREAM, None)
socket_serv.initparams.max_instreams = 3
socket_serv.initparams.num_ostreams = 3

socket_serv.bindx([ADDR])
socket_serv.listen(5)
socket_serv.events.data_io = 1
socket_serv.events.clear()

while True:
    print
    'Waiting for user connecting'
    conn_sock, addr = socket_serv.accept()
    print
    'connecting: ', addr
    msg = ''
    while True:
        fromaddr, flags, msg, notif = conn_sock.sctp_recv(BUFSIZE)
        print
        'recv: %s' % msg
        if not msg:
            break
        conn_sock.sctp_send('current time: %s, message is %s' % (ctime(), msg))

    conn_sock.close()
socket_serv.close()

四 verification tag

通过SCTP协议传输消息时,需要保证同一偶联内部verification tag保持一致,否则会当做错包丢弃。

所以通过PYSCTP建立偶联以后,如果需要进一步验证发包功能,需要在建立偶联的时候就记录下偶联的verification tag。

colvin@ubuntu:~$ sudo tcpdump host XX.XX.14.109 -w /tmp/sunny.pcap

[sudo] password for colvin:

tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes

^C6 packets captured

6 packets received by filter

0 packets dropped by kernel

得到的抓包的COOKIE里面的verification tag就是我们需要的vTag

verification tag

将获取的verification tag保存在文件Vtag.py中供其他python脚本调用,便可以实现在同一偶联中发送SCTP消息了。

#!/usr/bin/python

# -*- coding: utf-8 -*-

#verification tag

vtag = 0x619f0836

五 发送退出偶联的Abort消息

文件abort.py的代码内容如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from scapy.all import *
from Vtag import vtag

#casue=1, invalid stream identifier
data = struct.pack('=BBHI', 0x06, 0x00, 0x0800, 0x04000700)
#(type, chunk flag, length, cause code, cause)
pkt = IP(dst="XX.XX.14.109")/SCTP(dport=2904, sport=2904, tag=vtag)/data

print pkt.dst
print pkt.ttl
print repr(pkt)
pkt.show()
send(pkt)

执行$ sudo python abort.py即可以实现发送退出偶联的Abort消息。

【说明】根据SCTP标准,ABORT数据块的格式如下图所示。

ABORT数据块格式

数据块标志位(8比特):其中高7比特备用,在发送方设置为全0,并在接收方忽略。

T比特(1比特):当发送方有一个TCB被破坏时则该T比特设置为0,如果发送方没有TCB则把该比特设置为1。

长度(16比特无符号整数):设置为该数据块的长度,包括数据块头和所有包含的差错原因字段。

data = struct.pack('=BBHI', 0x06, 0x00, 0x0800, 0x04000700)

消息体中的0x06指的是消息类型6,0x0800指的是消息长度一共8个字节。

六 发送操作差错(ERROR)数据块

【说明】根据SCTP标准,操作差错(ERROR)数据块的格式如下图所示。

ERROR数据块格式

数据块标志位(8比特):在发送方设置为全0,并在接收方忽略。

可变长度(16比特无符号整数):设置为该数据块的字节数,包括数据块头和所有包含的差错原因字段的长度。

然后对于无效的流标识符原因的差错数据块,其差错原因的格式如下。

无效的流识别符原因参数的格式

流识别符:(16比特无符号整数):包含了接收的差错的DATA数据块的流标识符

备用字段(16比特):由发送方设为全0,在接收方忽略

由此,可以得到对于无效的流标识符原因的差错数据块,其消息体编码为

data = struct.pack('=BBHHHHH', 0x09, 0x00, 0x0c00, 0x0100, 0x0800, 0x1200, 0x0000)

其中0x09是消息类型9,0x0c00是消息体长度12个字节,0x0100是差错原因编码1,0x0800是原因长度8个字节。

最终的文件1InvalidStramID.py内容如下。

#!/usr/bin/python
# -*- coding: utf-8 -*-

from scapy.all import *
import Vtag 

#casue=1, invalid stream identifier
data = struct.pack('=BBHHHHH', 0x09, 0x00, 0x0c00, 0x0100, 0x0800, 0x1200, 0x0000)
#(type, chunk flag, length, cause code, cause)
pkt = IP(dst="XX.XX.14.109")/SCTP(dport=2904, sport=2904, tag=Vtag.vtag)/data

print pkt.dst
print pkt.ttl
print repr(pkt)
pkt.show()
send(pkt)

执行$ sudo python 1InvalidStramI.py即可以实现发送无效的流标识符原因的差错数据消息。

其他原因的差错数据块可依照上例完成。

七 下一步的方向

1 建立偶联发送消息时verification tag的抓取很不方便,可以考虑用代码同步实现。

2 考虑用PyQt5对操作界面做优化,方便测试人员使用。

最后,需要感谢同事@claire对PYSCTP的研究。


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/1FGifv9mPJ
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/10515
 
1041 次点击