old htb folders
This commit is contained in:
2023-08-29 21:53:22 +02:00
parent 62ab804867
commit 82b0759f1e
21891 changed files with 6277643 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
"""fast, simple packet creation and parsing."""
from __future__ import absolute_import
from __future__ import division
import sys
__author__ = 'Various'
__author_email__ = ''
__license__ = 'BSD-3-Clause'
__url__ = 'https://github.com/kbandla/dpkt'
__version__ = '1.9.8'
from .dpkt import *
from . import ah
from . import aoe
from . import aim
from . import arp
from . import asn1
from . import bgp
from . import cdp
from . import dhcp
from . import diameter
from . import dns
from . import dtp
from . import esp
from . import ethernet
from . import gre
from . import gzip
from . import h225
from . import hsrp
from . import http
from . import http2
from . import icmp
from . import icmp6
from . import ieee80211
from . import igmp
from . import ip
from . import ip6
from . import ipx
from . import llc
from . import loopback
from . import mrt
from . import netbios
from . import netflow
from . import ntp
from . import ospf
from . import pcap
from . import pcapng
from . import pim
from . import pmap
from . import ppp
from . import pppoe
from . import qq
from . import radiotap
from . import radius
from . import rfb
from . import rip
from . import rpc
from . import rtp
from . import rx
from . import sccp
from . import sctp
from . import sip
from . import sll
from . import sll2
from . import smb
from . import ssl
from . import stp
from . import stun
from . import tcp
from . import telnet
from . import tftp
from . import tns
from . import tpkt
from . import udp
from . import vrrp
from . import yahoo
# Note: list() is used to get a copy of the dict in order to avoid
# "RuntimeError: dictionary changed size during iteration"
# exception in Python 3 caused by _mod_init() funcs that load another modules
for name, mod in list(sys.modules.items()):
if name.startswith('dpkt.') and hasattr(mod, '_mod_init'):
mod._mod_init()

View File

@@ -0,0 +1,86 @@
# $Id: ah.py 34 2007-01-28 07:54:20Z dugsong $
# -*- coding: utf-8 -*-
"""Authentication Header."""
from __future__ import absolute_import
from . import dpkt
from . import ip
class AH(dpkt.Packet):
"""Authentication Header.
The Authentication Header (AH) protocol provides data origin authentication, data integrity, and replay protection.
Attributes:
__hdr__: Header fields of AH.
auth: Authentication body.
data: Message data.
"""
__hdr__ = (
('nxt', 'B', 0),
('len', 'B', 0), # payload length
('rsvd', 'H', 0),
('spi', 'I', 0),
('seq', 'I', 0)
)
auth = b''
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
auth_len = max(4*self.len - 4, 0) # see RFC 4302, section 2.2
self.auth = self.data[:auth_len]
buf = self.data[auth_len:]
try:
self.data = ip.IP.get_proto(self.nxt)(buf)
setattr(self, self.data.__class__.__name__.lower(), self.data)
except (KeyError, dpkt.UnpackError):
self.data = buf
def __len__(self):
return self.__hdr_len__ + len(self.auth) + len(self.data)
def __bytes__(self):
return self.pack_hdr() + bytes(self.auth) + bytes(self.data)
def test_default_creation():
ah = AH()
assert ah.nxt == 0
assert ah.len == 0
assert ah.rsvd == 0
assert ah.spi == 0
assert ah.seq == 0
assert len(ah) == ah.__hdr_len__
assert bytes(ah) == b'\x00' * 12
def test_creation_from_buf():
from binascii import unhexlify
buf_ip = unhexlify(
'04' # IP
'0000000000000000000000'
'4500002200000000401172c001020304'
'01020304006f00de000ebf35666f6f626172'
)
ah = AH(buf_ip)
assert ah.nxt == 4 # IP
assert isinstance(ah.data, ip.IP)
assert len(ah) == 46
assert bytes(ah) == buf_ip
buf_not_ip = unhexlify(
'37' # Not registered
'0000000000000000000000'
'4500002200000000401172c001020304'
'01020304006f00de000ebf35666f6f626172'
)
ah_not_ip = AH(buf_not_ip)
assert ah_not_ip.nxt == 0x37
assert isinstance(ah_not_ip.data, bytes)
assert len(ah_not_ip) == 46
assert bytes(ah_not_ip) == buf_not_ip

View File

@@ -0,0 +1,159 @@
# $Id: aim.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""AOL Instant Messenger."""
from __future__ import absolute_import
import struct
from . import dpkt
# OSCAR: http://iserverd1.khstu.ru/oscar/
class FLAP(dpkt.Packet):
"""Frame Layer Protocol.
See more about the FLAP on
https://en.wikipedia.org/wiki/OSCAR_protocol#FLAP_header
Attributes:
__hdr__: Header fields of FLAP.
data: Message data.
"""
__hdr__ = (
('ast', 'B', 0x2a), # '*'
('type', 'B', 0),
('seq', 'H', 0),
('len', 'H', 0)
)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
if self.ast != 0x2a:
raise dpkt.UnpackError('invalid FLAP header')
if len(self.data) < self.len:
raise dpkt.NeedData('%d left, %d needed' % (len(self.data), self.len))
class SNAC(dpkt.Packet):
"""Simple Network Atomic Communication.
See more about the SNAC on
https://en.wikipedia.org/wiki/OSCAR_protocol#SNAC_data
Attributes:
__hdr__: Header fields of SNAC.
"""
__hdr__ = (
('family', 'H', 0),
('subtype', 'H', 0),
('flags', 'H', 0),
('reqid', 'I', 0)
)
def tlv(buf):
n = 4
try:
t, l_ = struct.unpack('>HH', buf[:n])
except struct.error:
raise dpkt.UnpackError('invalid type, length fields')
v = buf[n:n + l_]
if len(v) < l_:
raise dpkt.NeedData('%d left, %d needed' % (len(v), l_))
buf = buf[n + l_:]
return t, l_, v, buf
# TOC 1.0: http://jamwt.com/Py-TOC/PROTOCOL
# TOC 2.0: http://www.firestuff.org/projects/firetalk/doc/toc2.txt
def testAIM():
testdata = (
b'\x2a\x02\xac\xf3\x00\x81\x00\x03\x00\x0b\x00\x00\xfa\x45\x55\x64\x0e\x75\x73\x72\x6e\x61'
b'\x6d\x65\x72\x65\x6d\x6f\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00\x02\x12\x90\x00\x44\x00'
b'\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f\x93\x00\x21\x00\x08'
b'\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00\x04\x00\x00\x00\x00\x00'
b'\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02\x01\xd2\x04\x72\x00\x01'
b'\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00'
b'\x00\x13\xf1'
)
flap = FLAP(testdata)
assert flap.ast == 0x2a
assert flap.type == 0x02
assert flap.seq == 44275
assert flap.len == 129
assert flap.data == (
b'\x00\x03\x00\x0b\x00\x00\xfa\x45\x55\x64\x0e\x75\x73\x72\x6e\x61\x6d\x65\x72\x65\x6d\x6f'
b'\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00\x02\x12\x90\x00\x44\x00\x01\x00\x00\x03\x00\x04'
b'\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d'
b'\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00'
b'\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04'
b'\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
)
snac = SNAC(flap.data)
assert snac.family == 3
assert snac.subtype == 11
assert snac.flags == 0
assert snac.reqid == 0xfa455564
assert snac.data == (
b'\x0e\x75\x73\x72\x6e\x61\x6d\x65\x72\x65\x6d\x6f\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00'
b'\x02\x12\x90\x00\x44\x00\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00'
b'\x0f\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00'
b'\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02'
b'\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc'
b'\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
)
# skip over the buddyname and TLV count in Oncoming Buddy message
tlvdata = snac.data[19:]
tlvCount = 0
while tlvdata:
t, l_, v, tlvdata = tlv(tlvdata)
tlvCount += 1
if tlvCount == 1:
# just check function return for first TLV
assert t == 0x01
assert l_ == 2
assert v == b'\x12\x90'
assert tlvdata == (
b'\x00\x44\x00\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f'
b'\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37'
b'\x00\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00'
b'\x00\x05\x02\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05'
b'\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
)
# make sure we extracted 10 TLVs
assert tlvCount == 10
def testExceptions():
testdata = b'xxxxxx'
try:
FLAP(testdata)
except dpkt.UnpackError as e:
assert str(e) == 'invalid FLAP header'
testdata = b'*\x02\x12\x34\x00\xff'
try:
FLAP(testdata)
except dpkt.NeedData as e:
assert str(e) == '0 left, 255 needed'
try:
t, l_, v, _ = tlv(b'x')
except dpkt.UnpackError as e:
assert str(e) == 'invalid type, length fields'
try:
t, l_, v, _ = tlv(b'\x00\x01\x00\xff')
except dpkt.NeedData as e:
assert str(e) == '0 left, 255 needed'

View File

@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
"""ATA over Ethernet Protocol."""
from __future__ import absolute_import
import struct
from . import dpkt
from .compat import iteritems
class AOE(dpkt.Packet):
"""ATA over Ethernet Protocol.
See more about the AOE on
https://en.wikipedia.org/wiki/ATA_over_Ethernet
Attributes:
__hdr__: Header fields of AOE.
data: Message data.
"""
__hdr__ = (
('_ver_fl', 'B', 0x10),
('err', 'B', 0),
('maj', 'H', 0),
('min', 'B', 0),
('cmd', 'B', 0),
('tag', 'I', 0),
)
__bit_fields__ = {
'_ver_fl': (
('ver', 4),
('fl', 4),
)
}
_cmdsw = {}
@classmethod
def set_cmd(cls, cmd, pktclass):
cls._cmdsw[cmd] = pktclass
@classmethod
def get_cmd(cls, cmd):
return cls._cmdsw[cmd]
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
try:
self.data = self._cmdsw[self.cmd](self.data)
setattr(self, self.data.__class__.__name__.lower(), self.data)
except (KeyError, struct.error, dpkt.UnpackError):
pass
AOE_CMD_ATA = 0
AOE_CMD_CFG = 1
AOE_FLAG_RSP = 1 << 3
def _load_cmds():
prefix = 'AOE_CMD_'
g = globals()
for k, v in iteritems(g):
if k.startswith(prefix):
name = 'aoe' + k[len(prefix):].lower()
try:
mod = __import__(name, g, level=1)
AOE.set_cmd(v, getattr(mod, name.upper()))
except (ImportError, AttributeError):
continue
def _mod_init():
"""Post-initialization called when all dpkt modules are fully loaded"""
if not AOE._cmdsw:
_load_cmds()
def test_creation():
aoe = AOE()
# hdr fields
assert aoe._ver_fl == 0x10
assert aoe.err == 0
assert aoe.maj == 0
assert aoe.min == 0
assert aoe.cmd == 0
assert aoe.tag == 0
assert bytes(aoe) == b'\x10' + b'\x00' * 9
def test_properties():
aoe = AOE()
# property getters
assert aoe.ver == 1
assert aoe.fl == 0
# property setters
aoe.ver = 2
assert aoe.ver == 2
assert aoe._ver_fl == 0x20
aoe.fl = 12
assert aoe.fl == 12
assert aoe._ver_fl == 0x2C
def test_unpack():
from binascii import unhexlify
buf = unhexlify(
'1000000000'
'00' # cmd: AOE_CMD_ATA
'00000000' # tag
)
aoe = AOE(buf)
# AOE_CMD_ATA specified, but no data supplied
assert aoe.data == b''
buf = unhexlify(
'1000000000'
'00' # cmd: AOE_CMD_ATA
'00000000' # tag
# AOEDATA specification
'030a6b190000000045000028941f0000e30699b4232b2400de8e8442abd100500035e1'
'2920d9000000229bf0e204656b'
)
aoe = AOE(buf)
assert aoe.aoeata == aoe.data
def test_cmds():
import dpkt
assert AOE.get_cmd(AOE_CMD_ATA) == dpkt.aoeata.AOEATA
assert AOE.get_cmd(AOE_CMD_CFG) == dpkt.aoecfg.AOECFG
def test_cmd_loading():
# this test checks that failing to load a module isn't catastrophic
standard_cmds = AOE._cmdsw
# delete the existing code->module mappings
AOE._cmdsw = {}
assert not AOE._cmdsw
# create a new global constant pointing to a module which doesn't exist
globals()['AOE_CMD_FAIL'] = "FAIL"
_mod_init()
# check that the same modules were loaded, ignoring the fail
assert AOE._cmdsw == standard_cmds

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
"""ATA over Ethernet ATA command"""
from __future__ import print_function
from __future__ import absolute_import
from . import dpkt
ATA_DEVICE_IDENTIFY = 0xec
class AOEATA(dpkt.Packet):
"""ATA over Ethernet ATA command.
See more about the AOEATA on
https://en.wikipedia.org/wiki/ATA_over_Ethernet
Attributes:
__hdr__: Header fields of AOEATA.
data: Message data.
"""
__hdr__ = (
('aflags', 'B', 0),
('errfeat', 'B', 0),
('scnt', 'B', 0),
('cmdstat', 'B', ATA_DEVICE_IDENTIFY),
('lba0', 'B', 0),
('lba1', 'B', 0),
('lba2', 'B', 0),
('lba3', 'B', 0),
('lba4', 'B', 0),
('lba5', 'B', 0),
('res', 'H', 0),
)
# XXX: in unpack, switch on ATA command like icmp does on type
def test_aoeata():
s = (b'\x03\x0a\x6b\x19\x00\x00\x00\x00\x45\x00\x00\x28\x94\x1f\x00\x00\xe3\x06\x99\xb4\x23\x2b'
b'\x24\x00\xde\x8e\x84\x42\xab\xd1\x00\x50\x00\x35\xe1\x29\x20\xd9\x00\x00\x00\x22\x9b\xf0\xe2\x04\x65\x6b')
aoeata = AOEATA(s)
assert (bytes(aoeata) == s)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""ATA over Ethernet ATA command"""
from __future__ import print_function
from __future__ import absolute_import
from . import dpkt
class AOECFG(dpkt.Packet):
"""ATA over Ethernet ATA command.
See more about the AOE on \
https://en.wikipedia.org/wiki/ATA_over_Ethernet
Attributes:
__hdr__: Header fields of AOECFG.
data: Message data.
"""
__hdr__ = (
('bufcnt', 'H', 0),
('fwver', 'H', 0),
('scnt', 'B', 0),
('aoeccmd', 'B', 0),
('cslen', 'H', 0),
)
def test_aoecfg():
s = (b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16\x88\xa2\x10\x00\x00\x01\x02\x01\x80'
b'\x00\x00\x00\x12\x34\x00\x00\x00\x00\x04\x00' + b'\0xed' * 1024)
aoecfg = AOECFG(s[14 + 10:])
assert (aoecfg.bufcnt == 0x1234)

View File

@@ -0,0 +1,42 @@
# $Id: arp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Address Resolution Protocol."""
from __future__ import absolute_import
from . import dpkt
# Hardware address format
ARP_HRD_ETH = 0x0001 # ethernet hardware
ARP_HRD_IEEE802 = 0x0006 # IEEE 802 hardware
# Protocol address format
ARP_PRO_IP = 0x0800 # IP protocol
# ARP operation
ARP_OP_REQUEST = 1 # request to resolve ha given pa
ARP_OP_REPLY = 2 # response giving hardware address
ARP_OP_REVREQUEST = 3 # request to resolve pa given ha
ARP_OP_REVREPLY = 4 # response giving protocol address
class ARP(dpkt.Packet):
"""Address Resolution Protocol.
See more about the ARP on \
https://en.wikipedia.org/wiki/Address_Resolution_Protocol
Attributes:
__hdr__: Header fields of ARP.
"""
__hdr__ = (
('hrd', 'H', ARP_HRD_ETH),
('pro', 'H', ARP_PRO_IP),
('hln', 'B', 6), # hardware address length
('pln', 'B', 4), # protocol address length
('op', 'H', ARP_OP_REQUEST),
('sha', '6s', b''),
('spa', '4s', b''),
('tha', '6s', b''),
('tpa', '4s', b'')
)

View File

@@ -0,0 +1,311 @@
# $Id: asn1.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Abstract Syntax Notation #1."""
from __future__ import absolute_import
from __future__ import print_function
import struct
from calendar import timegm
from . import dpkt
from .compat import compat_ord
# Type class
CLASSMASK = 0xc0
UNIVERSAL = 0x00
APPLICATION = 0x40
CONTEXT = 0x80
PRIVATE = 0xc0
# Constructed (vs. primitive)
CONSTRUCTED = 0x20
# Universal-class tags
TAGMASK = 0x1f
INTEGER = 2
BIT_STRING = 3 # arbitrary bit string
OCTET_STRING = 4 # arbitrary octet string
NULL = 5
OID = 6 # object identifier
SEQUENCE = 16 # ordered collection of types
SET = 17 # unordered collection of types
PRINT_STRING = 19 # printable string
T61_STRING = 20 # T.61 (8-bit) character string
IA5_STRING = 22 # ASCII
UTC_TIME = 23
def utctime(buf):
"""Convert ASN.1 UTCTime string to UTC float.
TODO: Long description here.
Args:
buf: A buffer with format "yymnddhhmm"
Returns:
A floating point number, indicates seconds since the Epoch.
"""
yy = int(buf[:2])
mn = int(buf[2:4])
dd = int(buf[4:6])
hh = int(buf[6:8])
mm = int(buf[8:10])
try:
ss = int(buf[10:12])
buf = buf[12:]
except TypeError:
ss = 0
buf = buf[10:]
if buf[0] == '+':
hh -= int(buf[1:3])
mm -= int(buf[3:5])
elif buf[0] == '-':
hh += int(buf[1:3])
mm += int(buf[3:5])
return timegm((2000 + yy, mn, dd, hh, mm, ss, 0, 0, 0))
def decode(buf):
"""Sleazy ASN.1 decoder.
TODO: Long description here.
Args:
buf: A buffer with Sleazy ASN.1 data.
Returns:
A list of (id, value) tuples from ASN.1 BER/DER encoded buffer.
Raises:
UnpackError: An error occurred the ASN.1 length exceed.
"""
msg = []
while buf:
t = compat_ord(buf[0])
constructed = t & CONSTRUCTED
tag = t & TAGMASK
l_ = compat_ord(buf[1])
c = 0
if constructed and l_ == 128:
# XXX - constructed, indefinite length
msg.append((t, decode(buf[2:])))
elif l_ >= 128:
c = l_ & 127
if c == 1:
l_ = compat_ord(buf[2])
elif c == 2:
l_ = struct.unpack('>H', buf[2:4])[0]
elif c == 3:
l_ = struct.unpack('>I', buf[1:5])[0] & 0xfff
c = 2
elif c == 4:
l_ = struct.unpack('>I', buf[2:6])[0]
else:
# XXX - can be up to 127 bytes, but...
raise dpkt.UnpackError('excessive long-form ASN.1 length %d' % l_)
# Skip type, length
buf = buf[2 + c:]
# Parse content
if constructed:
msg.append((t, decode(buf)))
elif tag == INTEGER:
if l_ == 0:
n = 0
elif l_ == 1:
n = compat_ord(buf[0])
elif l_ == 2:
n = struct.unpack('>H', buf[:2])[0]
elif l_ == 3:
n = struct.unpack('>I', buf[:4])[0] >> 8
elif l_ == 4:
n = struct.unpack('>I', buf[:4])[0]
else:
raise dpkt.UnpackError('excessive integer length > %d bytes' % l_)
msg.append((t, n))
elif tag == UTC_TIME:
msg.append((t, utctime(buf[:l_])))
else:
msg.append((t, buf[:l_]))
# Skip content
buf = buf[l_:]
return msg
def test_asn1():
s = (
b'0\x82\x02Q\x02\x01\x0bc\x82\x02J\x04xcn=Douglas J Song 1, ou=Information Technology Division,'
b' ou=Faculty and Staff, ou=People, o=University of Michigan, c=US\n\x01\x00\n\x01\x03\x02\x01'
b'\x00\x02\x01\x00\x01\x01\x00\x87\x0bobjectclass0\x82\x01\xb0\x04\rmemberOfGroup\x04\x03acl'
b'\x04\x02cn\x04\x05title\x04\rpostalAddress\x04\x0ftelephoneNumber\x04\x04mail\x04\x06member'
b'\x04\thomePhone\x04\x11homePostalAddress\x04\x0bobjectClass\x04\x0bdescription\x04\x18'
b'facsimileTelephoneNumber\x04\x05pager\x04\x03uid\x04\x0cuserPassword\x04\x08joinable\x04\x10'
b'associatedDomain\x04\x05owner\x04\x0erfc822ErrorsTo\x04\x08ErrorsTo\x04\x10rfc822RequestsTo\x04\n'
b'RequestsTo\x04\tmoderator\x04\nlabeledURL\x04\nonVacation\x04\x0fvacationMessage\x04\x05drink\x04\x0e'
b'lastModifiedBy\x04\x10lastModifiedTime\x04\rmodifiersname\x04\x0fmodifytimestamp\x04\x0ccreatorsname'
b'\x04\x0fcreatetimestamp'
)
assert decode(s) == [
(48, [
(2, 11),
(99, [
(4, (
b'cn=Douglas J Song 1, '
b'ou=Information Technology Division, '
b'ou=Faculty and Staff, '
b'ou=People, '
b'o=University of Michigan, '
b'c=US'
)),
(10, b'\x00'),
(10, b'\x03'),
(2, 0),
(2, 0),
(1, b'\x00'),
(135, b'objectclass'),
(48, [
(4, b'memberOfGroup'),
(4, b'acl'),
(4, b'cn'),
(4, b'title'),
(4, b'postalAddress'),
(4, b'telephoneNumber'),
(4, b'mail'),
(4, b'member'),
(4, b'homePhone'),
(4, b'homePostalAddress'),
(4, b'objectClass'),
(4, b'description'),
(4, b'facsimileTelephoneNumber'),
(4, b'pager'),
(4, b'uid'),
(4, b'userPassword'),
(4, b'joinable'),
(4, b'associatedDomain'),
(4, b'owner'),
(4, b'rfc822ErrorsTo'),
(4, b'ErrorsTo'),
(4, b'rfc822RequestsTo'),
(4, b'RequestsTo'),
(4, b'moderator'),
(4, b'labeledURL'),
(4, b'onVacation'),
(4, b'vacationMessage'),
(4, b'drink'),
(4, b'lastModifiedBy'),
(4, b'lastModifiedTime'),
(4, b'modifiersname'),
(4, b'modifytimestamp'),
(4, b'creatorsname'),
(4, b'createtimestamp'),
])
])
])
]
def test_utctime():
buf = (
'201005' # yymndd
'012345' # hhmmss
'+1234' # +hhmm
)
assert utctime(buf) == 1601815785.0
buf = (
'201005' # yymndd
'012345' # hhmmss
'-1234' # -hhmm
)
assert utctime(buf) == 1601906265.0
def test_decode():
import pytest
from binascii import unhexlify
buf = unhexlify(
'20' # CONSTRUCTED
'80' # 128 | 0
)
assert decode(buf) == [(32, []), (32, [])]
# unpacking UTC_TIME
buf = unhexlify(
'17' # t: code: UTC_TIME
'81' # l_: code: 128 | 1 (constructed
'22' # data len
'3230313030353031323334352b30303030'
)
assert decode(buf) == [(23, 1601861025.0)]
# unpacking 2-byte size; zero-length integer
buf = unhexlify(
'02' # t: INTEGER
'82' # l_: 128 | 2
'0000' # new l_
)
assert decode(buf) == [(2, 0)]
# unpacking 3-byte size
buf = unhexlify(
'02' # t: INTEGER
'83' # l_: 128 | 3
'000001' # new l_
)
assert decode(buf) == [(2, 1)]
# unpacking 4-byte size
buf = unhexlify(
'02' # t: INTEGER
'84' # l_: 128 | 4
'00000002' # new l_
'abcd'
)
assert decode(buf) == [(2, 43981)]
# unpacking 4-byte size
buf = unhexlify(
'02' # t: INTEGER
'85' # l_: 128 | 5
)
with pytest.raises(dpkt.UnpackError, match="excessive long-form ASN.1 length 133"):
decode(buf)
# unpacking 1-byte size; 4-byte integer
buf = unhexlify(
'02' # t: INTEGER
'81' # l_: 128 | 1
'04' # new l_
'12345678' # integer
)
assert decode(buf) == [(2, 305419896)]
# unpacking 1-byte size; 4-byte integer
buf = unhexlify(
'02' # t: INTEGER
'81' # l_: 128 | 1
'05' # new l_
)
with pytest.raises(dpkt.UnpackError, match="excessive integer length > 5 bytes"):
decode(buf)
# unpacking 1-byte size; 3-byte integer
buf = unhexlify(
'02' # t: INTEGER
'81' # l_: 128 | 1
'03' # new l_
'123456' # integer
'02' # t: INTEGER
'81' # l_: 128 | 1
'00' # new l_
)
assert decode(buf) == [
(2, 1193046),
(2, 0),
]

View File

@@ -0,0 +1,202 @@
# $Id: cdp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Cisco Discovery Protocol."""
from __future__ import absolute_import
from . import dpkt
CDP_DEVID = 1 # string
CDP_ADDRESS = 2
CDP_PORTID = 3 # string
CDP_CAPABILITIES = 4 # 32-bit bitmask
CDP_VERSION = 5 # string
CDP_PLATFORM = 6 # string
CDP_IPPREFIX = 7
CDP_VTP_MGMT_DOMAIN = 9 # string
CDP_NATIVE_VLAN = 10 # 16-bit integer
CDP_DUPLEX = 11 # 8-bit boolean
CDP_TRUST_BITMAP = 18 # 8-bit bitmask0x13
CDP_UNTRUST_COS = 19 # 8-bit port
CDP_SYSTEM_NAME = 20 # string
CDP_SYSTEM_OID = 21 # 10-byte binary string
CDP_MGMT_ADDRESS = 22 # 32-bit number of addrs, Addresses
CDP_LOCATION = 23 # string
class CDP(dpkt.Packet):
"""Cisco Discovery Protocol.
Cisco Discovery Protocol (CDP) is a proprietary Data Link Layer protocol developed by Cisco Systems in 1994
by Keith McCloghrie and Dino Farinacci. It is used to share information about other directly connected
Cisco equipment, such as the operating system version and IP address.
See more on
https://en.wikipedia.org/wiki/Cisco_Discovery_Protocol
Attributes:
__hdr__: Header fields of CDP.
version: (int): CDP protocol version. (1 byte)
ttl: (int): Time to live. The amount of time in seconds that a receiver should retain the information
contained in this packet. (1 byte)
sum: (int): Checksum. (2 bytes)
"""
__hdr__ = (
('version', 'B', 2),
('ttl', 'B', 180),
('sum', 'H', 0)
)
class TLV(dpkt.Packet):
"""Typelengthvalue
When constructing the packet, len is not mandatory:
if not provided, then self.data must be this exact TLV payload
Attributes:
__hdr__: Header fields of TLV.
type: (int): Type (2 bytes)
len: (int): The total length in bytes of the Type, Length and Data fields. (2 bytes)
"""
__hdr__ = (
('type', 'H', 0),
('len', 'H', 0)
)
def data_len(self):
if self.len:
return self.len - self.__hdr_len__
return len(self.data)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.data = self.data[:self.data_len()]
def __len__(self):
return self.__hdr_len__ + len(self.data)
def __bytes__(self):
if hasattr(self, 'len') and not self.len:
self.len = len(self)
return self.pack_hdr() + bytes(self.data)
class Address(TLV):
# XXX - only handle NLPID/IP for now
__hdr__ = (
('ptype', 'B', 1), # protocol type (NLPID)
('plen', 'B', 1), # protocol length
('p', 'B', 0xcc), # IP
('alen', 'H', 4) # address length
)
def data_len(self):
return self.alen
class TLV_Addresses(TLV):
__hdr__ = (
('type', 'H', CDP_ADDRESS),
('len', 'H', 0), # 17),
('Addresses', 'L', 1),
)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
buf = self.data
l_ = []
while buf:
# find the right TLV according to Type value
tlv_find_type = self.TLV(buf).type
# if this TLV is not in tlv_types, use the default TLV class
tlv = self.tlv_types.get(tlv_find_type, self.TLV)(buf)
l_.append(bytes(tlv))
buf = buf[len(tlv):]
self.tlvs = l_
self.data = b''.join(l_)
def __len__(self):
return self.__hdr_len__ + len(self.data)
def __bytes__(self):
data = bytes(self.data)
if not self.sum:
self.sum = dpkt.in_cksum(self.pack_hdr() + data)
return self.pack_hdr() + data
# keep here the TLV classes whose header is different from the generic TLV header (example : TLV_Addresses)
tlv_types = {CDP_ADDRESS: TLV_Addresses}
def test_cdp():
import socket
from . import ethernet
ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
rr1 = CDP(ss)
assert bytes(rr1) == ss
# construction
ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
p1 = CDP.TLV_Addresses(data=CDP.Address(data=socket.inet_aton('192.168.1.103')))
p2 = CDP.TLV(type=CDP_DEVID, data=b'cisco')
data = p2.pack() + p1.pack()
rr2 = CDP(data=data)
assert bytes(rr2) == ss
s = (b'\x01\x00\x0c\xcc\xcc\xcc\xc4\x022k\x00\x00\x01T\xaa\xaa\x03\x00\x00\x0c \x00\x02\xb4,B'
b'\x00\x01\x00\x06R2\x00\x05\x00\xffCisco IOS Software, 3700 Software (C3745-ADVENTERPRI'
b'SEK9_SNA-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.'
b'cisco.com/techsupport\nCopyright (c) 1986-2010 by Cisco Systems, Inc.\nCompiled Wed 18'
b'-Aug-10 08:18 by prod_rel_team\x00\x06\x00\x0eCisco 3745\x00\x02\x00\x11\x00\x00\x00\x01'
b'\x01\x01\xcc\x00\x04\n\x00\x00\x02\x00\x03\x00\x13FastEthernet0/0\x00\x04\x00\x08\x00'
b'\x00\x00)\x00\t\x00\x04\x00\x0b\x00\x05\x00')
eth = ethernet.Ethernet(s)
assert isinstance(eth.data.data, CDP)
assert len(eth.data.data.tlvs) == 8 # number of CDP TLVs; ensures they are decoded
assert str(eth) == str(s)
assert len(eth) == len(s)
def test_tlv():
from binascii import unhexlify
# len field set to 0
buf_no_len = unhexlify(
'0000' # type
'0000' # len
'abcd' # data
)
buf_with_len = unhexlify(
'0000' # type
'0006' # len
'abcd' # data
)
tlv = CDP.TLV(buf_no_len)
assert tlv.type == 0
assert tlv.len == 0
assert tlv.data_len() == 2
assert tlv.data == b'\xab\xcd'
assert bytes(tlv) == buf_with_len
# len field set manually
tlv = CDP.TLV(buf_with_len)
assert tlv.type == 0
assert tlv.len == 6
assert tlv.data_len() == 2
assert tlv.data == b'\xab\xcd'
assert bytes(tlv) == buf_with_len
def test_address():
from binascii import unhexlify
buf = unhexlify(
'00' # ptype
'11' # plen
'22' # p
'3333' # alen
)
address = CDP.Address(buf)
assert address.data_len() == 0x3333

View File

@@ -0,0 +1,60 @@
from __future__ import absolute_import
from struct import pack, unpack
import sys
if sys.version_info < (3,):
compat_ord = ord
else:
def compat_ord(char):
return char
try:
from itertools import izip
compat_izip = izip
except ImportError:
compat_izip = zip
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
try:
from BytesIO import BytesIO
except ImportError:
from io import BytesIO
if sys.version_info < (3,):
def iteritems(d, **kw):
return d.iteritems(**kw)
def intround(num):
return int(round(num))
else:
def iteritems(d, **kw):
return iter(d.items(**kw))
# python3 will return an int if you round to 0 decimal places
intround = round
def ntole(v):
"""convert a 2-byte word from the network byte order (big endian) to little endian;
replaces socket.ntohs() to work on both little and big endian architectures
"""
return unpack('<H', pack('!H', v))[0]
def ntole64(v):
"""
Convert an 8-byte word from network byte order (big endian) to little endian.
"""
return unpack('<Q', pack('!Q', v))[0]
def isstr(s):
"""True if 's' is an instance of basestring in py2, or of str in py3"""
bs = getattr(__builtins__, 'basestring', str)
return isinstance(s, bs)

View File

@@ -0,0 +1,99 @@
# $Id: crc32c.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import array
# CRC-32C Checksum for SCTP
# http://tools.ietf.org/html/rfc3309
crc32c_table = (
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F,
0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC,
0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27,
0x5E133C24, 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0,
0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC,
0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29,
0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E,
0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2,
0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59,
0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC,
0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0,
0x67DAFA54, 0x95B17957, 0xCBA24573, 0x39C9C670, 0x2A993584,
0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC,
0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F,
0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4,
0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD,
0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1,
0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA,
0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD,
0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B,
0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90,
0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, 0x92A8FC17,
0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B,
0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F,
0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9,
0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A,
0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81,
0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06,
0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A,
0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633,
0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914,
0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8,
0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643,
0x07198540, 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A,
0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06,
0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, 0x88D28022,
0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A,
0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9,
0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052,
0xAD7D5351
)
def add(crc, buf):
byte_buf = array.array('B', buf)
for b in byte_buf:
crc = (crc >> 8) ^ crc32c_table[(crc ^ b) & 0xff]
return crc
def done(crc):
tmp = ~crc & 0xffffffff
b0 = tmp & 0xff
b1 = (tmp >> 8) & 0xff
b2 = (tmp >> 16) & 0xff
b3 = (tmp >> 24) & 0xff
crc = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
return crc
def cksum(buf):
"""Return computed CRC-32c checksum."""
return done(add(0xffffffff, buf))
def test_crc32c():
def bswap32(x):
from struct import pack, unpack
return unpack('<I', pack('>I', x))[0]
# reference test value from CRC catalogue
# http://reveng.sourceforge.net/crc-catalogue/17plus.htm#crc.cat.crc-32c
# SCTP uses tranport-level mirrored byte ordering, so we bswap32
assert cksum(b'') == 0
assert cksum(b'123456789') == bswap32(0xe3069283)

View File

@@ -0,0 +1,264 @@
# $Id: dhcp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Dynamic Host Configuration Protocol."""
from __future__ import print_function
from __future__ import absolute_import
import struct
from . import arp
from . import dpkt
from .compat import compat_ord
DHCP_OP_REQUEST = 1
DHCP_OP_REPLY = 2
DHCP_MAGIC = 0x63825363
# DHCP option codes
DHCP_OPT_NETMASK = 1 # I: subnet mask
DHCP_OPT_TIMEOFFSET = 2
DHCP_OPT_ROUTER = 3 # s: list of router ips
DHCP_OPT_TIMESERVER = 4
DHCP_OPT_NAMESERVER = 5
DHCP_OPT_DNS_SVRS = 6 # s: list of DNS servers
DHCP_OPT_LOGSERV = 7
DHCP_OPT_COOKIESERV = 8
DHCP_OPT_LPRSERV = 9
DHCP_OPT_IMPSERV = 10
DHCP_OPT_RESSERV = 11
DHCP_OPT_HOSTNAME = 12 # s: client hostname
DHCP_OPT_BOOTFILESIZE = 13
DHCP_OPT_DUMPFILE = 14
DHCP_OPT_DOMAIN = 15 # s: domain name
DHCP_OPT_SWAPSERV = 16
DHCP_OPT_ROOTPATH = 17
DHCP_OPT_EXTENPATH = 18
DHCP_OPT_IPFORWARD = 19
DHCP_OPT_SRCROUTE = 20
DHCP_OPT_POLICYFILTER = 21
DHCP_OPT_MAXASMSIZE = 22
DHCP_OPT_IPTTL = 23
DHCP_OPT_MTUTIMEOUT = 24
DHCP_OPT_MTUTABLE = 25
DHCP_OPT_MTUSIZE = 26
DHCP_OPT_LOCALSUBNETS = 27
DHCP_OPT_BROADCASTADDR = 28
DHCP_OPT_DOMASKDISCOV = 29
DHCP_OPT_MASKSUPPLY = 30
DHCP_OPT_DOROUTEDISC = 31
DHCP_OPT_ROUTERSOLICIT = 32
DHCP_OPT_STATICROUTE = 33
DHCP_OPT_TRAILERENCAP = 34
DHCP_OPT_ARPTIMEOUT = 35
DHCP_OPT_ETHERENCAP = 36
DHCP_OPT_TCPTTL = 37
DHCP_OPT_TCPKEEPALIVE = 38
DHCP_OPT_TCPALIVEGARBAGE = 39
DHCP_OPT_NISDOMAIN = 40
DHCP_OPT_NISSERVERS = 41
DHCP_OPT_NISTIMESERV = 42
DHCP_OPT_VENDSPECIFIC = 43
DHCP_OPT_NBNS = 44
DHCP_OPT_NBDD = 45
DHCP_OPT_NBTCPIP = 46
DHCP_OPT_NBTCPSCOPE = 47
DHCP_OPT_XFONT = 48
DHCP_OPT_XDISPLAYMGR = 49
DHCP_OPT_REQ_IP = 50 # I: IP address
DHCP_OPT_LEASE_SEC = 51 # I: lease seconds
DHCP_OPT_OPTIONOVERLOAD = 52
DHCP_OPT_MSGTYPE = 53 # B: message type
DHCP_OPT_SERVER_ID = 54 # I: server IP address
DHCP_OPT_PARAM_REQ = 55 # s: list of option codes
DHCP_OPT_MESSAGE = 56
DHCP_OPT_MAXMSGSIZE = 57
DHCP_OPT_RENEWTIME = 58
DHCP_OPT_REBINDTIME = 59
DHCP_OPT_VENDOR_ID = 60 # s: vendor class id
DHCP_OPT_CLIENT_ID = 61 # Bs: idtype, id (idtype 0: FQDN, idtype 1: MAC)
DHCP_OPT_NISPLUSDOMAIN = 64
DHCP_OPT_NISPLUSSERVERS = 65
DHCP_OPT_MOBILEIPAGENT = 68
DHCP_OPT_SMTPSERVER = 69
DHCP_OPT_POP3SERVER = 70
DHCP_OPT_NNTPSERVER = 71
DHCP_OPT_WWWSERVER = 72
DHCP_OPT_FINGERSERVER = 73
DHCP_OPT_IRCSERVER = 74
DHCP_OPT_STSERVER = 75
DHCP_OPT_STDASERVER = 76
# DHCP message type values
DHCPDISCOVER = 1
DHCPOFFER = 2
DHCPREQUEST = 3
DHCPDECLINE = 4
DHCPACK = 5
DHCPNAK = 6
DHCPRELEASE = 7
DHCPINFORM = 8
class DHCP(dpkt.Packet):
"""Dynamic Host Configuration Protocol.
The Dynamic Host Configuration Protocol (DHCP) is a network management protocol used on Internet Protocol (IP)
networks for automatically assigning IP addresses and other communication parameters to devices connected
to the network using a clientserver architecture.
Attributes:
__hdr__: Header fields of DHCP.
op: (int): Operation. Message op code / message type. 1 = BOOTREQUEST, 2 = BOOTREPLY. (1 byte)
hrd: (int): Hardware type. Hardware address type, see ARP section in "Assigned
Numbers" RFC; e.g., '1' = 10mb ethernet. (1 byte)
hln: (int): Hardware Length. Hardware address length (e.g. '6' for 10mb
ethernet). (1 byte)
hops: (int): Hops. Client sets to zero, optionally used by relay agents
when booting via a relay agent. (1 byte)
xid: (int): Transaction ID. A random number chosen by the
client, used by the client and server to associate
messages and responses between a client and a
server. (4 bytes)
secs: (int): Seconds. Filled in by client, seconds elapsed since client
began address acquisition or renewal process. (2 bytes)
flags: (int): DHCP Flags. (2 bytes)
ciaddr: (int): Client IP address. Only filled in if client is in
BOUND, RENEW or REBINDING state and can respond
to ARP requests. (4 bytes)
yiaddr: (int): User IP address. (4 bytes)
siaddr: (int): Server IP address. IP address of next server to use in bootstrap;
returned in DHCPOFFER, DHCPACK by server. (4 bytes)
giaddr: (int): Gateway IP address. Relay agent IP address, used in booting via a
relay agent. (4 bytes)
chaddr: (int): Client hardware address. (16 bytes)
sname: (int): Server Hostname. Optional, null terminated string. (64 bytes)
file: (int): Boot file name. Null terminated string; "generic"
name or null in DHCPDISCOVER, fully qualified
directory-path name in DHCPOFFER. (128 bytes)
magic: (int): Magic cookie. Optional parameters field. See the options
documents for a list of defined options. (4 bytes)
"""
__hdr__ = (
('op', 'B', DHCP_OP_REQUEST),
('hrd', 'B', arp.ARP_HRD_ETH), # just like ARP.hrd
('hln', 'B', 6), # and ARP.hln
('hops', 'B', 0),
('xid', 'I', 0xdeadbeef),
('secs', 'H', 0),
('flags', 'H', 0),
('ciaddr', 'I', 0),
('yiaddr', 'I', 0),
('siaddr', 'I', 0),
('giaddr', 'I', 0),
('chaddr', '16s', 16 * b'\x00'),
('sname', '64s', 64 * b'\x00'),
('file', '128s', 128 * b'\x00'),
('magic', 'I', DHCP_MAGIC),
)
opts = (
(DHCP_OPT_MSGTYPE, chr(DHCPDISCOVER)),
(DHCP_OPT_PARAM_REQ, ''.join(map(chr, (DHCP_OPT_REQ_IP,
DHCP_OPT_ROUTER,
DHCP_OPT_NETMASK,
DHCP_OPT_DNS_SVRS))))
) # list of (type, data) tuples
def __len__(self):
return self.__hdr_len__ + \
sum([2 + len(o[1]) for o in self.opts]) + 1 + len(self.data)
def __bytes__(self):
return self.pack_hdr() + self.pack_opts() + bytes(self.data)
def pack_opts(self):
"""Return packed options string."""
if not self.opts:
return b''
l_ = []
for t, data in self.opts:
l_.append(struct.pack("BB%is" % len(data), t, len(data), data))
l_.append(b'\xff')
return b''.join(l_)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.chaddr = self.chaddr[:self.hln]
buf = self.data
l_ = []
while buf:
t = compat_ord(buf[0])
if t == 0xff:
buf = buf[1:]
break
elif t == 0:
buf = buf[1:]
else:
n = compat_ord(buf[1])
l_.append((t, buf[2:2 + n]))
buf = buf[2 + n:]
self.opts = l_
self.data = buf
def test_dhcp():
s = (
b'\x01\x01\x06\x00\xad\x53\xc8\x63\xb8\x87\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x02\x55\x82\xf3\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63\x35\x01\x01\xfb\x01\x01\x3d\x07\x01\x00'
b'\x02\x55\x82\xf3\xa6\x32\x04\x0a\x00\x01\x65\x0c\x09\x47\x75\x69\x6e\x65\x76\x65\x72\x65\x3c\x08\x4d'
b'\x53\x46\x54\x20\x35\x2e\x30\x37\x0a\x01\x0f\x03\x06\x2c\x2e\x2f\x1f\x21\x2b\xff\x00\x00\x00\x00\x00'
)
dhcp = DHCP(s)
assert (s == bytes(dhcp))
assert len(dhcp) == 300
assert isinstance(dhcp.chaddr, bytes)
assert isinstance(dhcp.sname, bytes)
assert isinstance(dhcp.file, bytes)
# Test default construction
dhcp = DHCP()
assert isinstance(dhcp.chaddr, bytes)
assert isinstance(dhcp.sname, bytes)
assert isinstance(dhcp.file, bytes)
def test_no_opts():
from binascii import unhexlify
buf_small_hdr = unhexlify(
'00' # op
'00' # hrd
'06' # hln
'12' # hops
'deadbeef' # xid
'1234' # secs
'9866' # flags
'00000000' # ciaddr
'00000000' # yiaddr
'00000000' # siaddr
'00000000' # giaddr
)
buf = b''.join([
buf_small_hdr,
b'\x00' * 16, # chaddr
b'\x11' * 64, # sname
b'\x22' * 128, # file
b'\x44' * 4, # magic
b'\x00' # data
])
dhcp = DHCP(buf)
assert dhcp.opts == []
assert dhcp.data == b''
assert dhcp.pack_opts() == b''

View File

@@ -0,0 +1,232 @@
# $Id: diameter.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Diameter."""
from __future__ import print_function
from __future__ import absolute_import
import struct
from . import dpkt
from .compat import compat_ord
# Diameter Base Protocol - RFC 3588
# http://tools.ietf.org/html/rfc3588
# Request/Answer Command Codes
ABORT_SESSION = 274
ACCOUNTING = 271
CAPABILITIES_EXCHANGE = 257
DEVICE_WATCHDOG = 280
DISCONNECT_PEER = 282
RE_AUTH = 258
SESSION_TERMINATION = 275
class Diameter(dpkt.Packet):
"""Diameter.
Diameter is an authentication, authorization, and accounting protocol for computer networks. It evolved from the
earlier RADIUS protocol. It belongs to the application layer protocols in the internet protocol suite.
Attributes:
__hdr__: Header fields of Diameter.
v: (int) Version. The version of the Diameter Base Protocol.
As of 2014, the only value supported is 1. (1 byte)
len: (bytes): Message Length. The Message Length field indicates the length of the Diameter message in
bytes, including the header fields and the padded AVPs. (3 bytes)
flags: (int): Command flags. (Request, Proxiable, Error, Potentially re-transmitted message) (1 byte)
cmd: (bytes): Commands. Determine the action that is to be taken for a particular message. (3 bytes)
app_id: (int): Application-ID. Application-ID is used to identify for which Diameter application the
message is applicable. (4 bytes)
hop_id: (int): Hop-by-Hop Identifier. Used to match the requests with their answers as the same value in
the request is used in the response. (4 bytes)
end_id: (int): End-to-End Identifier. used to detect duplicate messages along with the combination of the
Origin-Host AVP. (4 bytes)
"""
__hdr__ = (
('v', 'B', 1),
('len', '3s', 0),
('flags', 'B', 0),
('cmd', '3s', 0),
('app_id', 'I', 0),
('hop_id', 'I', 0),
('end_id', 'I', 0)
)
@property
def request_flag(self):
return (self.flags >> 7) & 0x1
@request_flag.setter
def request_flag(self, r):
self.flags = (self.flags & ~0x80) | ((r & 0x1) << 7)
@property
def proxiable_flag(self):
return (self.flags >> 6) & 0x1
@proxiable_flag.setter
def proxiable_flag(self, p):
self.flags = (self.flags & ~0x40) | ((p & 0x1) << 6)
@property
def error_flag(self):
return (self.flags >> 5) & 0x1
@error_flag.setter
def error_flag(self, e):
self.flags = (self.flags & ~0x20) | ((e & 0x1) << 5)
@property
def retransmit_flag(self):
return (self.flags >> 4) & 0x1
@retransmit_flag.setter
def retransmit_flag(self, t):
self.flags = (self.flags & ~0x10) | ((t & 0x1) << 4)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.cmd = (compat_ord(self.cmd[0]) << 16) | \
(compat_ord(self.cmd[1]) << 8) | \
(compat_ord(self.cmd[2]))
self.len = (compat_ord(self.len[0]) << 16) | \
(compat_ord(self.len[1]) << 8) | \
(compat_ord(self.len[2]))
self.data = self.data[:self.len - self.__hdr_len__]
l_ = []
while self.data:
avp = AVP(self.data)
l_.append(avp)
self.data = self.data[len(avp):]
self.data = self.avps = l_
def pack_hdr(self):
self.len = struct.pack("BBB", (self.len >> 16) & 0xff, (self.len >> 8) & 0xff, self.len & 0xff)
self.cmd = struct.pack("BBB", (self.cmd >> 16) & 0xff, (self.cmd >> 8) & 0xff, self.cmd & 0xff)
return dpkt.Packet.pack_hdr(self)
def __len__(self):
return self.__hdr_len__ + sum(map(len, self.data))
def __bytes__(self):
return self.pack_hdr() + b''.join(map(bytes, self.data))
class AVP(dpkt.Packet):
__hdr__ = (
('code', 'I', 0),
('flags', 'B', 0),
('len', '3s', 0),
)
@property
def vendor_flag(self):
return (self.flags >> 7) & 0x1
@vendor_flag.setter
def vendor_flag(self, v):
self.flags = (self.flags & ~0x80) | ((v & 0x1) << 7)
@property
def mandatory_flag(self):
return (self.flags >> 6) & 0x1
@mandatory_flag.setter
def mandatory_flag(self, m):
self.flags = (self.flags & ~0x40) | ((m & 0x1) << 6)
@property
def protected_flag(self):
return (self.flags >> 5) & 0x1
@protected_flag.setter
def protected_flag(self, p):
self.flags = (self.flags & ~0x20) | ((p & 0x1) << 5)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.len = (compat_ord(self.len[0]) << 16) | \
(compat_ord(self.len[1]) << 8) | \
(compat_ord(self.len[2]))
if self.vendor_flag:
self.vendor = struct.unpack('>I', self.data[:4])[0]
self.data = self.data[4:self.len - self.__hdr_len__]
else:
self.data = self.data[:self.len - self.__hdr_len__]
def pack_hdr(self):
self.len = struct.pack("BBB", (self.len >> 16) & 0xff, (self.len >> 8) & 0xff, self.len & 0xff)
data = dpkt.Packet.pack_hdr(self)
if self.vendor_flag:
data += struct.pack('>I', self.vendor)
return data
def __len__(self):
length = self.__hdr_len__ + len(self.data)
if self.vendor_flag:
length += 4
return length
__s = (b'\x01\x00\x00\x28\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00'
b'\x01\x08\x40\x00\x00\x0c\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08')
__t = (b'\x01\x00\x00\x2c\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00'
b'\x01\x08\xc0\x00\x00\x10\xde\xad\xbe\xef\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08')
def test_pack():
d = Diameter(__s)
assert (__s == bytes(d))
assert len(d) == len(__s)
d = Diameter(__t)
assert (__t == bytes(d))
assert len(d) == len(__t)
def test_unpack():
d = Diameter(__s)
assert (d.len == 40)
# assert (d.cmd == DEVICE_WATCHDOG_REQUEST)
assert (d.request_flag == 1)
assert (d.error_flag == 0)
assert (len(d.avps) == 2)
avp = d.avps[0]
# assert (avp.code == ORIGIN_HOST)
assert (avp.mandatory_flag == 1)
assert (avp.vendor_flag == 0)
assert (avp.len == 12)
assert (len(avp) == 12)
assert (avp.data == b'\x68\x30\x30\x32')
# also test the optional vendor id support
d = Diameter(__t)
assert (d.len == 44)
avp = d.avps[0]
assert (avp.vendor_flag == 1)
assert (avp.len == 16)
assert (len(avp) == 16)
assert (avp.vendor == 3735928559)
assert (avp.data == b'\x68\x30\x30\x32')
def test_diameter_properties():
diameter = Diameter()
for prop in ['request_flag', 'proxiable_flag', 'error_flag', 'retransmit_flag']:
assert hasattr(diameter, prop)
assert getattr(diameter, prop) == 0
setattr(diameter, prop, 1)
assert getattr(diameter, prop) == 1
def test_avp_properties():
avp = AVP()
for prop in ['vendor_flag', 'mandatory_flag', 'protected_flag']:
assert hasattr(avp, prop)
assert getattr(avp, prop) == 0
setattr(avp, prop, 1)
assert getattr(avp, prop) == 1

View File

@@ -0,0 +1,936 @@
# $Id: dns.py 27 2006-11-21 01:22:52Z dahelder $
# -*- coding: utf-8 -*-
"""Domain Name System."""
from __future__ import print_function
from __future__ import absolute_import
import struct
import codecs
from . import dpkt
from .compat import compat_ord
DNS_Q = 0
DNS_R = 1
# Opcodes
DNS_QUERY = 0
DNS_IQUERY = 1
DNS_STATUS = 2
DNS_NOTIFY = 4
DNS_UPDATE = 5
# Flags
DNS_CD = 0x0010 # checking disabled
DNS_AD = 0x0020 # authenticated data
DNS_Z = 0x0040 # unused
DNS_RA = 0x0080 # recursion available
DNS_RD = 0x0100 # recursion desired
DNS_TC = 0x0200 # truncated
DNS_AA = 0x0400 # authoritative answer
DNS_QR = 0x8000 # response ( query / response )
# Response codes
DNS_RCODE_NOERR = 0
DNS_RCODE_FORMERR = 1
DNS_RCODE_SERVFAIL = 2
DNS_RCODE_NXDOMAIN = 3
DNS_RCODE_NOTIMP = 4
DNS_RCODE_REFUSED = 5
DNS_RCODE_YXDOMAIN = 6
DNS_RCODE_YXRRSET = 7
DNS_RCODE_NXRRSET = 8
DNS_RCODE_NOTAUTH = 9
DNS_RCODE_NOTZONE = 10
# RR types
DNS_A = 1
DNS_NS = 2
DNS_CNAME = 5
DNS_SOA = 6
DNS_NULL = 10
DNS_PTR = 12
DNS_HINFO = 13
DNS_MX = 15
DNS_TXT = 16
DNS_AAAA = 28
DNS_SRV = 33
DNS_OPT = 41
# RR classes
DNS_IN = 1
DNS_CHAOS = 3
DNS_HESIOD = 4
DNS_ANY = 255
def pack_name(name, off, label_ptrs):
name = codecs.encode(name, 'utf-8')
if name:
labels = name.split(b'.')
else:
labels = []
labels.append(b'')
buf = b''
for i, label in enumerate(labels):
key = b'.'.join(labels[i:]).upper()
ptr = label_ptrs.get(key)
if ptr is None:
if len(key) > 1:
ptr = off + len(buf)
if ptr < 0xc000:
label_ptrs[key] = ptr
i = len(label)
buf += struct.pack("B", i) + label
else:
buf += struct.pack('>H', (0xc000 | ptr))
break
return buf
def unpack_name(buf, off):
name = []
saved_off = 0
start_off = off
name_length = 0
while True:
if off >= len(buf):
raise dpkt.NeedData()
n = compat_ord(buf[off])
if n == 0:
off += 1
break
elif (n & 0xc0) == 0xc0:
ptr = struct.unpack('>H', buf[off:off + 2])[0] & 0x3fff
if ptr >= start_off:
raise dpkt.UnpackError('Invalid label compression pointer')
off += 2
if not saved_off:
saved_off = off
start_off = off = ptr
elif (n & 0xc0) == 0x00:
off += 1
name.append(buf[off:off + n])
name_length += n + 1
if name_length > 255:
raise dpkt.UnpackError('name longer than 255 bytes')
off += n
else:
raise dpkt.UnpackError('Invalid label length %02x' % n)
if not saved_off:
saved_off = off
return codecs.decode(b'.'.join(name), 'utf-8'), saved_off
class DNS(dpkt.Packet):
"""Domain Name System.
The Domain Name System (DNS) is the hierarchical and decentralized naming system used to identify computers,
services, and other resources reachable through the Internet or other Internet Protocol (IP) networks.
The resource records contained in the DNS associate domain names with other forms of information.
Attributes:
__hdr__ (tuple(header_name, c_type, offset)): Header fields of DNS.
id: (int): Identification. Used to match request/reply packets.
op: (int): Operation
qd: (int): Query Definition
an: (int): Answer
ns: (int): Name Server
ar: (int): Additional Record
"""
__hdr__ = (
('id', 'H', 0),
('op', 'H', DNS_RD), # recursive query
# XXX - lists of query, RR objects
('qd', 'H', []),
('an', 'H', []),
('ns', 'H', []),
('ar', 'H', [])
)
@property
def qr(self):
"""DNS Query/Response. 1 bit"""
return int((self.op & DNS_QR) == DNS_QR)
@qr.setter
def qr(self, v):
if v:
self.op |= DNS_QR
else:
self.op &= ~DNS_QR
@property
def opcode(self):
"""Operation code. 4 bits."""
return (self.op >> 11) & 0xf
@opcode.setter
def opcode(self, v):
self.op = (self.op & ~0x7800) | ((v & 0xf) << 11)
@property
def aa(self):
"""Authoritative Answer. 1 bit.
Specifies that the responding name server is an authority for the domain name in question section."""
return int((self.op & DNS_AA) == DNS_AA)
@aa.setter
def aa(self, v):
if v:
self.op |= DNS_AA
else:
self.op &= ~DNS_AA
@property
def tc(self):
"""Truncated. 1 bit. Indicates that only the first 512 bytes of the reply was returned."""
return int((self.op & DNS_TC) == DNS_TC)
@tc.setter
def tc(self, v):
if v:
self.op |= DNS_TC
else:
self.op &= ~DNS_TC
@property
def rd(self):
"""Recursion Desired. 1 bit. May be set in a query and is copied into the response.
If set, the name server is directed to pursue the query recursively. Recursive query support is optional."""
return int((self.op & DNS_RD) == DNS_RD)
@rd.setter
def rd(self, v):
if v:
self.op |= DNS_RD
else:
self.op &= ~DNS_RD
@property
def ra(self):
"""Recursion Available. 1 bit. Indicates if recursive query support is available in the name server."""
return int((self.op & DNS_RA) == DNS_RA)
@ra.setter
def ra(self, v):
if v:
self.op |= DNS_RA
else:
self.op &= ~DNS_RA
@property
def zero(self):
"""Zero 1 bit"""
return int((self.op & DNS_Z) == DNS_Z)
@zero.setter
def zero(self, v):
if v:
self.op |= DNS_Z
else:
self.op &= ~DNS_Z
@property
def rcode(self):
"""Return code. 4 bits."""
return self.op & 0xf
@rcode.setter
def rcode(self, v):
self.op = (self.op & ~0xf) | (v & 0xf)
class Q(dpkt.Packet):
"""DNS question."""
__hdr__ = (
('name', '1025s', b''),
('type', 'H', DNS_A),
('cls', 'H', DNS_IN)
)
# XXX - suk
def __len__(self):
raise NotImplementedError
__str__ = __len__
def unpack(self, buf):
raise NotImplementedError
class RR(Q):
"""DNS resource record."""
__hdr__ = (
('name', '1025s', b''),
('type', 'H', DNS_A),
('cls', 'H', DNS_IN),
('ttl', 'I', 0),
('rlen', 'H', 4),
('rdata', 's', b'')
)
def pack_rdata(self, off, label_ptrs):
# XXX - yeah, this sux
if self.rdata:
return self.rdata
if self.type == DNS_A:
return self.ip
elif self.type == DNS_NS:
return pack_name(self.nsname, off, label_ptrs)
elif self.type == DNS_CNAME:
return pack_name(self.cname, off, label_ptrs)
elif self.type == DNS_PTR:
return pack_name(self.ptrname, off, label_ptrs)
elif self.type == DNS_SOA:
l_ = []
l_.append(pack_name(self.mname, off, label_ptrs))
l_.append(pack_name(self.rname, off + len(l_[0]), label_ptrs))
l_.append(struct.pack('>IIIII', self.serial, self.refresh,
self.retry, self.expire, self.minimum))
return b''.join(l_)
elif self.type == DNS_MX:
return struct.pack('>H', self.preference) + \
pack_name(self.mxname, off + 2, label_ptrs)
elif self.type == DNS_TXT or self.type == DNS_HINFO:
return b''.join(struct.pack('B', len(x)) + x for x in self.text)
elif self.type == DNS_AAAA:
return self.ip6
elif self.type == DNS_SRV:
return struct.pack('>HHH', self.priority, self.weight, self.port) + \
pack_name(self.srvname, off + 6, label_ptrs)
elif self.type == DNS_OPT:
return b'' # self.rdata
else:
raise dpkt.PackError('RR type %s is not supported' % self.type)
def unpack_rdata(self, buf, off):
if self.type == DNS_A:
self.ip = self.rdata
elif self.type == DNS_NS:
self.nsname, off = unpack_name(buf, off)
elif self.type == DNS_CNAME:
self.cname, off = unpack_name(buf, off)
elif self.type == DNS_PTR:
self.ptrname, off = unpack_name(buf, off)
elif self.type == DNS_SOA:
self.mname, off = unpack_name(buf, off)
self.rname, off = unpack_name(buf, off)
self.serial, self.refresh, self.retry, self.expire, self.minimum = \
struct.unpack('>IIIII', buf[off:off + 20])
elif self.type == DNS_MX:
self.preference = struct.unpack('>H', self.rdata[:2])
self.mxname, off = unpack_name(buf, off + 2)
elif self.type == DNS_TXT or self.type == DNS_HINFO:
self.text = []
buf = self.rdata
while buf:
n = compat_ord(buf[0])
self.text.append(codecs.decode(buf[1:1 + n], 'utf-8'))
buf = buf[1 + n:]
elif self.type == DNS_AAAA:
self.ip6 = self.rdata
elif self.type == DNS_NULL:
self.null = codecs.encode(self.rdata, 'hex')
elif self.type == DNS_SRV:
self.priority, self.weight, self.port = struct.unpack('>HHH', self.rdata[:6])
self.srvname, off = unpack_name(buf, off + 6)
elif self.type == DNS_OPT:
pass # RFC-6891: OPT is a pseudo-RR not carrying any DNS data
else:
raise dpkt.UnpackError('RR type %s is not supported' % self.type)
def pack_q(self, buf, q):
"""Append packed DNS question and return buf."""
return buf + pack_name(q.name, len(buf), self.label_ptrs) + struct.pack('>HH', q.type, q.cls)
def unpack_q(self, buf, off):
"""Return DNS question and new offset."""
q = self.Q()
q.name, off = unpack_name(buf, off)
q.type, q.cls = struct.unpack('>HH', buf[off:off + 4])
off += 4
return q, off
def pack_rr(self, buf, rr):
"""Append packed DNS RR and return buf."""
name = pack_name(rr.name, len(buf), self.label_ptrs)
rdata = rr.pack_rdata(len(buf) + len(name) + 10, self.label_ptrs)
return buf + name + struct.pack('>HHIH', rr.type, rr.cls, rr.ttl, len(rdata)) + rdata
def unpack_rr(self, buf, off):
"""Return DNS RR and new offset."""
rr = self.RR()
rr.name, off = unpack_name(buf, off)
rr.type, rr.cls, rr.ttl, rdlen = struct.unpack('>HHIH', buf[off:off + 10])
off += 10
rr.rdata = buf[off:off + rdlen]
rr.rlen = rdlen
rr.unpack_rdata(buf, off)
off += rdlen
return rr, off
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
off = self.__hdr_len__
cnt = self.qd # FIXME: This relies on this being properly set somewhere else
self.qd = []
for _ in range(cnt):
q, off = self.unpack_q(buf, off)
self.qd.append(q)
for x in ('an', 'ns', 'ar'):
cnt = getattr(self, x, 0)
setattr(self, x, [])
for _ in range(cnt):
rr, off = self.unpack_rr(buf, off)
getattr(self, x).append(rr)
self.data = b''
def __len__(self):
# XXX - cop out
return len(bytes(self))
def __bytes__(self):
# XXX - compress names on the fly
self.label_ptrs = {}
buf = struct.pack(self.__hdr_fmt__, self.id, self.op, len(self.qd),
len(self.an), len(self.ns), len(self.ar))
for q in self.qd:
buf = self.pack_q(buf, q)
for x in ('an', 'ns', 'ar'):
for rr in getattr(self, x):
buf = self.pack_rr(buf, rr)
del self.label_ptrs
return buf
# TESTS
def define_testdata():
"""
Reference test data is stored in the dynamically defined class.
It is created in this way so that we can import unhexlify only during
testing, and not during normal use.
"""
from binascii import unhexlify
class TestData(object):
a_resp = unhexlify(
"059c8180000100010000000106676f6f676c6503636f6d0000010001c00c00010"
"0010000012b0004d83ace2e0000290200000000000000"
)
aaaa_resp = unhexlify(
"7f228180000100010000000005676d61696c03636f6d00001c0001c00c001c000"
"10000012b00102a001450400908020000000000002005"
)
cname_resp = unhexlify(
"a154818000010001000000000377777705676d61696c03636f6d0000010001c00"
"c000500010000545f000e046d61696c06676f6f676c65c016"
)
invalid_rr = unhexlify(
"000001000000000100000000046e616d650000150001000000000000"
)
mx_resp = unhexlify(
"053b8180000100010000000006676f6f676c6503636f6d00000f0001c00c000f0"
"001000002570011001e04616c7432056173706d78016cc00c"
)
null_resp = unhexlify(
"12b0840000010001000000000b626c6168626c616836363606706972617465037"
"3656100000a0001c00c000a00010000000000095641434b4403c5e901"
)
opt_resp = unhexlify(
"8d6e0110000100000000000104783131310678787878313106616b616d6169036"
"e657400000100010000290fa0000080000000"
)
ptr_resp = unhexlify(
"67028180000100010003000001310131033231310331343107696e2d616464720"
"46172706100000c0001c00c000c000100000d3600240764656661756c740a762d"
"756d63652d69667305756d6e657405756d6963680365647500c00e00020001000"
"00d36000d0673686162627903696673c04fc00e0002000100000d36000f0c6669"
"73682d6c6963656e7365c06dc00e0002000100000d36000b04646e73320369746"
"4c04f"
)
soa_resp = unhexlify(
"851f8180000100010000000006676f6f676c6503636f6d0000060001c00c00060"
"001000000230026036e7332c00c09646e732d61646d696ec00c0a747447000003"
"8400000384000007080000003c"
)
srv_resp = unhexlify(
"7f2281800001000100000000075f6a6162626572045f746370066a61626265720"
"3636f6d0000210001c00c0021000100000e0f001a000a000014950764656e6a61"
"6232066a616262657203636f6d00"
)
txt_resp = unhexlify(
"10328180000100010000000006676f6f676c6503636f6d0000100001c00c00100"
"0010000010e00100f763d7370663120707472203f616c6c"
)
return TestData()
def test_basic():
buf = define_testdata().a_resp
my_dns = DNS(buf)
assert my_dns.qd[0].name == 'google.com'
assert my_dns.an[0].name == 'google.com'
assert bytes(my_dns) == buf
class TryExceptException:
def __init__(self, exception_type, msg=''):
self.exception_type = exception_type
self.msg = msg
def __call__(self, f, *args, **kwargs):
def wrapper(*args, **kwargs):
try:
f()
except self.exception_type as e:
if self.msg:
assert str(e) == self.msg
else:
raise Exception("There should have been an Exception raised")
return wrapper
@TryExceptException(Exception, msg='There should have been an Exception raised')
def test_TryExceptException():
"""Check that we can catch a function which does not throw an exception when it is supposed to"""
@TryExceptException(NotImplementedError)
def fun():
pass
try:
fun()
except Exception as e:
raise e
@TryExceptException(NotImplementedError)
def test_Q_len():
"""Test in place for when the method is written"""
q = DNS.Q()
len(q)
@TryExceptException(NotImplementedError)
def test_Q_unpack():
"""Test in place for when the method is written"""
q = DNS.Q()
q.unpack(None)
def property_runner(prop, ops, set_to=None):
if set_to is None:
set_to = [False, True, False]
buf = define_testdata().a_resp
dns = DNS(buf)
for set_to, op in zip(set_to, ops):
setattr(dns, prop, set_to)
assert dns.op == op
assert getattr(dns, prop) == set_to
def test_qr():
property_runner('qr', ops=[384, 33152, 384])
def test_opcode():
property_runner('opcode', ops=[33152, 35200, 33152])
def test_aa():
property_runner('aa', ops=[33152, 34176, 33152])
def test_tc():
property_runner('tc', ops=[33152, 33664, 33152])
def test_rd():
property_runner('rd', ops=[32896, 33152, 32896])
def test_ra():
property_runner('ra', ops=[33024, 33152, 33024])
def test_zero():
property_runner('zero', ops=[33152, 33216, 33152])
def test_rcode():
property_runner('rcode', ops=[33152, 33153, 33152])
def test_PTR():
buf = define_testdata().ptr_resp
my_dns = DNS(buf)
assert my_dns.qd[0].name == '1.1.211.141.in-addr.arpa' and \
my_dns.an[0].ptrname == 'default.v-umce-ifs.umnet.umich.edu' and \
my_dns.ns[0].nsname == 'shabby.ifs.umich.edu' and \
my_dns.ns[1].ttl == 3382 and \
my_dns.ns[2].nsname == 'dns2.itd.umich.edu'
assert buf == bytes(my_dns)
def test_OPT():
buf = define_testdata().opt_resp
my_dns = DNS(buf)
my_rr = my_dns.ar[0]
assert my_rr.type == DNS_OPT
assert my_rr.rlen == 0 and my_rr.rdata == b''
assert bytes(my_dns) == buf
my_rr.rdata = b'\x00\x00\x00\x02\x00\x00' # add 1 attribute tlv
my_dns2 = DNS(bytes(my_dns))
my_rr2 = my_dns2.ar[0]
assert my_rr2.rlen == 6 and my_rr2.rdata == b'\x00\x00\x00\x02\x00\x00'
def test_pack_name():
# Empty name is \0
x = pack_name('', 0, {})
assert x == b'\0'
@TryExceptException(dpkt.UnpackError)
def test_unpack_name():
"""If the offset is longer than the buffer, there will be an UnpackError"""
unpack_name(b' ', 0)
@TryExceptException(dpkt.UnpackError)
def test_random_data():
DNS(b'\x83z0\xd2\x9a\xec\x94_7\xf3\xb7+\x85"?\xf0\xfb')
@TryExceptException(dpkt.UnpackError)
def test_circular_pointers():
DNS(b'\xc0\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\xc0\x00')
@TryExceptException(dpkt.UnpackError)
def test_very_long_name():
DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00' + (b'\x10abcdef0123456789' * 16) + b'\x00')
def test_null_response():
buf = define_testdata().null_resp
my_dns = DNS(buf)
assert my_dns.qd[0].name == 'blahblah666.pirate.sea' and \
my_dns.an[0].null == b'5641434b4403c5e901'
assert str(buf) == str(my_dns)
def test_txt_response():
buf = define_testdata().txt_resp
my_dns = DNS(buf)
my_rr = my_dns.an[0]
assert my_rr.type == DNS_TXT
assert my_rr.name == 'google.com'
assert my_rr.text == ['v=spf1 ptr ?all']
assert str(my_dns) == str(buf)
assert bytes(my_dns) == buf
def test_rdata_TXT():
rr = DNS.RR(
type=DNS_TXT,
text=[b'v=spf1 ptr ?all', b'a=something']
)
packdata = rr.pack_rdata(0, {})
correct = b'\x0fv=spf1 ptr ?all\x0ba=something'
assert packdata == correct
def test_rdata_HINFO():
rr = DNS.RR(
type=DNS_HINFO,
text=[b'v=spf1 ptr ?all', b'a=something']
)
packdata = rr.pack_rdata(0, {})
correct = b'\x0fv=spf1 ptr ?all\x0ba=something'
assert packdata == correct
def test_rdata_rdata():
rr = DNS.RR(
name='zc.akadns.org',
ttl=123446,
rdata=b'?\xf1\xc76',
)
packdata = rr.pack_rdata(0, {})
correct = b'?\xf1\xc76'
assert packdata == correct
def test_rdata_A():
rr = DNS.RR(
name='zc.akadns.org',
ttl=123446,
ip=b'?\xf1\xc76',
type=DNS_A,
)
packdata = rr.pack_rdata(0, {})
correct = b'?\xf1\xc76'
assert packdata == correct
def test_rdata_NS():
rr = DNS.RR(
nsname='zc.akadns.org',
ttl=123446,
ip=b'?\xf1\xc76',
type=DNS_NS,
)
packdata = rr.pack_rdata(0, {})
correct = b'\x02zc\x06akadns\x03org\x00'
assert packdata == correct
def test_rdata_CNAME():
rr = DNS.RR(
cname='zc.akadns.org',
ttl=123446,
ip=b'?\xf1\xc76',
type=DNS_CNAME,
)
packdata = rr.pack_rdata(0, {})
correct = b'\x02zc\x06akadns\x03org\x00'
assert packdata == correct
def test_rdata_PTR():
rr = DNS.RR(
ptrname='default.v-umce-ifs.umnet.umich.edu',
ttl=1236,
ip=b'?\xf1\xc76',
type=DNS_PTR,
)
packdata = rr.pack_rdata(0, {})
correct = b'\x07default\nv-umce-ifs\x05umnet\x05umich\x03edu\x00'
assert packdata == correct
def test_rdata_SOA():
rr = DNS.RR(
mname='blah.google.com',
rname='moo.blah.com',
serial=12345666,
refresh=123463,
retry=209834,
minimum=9000,
expire=28341,
type=DNS_SOA,
)
packdata = rr.pack_rdata(0, {})
correct = (
b'\x04blah\x06google\x03com\x00\x03moo\x04blah\xc0\x0c\x00\xbcaB'
b'\x00\x01\xe2G\x00\x033\xaa\x00\x00n\xb5\x00\x00#(')
assert packdata == correct
def test_rdata_MX():
rr = DNS.RR(
type=DNS_MX,
preference=2124,
mxname='mail.google.com',
)
packdata = rr.pack_rdata(0, {})
correct = b'\x08L\x04mail\x06google\x03com\x00'
assert packdata == correct
def test_rdata_AAAA():
ip6 = b'&\x07\xf8\xb0@\x0c\x0c\x03\x00\x00\x00\x00\x00\x00\x00\x1a'
rr = DNS.RR(
type=DNS_AAAA,
ip6=ip6,
)
packdata = rr.pack_rdata(0, {})
correct = ip6
assert packdata == correct
def test_rdata_SRV():
rr = DNS.RR(
type=DNS_SRV,
ttl=86400,
priority=0,
weight=5,
port=5060,
srvname='_sip._tcp.example.com',
)
packdata = rr.pack_rdata(0, {})
correct = b'\x00\x00\x00\x05\x13\xc4\x04_sip\x04_tcp\x07example\x03com\x00'
assert packdata == correct
def test_rdata_OPT():
rr = DNS.RR(
type=DNS_OPT,
)
# TODO: This is hardcoded to return b''. Is this intentional?
packdata = rr.pack_rdata(0, {})
correct = b''
assert packdata == correct
def test_dns_len():
my_dns = DNS()
assert len(my_dns) == 12
@TryExceptException(dpkt.PackError)
def test_rdata_FAIL():
DNS.RR(type=12345666).pack_rdata(0, {})
def test_soa():
buf = define_testdata().soa_resp
soa = DNS(buf)
assert soa.id == 34079
assert soa.op == 33152
assert len(soa.qd) == 1
q = soa.qd[0]
assert q.name == 'google.com'
assert q.type == DNS_SOA
assert q.cls == DNS_IN
assert len(soa.an) == 1
a = soa.an[0]
assert a.name == 'google.com'
assert a.type == DNS_SOA
assert a.cls == DNS_IN
assert a.ttl == 35
assert a.retry == 900
assert a.mname == 'ns2.google.com'
assert a.minimum == 60
assert a.refresh == 900
assert a.expire == 1800
assert a.serial == 175404103
assert a.rlen == 38
assert a.rname == 'dns-admin.google.com'
assert a.rdata == b'\x03ns2\xc0\x0c\tdns-admin\xc0\x0c\nttG\x00\x00\x03\x84\x00\x00\x03\x84\x00\x00\x07\x08\x00\x00\x00<'
assert soa.ar == []
def test_mx():
buf = define_testdata().mx_resp
mx = DNS(buf)
assert mx.id == 1339
assert mx.op == 33152
assert len(mx.qd) == 1
q = mx.qd[0]
assert q.name == 'google.com'
assert q.type == DNS_MX
assert q.cls == DNS_IN
assert len(mx.an) == 1
a = mx.an[0]
assert a.type == DNS_MX
assert a.cls == DNS_IN
assert a.name == 'google.com'
assert a.ttl == 599
assert a.mxname == 'alt2.aspmx.l.google.com'
assert a.preference == (30,)
assert a.rlen == 17
assert a.rdata == b'\x00\x1e\x04alt2\x05aspmx\x01l\xc0\x0c'
assert mx.ar == []
def test_aaaa():
buf = define_testdata().aaaa_resp
aaaa = DNS(buf)
aaaa.id = 32546
aaaa.op = 33152
assert len(aaaa.qd) == 1
q = aaaa.qd[0]
assert q.type == DNS_AAAA
assert q.name == 'gmail.com'
assert len(aaaa.an) == 1
a = aaaa.an[0]
assert a.type == DNS_AAAA
assert a.cls == DNS_IN
assert a.name == 'gmail.com'
assert a.ttl == 299
assert a.ip6 == b'*\x00\x14P@\t\x08\x02\x00\x00\x00\x00\x00\x00 \x05'
assert a.rlen == 16
assert a.rdata == b'*\x00\x14P@\t\x08\x02\x00\x00\x00\x00\x00\x00 \x05'
assert aaaa.ar == []
def test_srv():
buf = define_testdata().srv_resp
srv = DNS(buf)
srv.id = 32546
srv.op = 33152
assert len(srv.qd) == 1
q = srv.qd[0]
assert q.type == DNS_SRV
assert q.name == '_jabber._tcp.jabber.com'
assert q.cls == DNS_IN
assert len(srv.an) == 1
a = srv.an[0]
assert a.type == DNS_SRV
assert a.cls == DNS_IN
assert a.name == '_jabber._tcp.jabber.com'
assert a.port == 5269
assert a.ttl == 3599
assert a.srvname == 'denjab2.jabber.com'
assert a.priority == 10
assert a.weight == 0
assert a.rlen == 26
assert a.rdata == b'\x00\n\x00\x00\x14\x95\x07denjab2\x06jabber\x03com\x00'
assert srv.ar == []
def test_cname():
buf = define_testdata().cname_resp
cname = DNS(buf)
cname.id = 41300
cname.op = 33152
assert len(cname.qd) == 1
q = cname.qd[0]
assert q.type == DNS_A
assert q.cls == DNS_IN
assert q.name == 'www.gmail.com'
assert len(cname.an) == 1
a = cname.an[0]
assert a.type == DNS_CNAME
assert a.cls == DNS_IN
assert a.name == 'www.gmail.com'
assert a.ttl == 21599
assert a.cname == 'mail.google.com'
assert a.rlen == 14
assert a.rdata == b'\x04mail\x06google\xc0\x16'
assert cname.ar == []
@TryExceptException(dpkt.UnpackError)
def test_invalid_rr():
buf = define_testdata().invalid_rr
DNS(buf)

View File

@@ -0,0 +1,541 @@
# $Id: dpkt.py 43 2007-08-02 22:42:59Z jon.oberheide $
# -*- coding: utf-8 -*-
"""Simple packet creation and parsing.
The dpkt project is a python module for fast, simple packet parsing, with definitions for the basic TCP/IP protocols.
"""
from __future__ import absolute_import, print_function
import copy
import struct
from functools import partial
from itertools import chain
from .compat import compat_ord, compat_izip, iteritems, ntole
class Error(Exception):
pass
class UnpackError(Error):
pass
class NeedData(UnpackError):
pass
class PackError(Error):
pass
# See the "creating parsers" documentation for how all of this works
class _MetaPacket(type):
def __new__(cls, clsname, clsbases, clsdict):
t = type.__new__(cls, clsname, clsbases, clsdict)
st = getattr(t, '__hdr__', None)
if st is not None:
# XXX - __slots__ only created in __new__()
clsdict['__slots__'] = [x[0] for x in st] + ['data']
t = type.__new__(cls, clsname, clsbases, clsdict)
t.__hdr_fields__ = [x[0] for x in st]
t.__hdr_fmt__ = getattr(t, '__byte_order__', '>') + ''.join([x[1] for x in st])
t.__hdr_len__ = struct.calcsize(t.__hdr_fmt__)
t.__hdr_defaults__ = dict(compat_izip(
t.__hdr_fields__, [x[2] for x in st]))
# process __bit_fields__
bit_fields = getattr(t, '__bit_fields__', None)
if bit_fields:
t.__bit_fields_defaults__ = {} # bit fields equivalent of __hdr_defaults__
for (ph_name, ph_struct, ph_default) in t.__hdr__: # ph: placeholder variable for the bit field
if ph_name in bit_fields:
field_defs = bit_fields[ph_name]
bits_total = sum(bf[1] for bf in field_defs) # total size in bits
bits_used = 0
# make sure the sum of bits matches the overall size of the placeholder field
assert bits_total == struct.calcsize(ph_struct) * 8, \
"the overall count of bits in [%s] as declared in __bit_fields__ " \
"does not match its struct size in __hdr__" % ph_name
for (bf_name, bf_size) in field_defs:
if bf_name.startswith('_'): # do not create properties for _private fields
bits_used += bf_size
continue
shift = bits_total - bits_used - bf_size
mask = (2**bf_size - 1) << shift # all zeroes except the field bits
mask_inv = (2**bits_total - 1) - mask # inverse mask
bits_used += bf_size
# calculate the default value for the bit field
bf_default = (t.__hdr_defaults__[ph_name] & mask) >> shift
t.__bit_fields_defaults__[bf_name] = bf_default
# create getter, setter and delete properties for the bit fields
def make_getter(ph_name=ph_name, mask=mask, shift=shift):
def getter_func(self):
ph_val = getattr(self, ph_name)
return (ph_val & mask) >> shift
return getter_func
def make_setter(ph_name=ph_name, mask_inv=mask_inv, shift=shift, bf_name=bf_name, max_val=2**bf_size):
def setter_func(self, bf_val):
# ensure the given value fits into the number of bits available
if bf_val >= max_val:
raise ValueError('value %s is too large for field %s' % (bf_val, bf_name))
ph_val = getattr(self, ph_name)
ph_val_new = (bf_val << shift) | (ph_val & mask_inv)
setattr(self, ph_name, ph_val_new)
return setter_func
# delete property to set the bit field back to its default value
def make_delete(bf_name=bf_name, bf_default=bf_default):
def delete_func(self):
setattr(self, bf_name, bf_default)
return delete_func
setattr(t, bf_name, property(make_getter(), make_setter(), make_delete()))
# optional map of functions for pretty printing
# {field_name: callable(field_value) -> str, ..}
# define as needed in the child protocol classes
#t.__pprint_funcs__ = {} - disabled here to keep the base class lightweight
# placeholder for __public_fields__, a class attribute used in __repr__ and pprint()
t.__public_fields__ = None
return t
class Packet(_MetaPacket("Temp", (object,), {})):
r"""Base packet class, with metaclass magic to generate members from self.__hdr__.
Attributes:
__hdr__: Packet header should be defined as a list of
(name, structfmt, default) tuples.
__byte_order__: Byte order, can be set to override the default ('>')
Example:
>>> class Foo(Packet):
... __hdr__ = (('foo', 'I', 1), ('bar', 'H', 2), ('baz', '4s', 'quux'))
...
>>> foo = Foo(bar=3)
>>> foo
Foo(bar=3)
>>> str(foo)
'\x00\x00\x00\x01\x00\x03quux'
>>> foo.bar
3
>>> foo.baz
'quux'
>>> foo.foo = 7
>>> foo.baz = 'whee'
>>> foo
Foo(baz='whee', foo=7, bar=3)
>>> Foo('hello, world!')
Foo(baz=' wor', foo=1751477356L, bar=28460, data='ld!')
"""
def __init__(self, *args, **kwargs):
"""Packet constructor with ([buf], [field=val,...]) prototype.
Arguments:
buf -- optional packet buffer to unpack
Optional keyword arguments correspond to members to set
(matching fields in self.__hdr__, or 'data').
"""
self.data = b''
if args:
try:
self.unpack(args[0])
except struct.error:
if len(args[0]) < self.__hdr_len__:
raise NeedData('got %d, %d needed at least' % (len(args[0]), self.__hdr_len__))
raise UnpackError('invalid %s: %r' %
(self.__class__.__name__, args[0]))
else:
if hasattr(self, '__hdr_fields__'):
for k in self.__hdr_fields__:
setattr(self, k, copy.copy(self.__hdr_defaults__[k]))
for k, v in iteritems(kwargs):
setattr(self, k, v)
if hasattr(self, '__hdr_fmt__'):
self._pack_hdr = partial(struct.pack, self.__hdr_fmt__)
def __len__(self):
return self.__hdr_len__ + len(self.data)
# legacy
def __iter__(self):
return iter((fld, getattr(self, fld)) for fld in self.__class__.__hdr_fields__)
def __getitem__(self, kls):
"""Return the 1st occurrence of the underlying <kls> data layer, raise KeyError otherwise."""
dd = self.data
while isinstance(dd, Packet):
if dd.__class__ == kls:
return dd
dd = dd.data
raise KeyError(kls)
def __contains__(self, kls):
"""Return True is the given <kls> data layer is present in the stack."""
try:
return bool(self.__getitem__(kls))
except KeyError:
return False
def _create_public_fields(self):
"""Construct __public_fields__ to be used inside __repr__ and pprint"""
l_ = []
for field_name, _, _ in getattr(self, '__hdr__', []):
# public fields defined in __hdr__; "public" means not starting with an underscore
if field_name[0] != '_':
l_.append(field_name) # (1)
# if a field name starts with an underscore, and does NOT contain more underscores,
# it is considered hidden and is ignored (good for fields reserved for future use)
# if a field name starts with an underscore, and DOES contain more underscores,
# it is viewed as a complex field where underscores separate the named properties
# of the class;
elif '_' in field_name[1:]:
# (1) search for these properties in __bit_fields__ where they are explicitly defined
if field_name in getattr(self, '__bit_fields__', {}):
for (prop_name, _) in self.__bit_fields__[field_name]:
if isinstance(getattr(self.__class__, prop_name, None), property):
l_.append(prop_name)
# (2) split by underscore into 1- and 2-component names and look for properties with such names;
# Example: _foo_bar_baz -> look for properties named "foo", "bar", "baz", "foo_bar" and "bar_baz"
# (but not "foo_bar_baz" since it contains more than one underscore)
else:
fns = field_name[1:].split('_')
for prop_name in chain(fns, ('_'.join(x) for x in zip(fns, fns[1:]))):
if isinstance(getattr(self.__class__, prop_name, None), property):
l_.append(prop_name)
# check for duplicates, there shouldn't be any
assert len(l_) == len(set(l_))
self.__class__.__public_fields__ = l_ # store it in the class attribute
def __repr__(self):
if self.__public_fields__ is None:
self._create_public_fields()
# Collect and display protocol fields in order:
# 1. public fields defined in __hdr__, unless their value is default
# 2. properties derived from _private fields defined in __hdr__ and __bit_fields__
# 3. dynamically added fields from self.__dict__, unless they are _private
# 4. self.data when it's present
l_ = []
# (1) and (2) are done via __public_fields__; just filter out defaults here
for field_name in self.__public_fields__:
field_value = getattr(self, field_name)
if (hasattr(self, '__hdr_defaults__') and
field_name in self.__hdr_defaults__ and
field_value == self.__hdr_defaults__[field_name]):
continue
if (hasattr(self, '__bit_fields_defaults__') and
field_name in self.__bit_fields_defaults__ and
field_value == self.__bit_fields_defaults__[field_name]):
continue
l_.append('%s=%r' % (field_name, field_value))
# (3)
l_.extend(
['%s=%r' % (attr_name, attr_value)
for attr_name, attr_value in iteritems(self.__dict__)
if attr_name[0] != '_' and # exclude _private attributes
attr_name != self.data.__class__.__name__.lower()]) # exclude fields like ip.udp
# (4)
if self.data:
l_.append('data=%r' % self.data)
return '%s(%s)' % (self.__class__.__name__, ', '.join(l_))
def pprint(self, indent=1):
"""Human friendly pretty-print."""
if self.__public_fields__ is None:
self._create_public_fields()
l_ = []
def add_field(fn, fv):
"""name=value, # pretty-print form (if available)"""
try:
l_.append('%s=%r, # %s' % (fn, fv, self.__pprint_funcs__[fn](fv)))
except (AttributeError, KeyError):
l_.append('%s=%r,' % (fn, fv))
for field_name in self.__public_fields__:
add_field(field_name, getattr(self, field_name))
for attr_name, attr_value in iteritems(self.__dict__):
if (attr_name[0] != '_' and # exclude _private attributes
attr_name != self.data.__class__.__name__.lower()): # exclude fields like ip.udp
if type(attr_value) == list and attr_value: # expand non-empty lists to print one item per line
l_.append('%s=[' % attr_name)
for av1 in attr_value:
l_.append(' ' + repr(av1) + ',') # XXX - TODO: support pretty-print
l_.append('],')
else:
add_field(attr_name, attr_value)
print('%s(' % self.__class__.__name__) # class name, opening brace
for ii in l_:
print(' ' * indent, '%s' % ii)
if self.data:
if isinstance(self.data, Packet): # recursively descend to lower layers
print(' ' * indent, 'data=', end='')
self.data.pprint(indent=indent + 2)
else:
print(' ' * indent, 'data=%r' % self.data)
print(' ' * (indent - 1), end='')
print(') # %s' % self.__class__.__name__) # closing brace # class name
def __str__(self):
return str(self.__bytes__())
def __bytes__(self):
return self.pack_hdr() + bytes(self.data)
def pack_hdr(self):
"""Return packed header string."""
try:
return self._pack_hdr(
*[getattr(self, k) for k in self.__hdr_fields__]
)
except (TypeError, struct.error):
vals = []
for k in self.__hdr_fields__:
v = getattr(self, k)
if isinstance(v, tuple):
vals.extend(v)
else:
vals.append(v)
try:
return struct.pack(self.__hdr_fmt__, *vals)
except struct.error as e:
raise PackError(str(e))
def pack(self):
"""Return packed header + self.data string."""
return bytes(self)
def unpack(self, buf):
"""Unpack packet header fields from buf, and set self.data."""
for k, v in compat_izip(self.__hdr_fields__,
struct.unpack(self.__hdr_fmt__, buf[:self.__hdr_len__])):
setattr(self, k, v)
self.data = buf[self.__hdr_len__:]
# XXX - ''.join([(len(`chr(x)`)==3) and chr(x) or '.' for x in range(256)])
__vis_filter = (
b'................................ !"#$%&\'()*+,-./0123456789:;<=>?'
b'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~.'
b'................................................................'
b'................................................................')
def hexdump(buf, length=16):
"""Return a hexdump output string of the given buffer."""
n = 0
res = []
while buf:
line, buf = buf[:length], buf[length:]
hexa = ' '.join(['%02x' % compat_ord(x) for x in line])
line = line.translate(__vis_filter).decode('utf-8')
res.append(' %04d: %-*s %s' % (n, length * 3, hexa, line))
n += length
return '\n'.join(res)
def in_cksum_add(s, buf):
n = len(buf)
cnt = (n // 2) * 2
a = struct.unpack('<{}H'.format(n // 2), buf[:cnt]) # unpack as little endian words
res = s + sum(a)
if cnt != n:
res += compat_ord(buf[-1])
return res
def in_cksum_done(s):
s = (s >> 16) + (s & 0xffff)
s += (s >> 16)
return ntole(~s & 0xffff)
def in_cksum(buf):
"""Return computed Internet checksum."""
return in_cksum_done(in_cksum_add(0, buf))
def test_utils():
__buf = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e'
__hd = ' 0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e ...............'
h = hexdump(__buf)
assert (h == __hd)
assert in_cksum_add(0, __buf) == 12600 # endianness
c = in_cksum(__buf)
assert (c == 51150)
# test Packet.__getitem__ and __contains__ methods
def test_getitem_contains():
import pytest
class Foo(Packet):
__hdr__ = (('foo', 'I', 0),)
class Bar(Packet):
__hdr__ = (('bar', 'I', 0),)
class Baz(Packet):
__hdr__ = (('baz', 'I', 0),)
class Zeb(Packet):
pass
ff = Foo(foo=1, data=Bar(bar=2, data=Baz(attr=Zeb())))
# __contains__
assert Bar in ff
assert Baz in ff
assert Baz in ff.data
assert Zeb not in ff
assert Zeb not in Baz()
# __getitem__
assert isinstance(ff[Bar], Bar)
assert isinstance(ff[Baz], Baz)
assert isinstance(ff[Bar][Baz], Baz)
with pytest.raises(KeyError):
ff[Baz][Bar]
with pytest.raises(KeyError):
ff[Zeb]
with pytest.raises(KeyError):
Bar()[Baz]
def test_pack_hdr_overflow():
"""Try to fit too much data into struct packing"""
import pytest
class Foo(Packet):
__hdr__ = (
('foo', 'I', 1),
('bar', 'I', (1, 2)),
)
foo = Foo(foo=2**32)
with pytest.raises(PackError):
bytes(foo)
def test_bit_fields_overflow():
"""Try to fit too much data into too few bits"""
import pytest
class Foo(Packet):
__hdr__ = (
('_a_b', 'B', 0),
)
__bit_fields__ = {
'_a_b': (
('a', 2),
('b', 6),
)
}
foo = Foo()
with pytest.raises(ValueError):
foo.a = 5
def test_pack_hdr_tuple():
"""Test the unpacking of a tuple for a single format string"""
class Foo(Packet):
__hdr__ = (
('bar', 'II', (1, 2)),
)
foo = Foo()
b = bytes(foo)
assert b == b'\x00\x00\x00\x01\x00\x00\x00\x02'
def test_unpacking_failure():
# during dynamic-sized unpacking in the subclass there may be struct.errors raised,
# but if the header has unpacked correctly, a different error is raised by the superclass
import pytest
class TestPacket(Packet):
__hdr__ = (('test', 'B', 0),)
def unpack(self, buf):
Packet.unpack(self, buf)
self.attribute = struct.unpack('B', buf[1:])
with pytest.raises(UnpackError, match="invalid TestPacket: "):
TestPacket(b'\x00') # header will unpack successfully
def test_repr():
"""complex test for __repr__, __public_fields__"""
class TestPacket(Packet):
__hdr__ = (
('_a_b', 'B', 1), # 'a' and 'b' bit fields
('_rsv', 'B', 0), # hidden reserved field
('_c_flag', 'B', 1), # 'c_flag' property
('d', 'B', 0) # regular field
)
__bit_fields__ = {
'_a_b': (
('a', 4),
('b', 4),
),
}
@property
def c_flag(self):
return (self.a | self.b)
# init with default values
test_packet = TestPacket()
# test repr with all default values so expect no output
# (except for the explicitly defined property, where dpkt doesn't process defaults yet)
assert repr(test_packet) == "TestPacket(c_flag=1)"
# init with non-default values
test_packet = TestPacket(b'\x12\x11\x00\x04')
# ensure the display fields were cached and propagated via class attribute
assert test_packet.__public_fields__ == ['a', 'b', 'c_flag', 'd']
# verify repr
assert repr(test_packet) == "TestPacket(a=1, b=2, c_flag=3, d=4)"

View File

@@ -0,0 +1,65 @@
# $Id: dtp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Dynamic Trunking Protocol."""
from __future__ import absolute_import
import struct
from . import dpkt
TRUNK_NAME = 0x01
MAC_ADDR = 0x04
class DTP(dpkt.Packet):
"""Dynamic Trunking Protocol.
The Dynamic Trunking Protocol (DTP) is a proprietary networking protocol developed by Cisco Systems for the purpose
of negotiating trunking on a link between two VLAN-aware switches, and for negotiating the type of trunking
encapsulation to be used. It works on Layer 2 of the OSI model. VLAN trunks formed using DTP may utilize either
IEEE 802.1Q or Cisco ISL trunking protocols.
Attributes:
__hdr__: Header fields of DTP.
v: (int) Version. (1 byte)
"""
__hdr__ = (
('v', 'B', 0),
) # rest is TLVs
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
buf = self.data
tvs = []
while buf:
t, l_ = struct.unpack('>HH', buf[:4])
v, buf = buf[4:4 + l_], buf[4 + l_:]
tvs.append((t, v))
self.data = tvs
def __bytes__(self):
return b''.join([struct.pack('>HH', t, len(v)) + v for t, v in self.data])
def test_creation():
dtp1 = DTP()
assert dtp1.v == 0
from binascii import unhexlify
buf = unhexlify(
'04'
'0001' # type
'0002' # length
'1234' # value
)
dtp2 = DTP(buf)
assert dtp2.v == 4
assert len(dtp2.data) == 1
tlvs = dtp2.data
tlv = tlvs[0]
key, value = tlv
assert key == 1
assert value == unhexlify('1234')
assert bytes(dtp2) == buf[1:]

View File

@@ -0,0 +1,77 @@
"""Extreme Discovery Protocol."""
from __future__ import absolute_import
import dpkt
class EDP(dpkt.Packet):
__hdr__ = (
('version', 'B', 1),
('reserved', 'B', 0),
('hlen', 'H', 0),
('sum', 'H', 0),
('seq', 'H', 0),
('mid', 'H', 0),
('mac', '6s', b'')
)
def __bytes__(self):
if not self.sum:
self.sum = dpkt.in_cksum(dpkt.Packet.__bytes__(self))
return dpkt.Packet.__bytes__(self)
class TestEDP(object):
"""
Test basic EDP functionality.
"""
@classmethod
def setup_class(cls):
from binascii import unhexlify
cls.buf = unhexlify(
'01' # version
'00' # reserved
'013c' # hlen
'9e76' # sum
'001b' # seq
'0000' # mid
'080027' # mac
'2d90ed990200240000000000000000000000000f020207000000000000000000000000000000009901010445584f532d32000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000099000004'
)
cls.p = EDP(cls.buf)
def test_version(self):
assert (self.p.version == 1)
def test_reserved(self):
assert (self.p.reserved == 0)
def test_hlen(self):
assert (self.p.hlen == 316)
def test_sum(self):
assert (self.p.sum == 40566)
def test_seq(self):
assert (self.p.seq == 27)
def test_mid(self):
assert (self.p.mid == 0)
def test_mac(self):
assert (self.p.mac == b"\x08\x00'-\x90\xed")
def test_bytes(self):
assert bytes(self.p) == self.buf
# force recalculation of the checksum
edp = EDP(self.buf)
edp.sum = 0
assert edp.sum == 0
assert bytes(edp) == self.buf

View File

@@ -0,0 +1,26 @@
# $Id: esp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Encapsulated Security Protocol."""
from __future__ import absolute_import
from . import dpkt
class ESP(dpkt.Packet):
"""Encapsulated Security Protocol.
Encapsulating Security Payload (ESP) is a member of the Internet Protocol Security (IPsec) set of protocols that
encrypt and authenticate the packets of data between computers using a Virtual Private Network (VPN). The focus
and layer on which ESP operates makes it possible for VPNs to function securely.
Attributes:
__hdr__: Header fields of ESP.
spi: (int): Security Parameters Index. An arbitrary value that, in combination with the destination
IP address and security protocol (ESP), uniquely identifies the SA for this datagram. (4 bytes)
spi: (int): Sequence number. This field contains a monotonically increasing counter value. (4 bytes)
"""
__hdr__ = (
('spi', 'I', 0),
('seq', 'I', 0)
)

View File

@@ -0,0 +1,875 @@
# $Id: ethernet.py 65 2010-03-26 02:53:51Z dugsong $
# -*- coding: utf-8 -*-
"""
Ethernet II, LLC (802.3+802.2), LLC/SNAP, and Novell raw 802.3,
with automatic 802.1q, MPLS, PPPoE, and Cisco ISL decapsulation.
"""
from __future__ import print_function
from __future__ import absolute_import
import struct
from zlib import crc32
from . import dpkt
from . import llc
from .utils import mac_to_str
from .compat import compat_ord, iteritems, isstr
ETH_CRC_LEN = 4
ETH_HDR_LEN = 14
ETH_LEN_MIN = 64 # minimum frame length with CRC
ETH_LEN_MAX = 1518 # maximum frame length with CRC
ETH_MTU = (ETH_LEN_MAX - ETH_HDR_LEN - ETH_CRC_LEN)
ETH_MIN = (ETH_LEN_MIN - ETH_HDR_LEN - ETH_CRC_LEN)
# Ethernet payload types - http://standards.ieee.org/regauth/ethertype
ETH_TYPE_UNKNOWN = 0x0000
ETH_TYPE_EDP = 0x00bb # Extreme Networks Discovery Protocol
ETH_TYPE_PUP = 0x0200 # PUP protocol
ETH_TYPE_IP = 0x0800 # IP protocol
ETH_TYPE_ARP = 0x0806 # address resolution protocol
ETH_TYPE_AOE = 0x88a2 # AoE protocol
ETH_TYPE_CDP = 0x2000 # Cisco Discovery Protocol
ETH_TYPE_DTP = 0x2004 # Cisco Dynamic Trunking Protocol
ETH_TYPE_REVARP = 0x8035 # reverse addr resolution protocol
ETH_TYPE_8021Q = 0x8100 # IEEE 802.1Q VLAN tagging
ETH_TYPE_8021AD = 0x88a8 # IEEE 802.1ad
ETH_TYPE_QINQ1 = 0x9100 # Legacy QinQ
ETH_TYPE_QINQ2 = 0x9200 # Legacy QinQ
ETH_TYPE_IPX = 0x8137 # Internetwork Packet Exchange
ETH_TYPE_IP6 = 0x86DD # IPv6 protocol
ETH_TYPE_PPP = 0x880B # PPP
ETH_TYPE_MPLS = 0x8847 # MPLS
ETH_TYPE_MPLS_MCAST = 0x8848 # MPLS Multicast
ETH_TYPE_PPPoE_DISC = 0x8863 # PPP Over Ethernet Discovery Stage
ETH_TYPE_PPPoE = 0x8864 # PPP Over Ethernet Session Stage
ETH_TYPE_LLDP = 0x88CC # Link Layer Discovery Protocol
ETH_TYPE_TEB = 0x6558 # Transparent Ethernet Bridging
ETH_TYPE_PROFINET = 0x8892 # PROFINET protocol
# all QinQ types for fast checking
_ETH_TYPES_QINQ = frozenset([ETH_TYPE_8021Q, ETH_TYPE_8021AD, ETH_TYPE_QINQ1, ETH_TYPE_QINQ2])
class Ethernet(dpkt.Packet):
"""Ethernet.
Ethernet II, LLC (802.3+802.2), LLC/SNAP, and Novell raw 802.3,
with automatic 802.1q, MPLS, PPPoE, and Cisco ISL decapsulation.
Attributes:
__hdr__: Header fields of Ethernet.
dst: (bytes): Destination MAC address
src: (bytes): Source MAC address
type: (int): Ethernet frame type (Ethernet II, Novell raw IEEE 802.3, IEEE 802.2 LLC, IEEE 802.2 SNAP)
"""
__hdr__ = (
('dst', '6s', b''),
('src', '6s', b''),
('type', 'H', ETH_TYPE_IP)
)
_typesw = {}
_typesw_rev = {} # reverse mapping
__pprint_funcs__ = {
'dst': mac_to_str,
'src': mac_to_str,
}
def __init__(self, *args, **kwargs):
self._next_type = None
dpkt.Packet.__init__(self, *args, **kwargs)
# if data was given in kwargs, try to unpack it
if self.data:
if isstr(self.data) or isinstance(self.data, bytes):
self._unpack_data(self.data)
def _unpack_data(self, buf):
# unpack vlan tag and mpls label stacks
if self._next_type in _ETH_TYPES_QINQ:
self.vlan_tags = []
# support up to 2 tags (double tagging aka QinQ)
for _ in range(2):
tag = VLANtag8021Q(buf)
buf = buf[tag.__hdr_len__:]
self.vlan_tags.append(tag)
self._next_type = tag.type
if self._next_type != ETH_TYPE_8021Q:
break
# backward compatibility, use the 1st tag
self.vlanid, self.priority, self.cfi = self.vlan_tags[0].as_tuple()
elif self._next_type == ETH_TYPE_MPLS or self._next_type == ETH_TYPE_MPLS_MCAST:
self.labels = [] # old list containing labels as tuples
self.mpls_labels = [] # new list containing labels as instances of MPLSlabel
# XXX - max # of labels is undefined, just use 24
for i in range(24):
lbl = MPLSlabel(buf)
buf = buf[lbl.__hdr_len__:]
self.mpls_labels.append(lbl)
self.labels.append(lbl.as_tuple())
if lbl.s: # bottom of stack
break
# poor man's heuristics to guessing the next type
if compat_ord(buf[0]) == 0x45: # IP version 4 + header len 20 bytes
self._next_type = ETH_TYPE_IP
elif compat_ord(buf[0]) & 0xf0 == 0x60: # IP version 6
self._next_type = ETH_TYPE_IP6
# pseudowire Ethernet
elif len(buf) >= self.__hdr_len__:
if buf[:2] == b'\x00\x00': # looks like the control word (ECW)
buf = buf[4:] # skip the ECW
self._next_type = ETH_TYPE_TEB # re-use TEB class mapping to decode Ethernet
try:
eth_type = self._next_type or self.type
self.data = self._typesw[eth_type](buf)
setattr(self, self.data.__class__.__name__.lower(), self.data)
except (KeyError, dpkt.UnpackError):
self.data = buf
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
if self.type > 1500:
# Ethernet II
self._next_type = self.type
self._unpack_data(self.data)
elif (self.dst.startswith(b'\x01\x00\x0c\x00\x00') or
self.dst.startswith(b'\x03\x00\x0c\x00\x00')):
# Cisco ISL
tag = VLANtagISL(buf)
buf = buf[tag.__hdr_len__:]
self.vlan_tags = [tag]
self.vlan = tag.id # backward compatibility
self.unpack(buf)
elif self.data.startswith(b'\xff\xff'):
# Novell "raw" 802.3
self.type = ETH_TYPE_IPX
self.data = self.ipx = self._typesw[ETH_TYPE_IPX](self.data[2:])
elif self.type == ETH_TYPE_UNKNOWN:
# Unknown type, assume Ethernet
self._unpack_data(self.data)
else:
# IEEE 802.3 Ethernet - LLC
# try to unpack FCS, padding and trailer here
# we follow a heuristic approach similar to that of Wireshark
# size of eth body, not including the header
eth_len = self.len = self.type
# actual size of the remaining data, could include eth body, padding, fcs, trailer
data_len = len(self.data)
if data_len > eth_len:
# everything after eth body
tail = self.data[eth_len:]
# could be padding + fcs, possibly trailer
if len(tail) > 4:
# determine size of padding
if eth_len < 46: # 46=60-14; 14=size of eth hdr; all padded to 60 bytes
pad_len = 46 - eth_len
padding = tail[:pad_len]
# heuristic
if padding == pad_len * b'\x00': # padding is likely zeroes
self.padding = padding
tail = tail[pad_len:]
# else proceed to decode as fcs+trailer
# 4 bytes FCS and possible trailer
if len(tail) >= 4:
self.fcs = struct.unpack('>I', tail[:4])[0]
tail = tail[4:]
if tail:
self.trailer = tail
self.data = self.llc = llc.LLC(self.data[:eth_len])
def pack_hdr(self):
tags_buf = b''
new_type = self.type # replacement self.type when packing eth header
is_isl = False # ISL wraps Ethernet, this determines order of packing
if getattr(self, 'mpls_labels', None):
# mark all labels with s=0, last one with s=1
for lbl in self.mpls_labels:
lbl.s = 0
lbl.s = 1
# set encapsulation type
if new_type not in (ETH_TYPE_MPLS, ETH_TYPE_MPLS_MCAST):
new_type = ETH_TYPE_MPLS
tags_buf = b''.join(lbl.pack_hdr() for lbl in self.mpls_labels)
elif getattr(self, 'vlan_tags', None):
# set last tag type to next layer pointed by self.data
last_tag_type = self.type # default
if isinstance(self.data, dpkt.Packet):
last_tag_type = self._typesw_rev.get(self.data.__class__, self.type)
# set encapsulation types
t1 = self.vlan_tags[0]
if len(self.vlan_tags) == 1:
if isinstance(t1, VLANtag8021Q):
if new_type not in _ETH_TYPES_QINQ: # preserve the type if already set
new_type = ETH_TYPE_8021Q
t1.type = last_tag_type
elif isinstance(t1, VLANtagISL):
t1.type = 0 # 0 means Ethernet
is_isl = True
elif len(self.vlan_tags) == 2:
t2 = self.vlan_tags[1]
if isinstance(t1, VLANtag8021Q) and isinstance(t2, VLANtag8021Q):
t1.type = ETH_TYPE_8021Q
if new_type not in _ETH_TYPES_QINQ:
new_type = ETH_TYPE_8021AD
t2.type = last_tag_type
else:
raise dpkt.PackError('maximum is 2 VLAN tags per Ethernet frame')
tags_buf = b''.join(tag.pack_hdr() for tag in self.vlan_tags)
# initial type is based on next layer, pointed by self.data;
# try to find an ETH_TYPE matching the data class
elif isinstance(self.data, dpkt.Packet):
new_type = self._typesw_rev.get(self.data.__class__, new_type)
# if self.data is LLC then this is IEEE 802.3 Ethernet and self.type
# then actually encodes the length of data
if isinstance(self.data, llc.LLC):
new_type = len(self.data)
hdr_buf = dpkt.Packet.pack_hdr(self)[:-2] + struct.pack('>H', new_type)
if not is_isl:
return hdr_buf + tags_buf
else:
return tags_buf + hdr_buf
def __bytes__(self):
tail = b''
if isinstance(self.data, llc.LLC):
fcs = b''
if hasattr(self, 'fcs'):
if self.fcs:
fcs = self.fcs
else:
# if fcs field is present but 0/None, then compute it and add to the tail
fcs_buf = self.pack_hdr() + bytes(self.data)
# if ISL header is present, exclude it from the calculation
if getattr(self, 'vlan_tags', None):
if isinstance(self.vlan_tags[0], VLANtagISL):
fcs_buf = fcs_buf[VLANtagISL.__hdr_len__:]
fcs_buf += getattr(self, 'padding', b'')
revcrc = crc32(fcs_buf) & 0xffffffff
fcs = struct.unpack('<I', struct.pack('>I', revcrc))[0] # bswap32
fcs = struct.pack('>I', fcs)
tail = getattr(self, 'padding', b'') + fcs + getattr(self, 'trailer', b'')
return bytes(dpkt.Packet.__bytes__(self) + tail)
def __len__(self):
tags = getattr(self, 'mpls_labels', []) + getattr(self, 'vlan_tags', [])
_len = dpkt.Packet.__len__(self) + sum(t.__hdr_len__ for t in tags)
if isinstance(self.data, llc.LLC):
_len += len(getattr(self, 'padding', b''))
if hasattr(self, 'fcs'):
_len += 4
_len += len(getattr(self, 'trailer', b''))
return _len
@classmethod
def set_type(cls, t, pktclass):
cls._typesw[t] = pktclass
cls._typesw_rev[pktclass] = t
@classmethod
def get_type(cls, t):
return cls._typesw[t]
@classmethod
def get_type_rev(cls, k):
return cls._typesw_rev[k]
# XXX - auto-load Ethernet dispatch table from ETH_TYPE_* definitions
def __load_types():
g = globals()
for k, v in iteritems(g):
if k.startswith('ETH_TYPE_'):
name = k[9:]
modname = name.lower()
try:
mod = __import__(modname, g, level=1)
Ethernet.set_type(v, getattr(mod, name))
except (ImportError, AttributeError):
continue
# add any special cases below
Ethernet.set_type(ETH_TYPE_TEB, Ethernet)
def _mod_init():
"""Post-initialization called when all dpkt modules are fully loaded"""
if not Ethernet._typesw:
__load_types()
# Misc protocols
class MPLSlabel(dpkt.Packet):
"""A single entry in MPLS label stack"""
__hdr__ = (
('_val_exp_s_ttl', 'I', 0),
)
# field names are according to RFC3032
__bit_fields__ = {
'_val_exp_s_ttl': (
('val', 20), # label value, 20 bits
('exp', 3), # experimental use, 3 bits
('s', 1), # bottom of stack flag, 1 bit
('ttl', 8), # time to live, 8 bits
)
}
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.data = b''
def as_tuple(self): # backward-compatible representation
return (self.val, self.exp, self.ttl)
class VLANtag8021Q(dpkt.Packet):
"""IEEE 802.1q VLAN tag"""
__hdr__ = (
('_pri_cfi_id', 'H', 0),
('type', 'H', ETH_TYPE_IP)
)
__bit_fields__ = {
'_pri_cfi_id': (
('pri', 3), # priority, 3 bits
('cfi', 1), # canonical format indicator, 1 bit
('id', 12), # VLAN id, 12 bits
)
}
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.data = b''
def as_tuple(self):
return (self.id, self.pri, self.cfi)
class VLANtagISL(dpkt.Packet):
"""Cisco Inter-Switch Link VLAN tag"""
__hdr__ = (
('da', '5s', b'\x01\x00\x0c\x00\x00'),
('_type_pri', 'B', 3),
('sa', '6s', b''),
('len', 'H', 0),
('snap', '3s', b'\xaa\xaa\x03'),
('hsa', '3s', b'\x00\x00\x0c'),
('_id_bpdu', 'H', 0),
('indx', 'H', 0),
('res', 'H', 0)
)
__bit_fields__ = {
'_type_pri': (
('type', 4), # encapsulation type, 4 bits; 0 means Ethernet
('pri', 4), # user defined bits, 2 lo bits are used; means priority
),
'_id_bpdu': (
('id', 15), # vlan id, 15 bits
('bpdu', 1), # bridge protocol data unit indicator
)
}
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.data = b''
# Unit tests
def test_eth():
from . import ip6
from . import tcp
s = (b'\x00\xb0\xd0\xe1\x80\x72\x00\x11\x24\x8c\x11\xde\x86\xdd\x60\x00\x00\x00'
b'\x00\x28\x06\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11\x24\xff\xfe\x8c'
b'\x11\xde\xfe\x80\x00\x00\x00\x00\x00\x00\x02\xb0\xd0\xff\xfe\xe1\x80\x72'
b'\xcd\xd3\x00\x16\xff\x50\xd7\x13\x00\x00\x00\x00\xa0\x02\xff\xff\x67\xd3'
b'\x00\x00\x02\x04\x05\xa0\x01\x03\x03\x00\x01\x01\x08\x0a\x7d\x18\x3a\x61'
b'\x00\x00\x00\x00')
eth = Ethernet(s)
assert eth
assert isinstance(eth.data, ip6.IP6)
assert isinstance(eth.data.data, tcp.TCP)
assert str(eth) == str(s)
assert len(eth) == len(s)
def test_eth_zero_ethtype():
s = (b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x89\x12\x04')
eth = Ethernet(s)
assert eth
assert eth.type == ETH_TYPE_UNKNOWN
assert str(eth) == str(s)
assert len(eth) == len(s)
def test_eth_init_with_data():
# initialize with a data string, test that it gets unpacked
from . import arp
eth1 = Ethernet(
dst=b'PQRSTU', src=b'ABCDEF', type=ETH_TYPE_ARP,
data=b'\x00\x01\x08\x00\x06\x04\x00\x01123456abcd7890abwxyz')
assert isinstance(eth1.data, arp.ARP)
# now initialize with a class, test packing
eth2 = Ethernet(
dst=b'PQRSTU', src=b'ABCDEF',
data=arp.ARP(sha=b'123456', spa=b'abcd', tha=b'7890ab', tpa=b'wxyz'))
assert str(eth1) == str(eth2)
assert len(eth1) == len(eth2)
def test_mpls_label():
s = b'\x00\x01\x0b\xff'
m = MPLSlabel(s)
assert m.val == 16
assert m.exp == 5
assert m.s == 1
assert m.ttl == 255
assert str(m) == str(s)
assert len(m) == len(s)
def test_802dot1q_tag():
s = b'\xa0\x76\x01\x65'
t = VLANtag8021Q(s)
assert t.pri == 5
assert t.cfi == 0
assert t.id == 118
assert str(t) == str(s)
t.cfi = 1
assert str(t) == str(b'\xb0\x76\x01\x65')
assert len(t) == len(s)
def test_isl_tag():
s = (b'\x01\x00\x0c\x00\x00\x03\x00\x02\xfd\x2c\xb8\x97\x00\x00\xaa\xaa\x03\x00\x00\x00\x04\x57'
b'\x00\x00\x00\x00')
t = VLANtagISL(s)
assert t.pri == 3
assert t.id == 555
assert t.bpdu == 1
assert str(t) == str(s)
assert len(t) == len(s)
def test_eth_802dot1q():
from . import ip
s = (b'\x00\x60\x08\x9f\xb1\xf3\x00\x40\x05\x40\xef\x24\x81\x00\x90\x20\x08'
b'\x00\x45\x00\x00\x34\x3b\x64\x40\x00\x40\x06\xb7\x9b\x83\x97\x20\x81'
b'\x83\x97\x20\x15\x04\x95\x17\x70\x51\xd4\xee\x9c\x51\xa5\x5b\x36\x80'
b'\x10\x7c\x70\x12\xc7\x00\x00\x01\x01\x08\x0a\x00\x04\xf0\xd4\x01\x99'
b'\xa3\xfd')
eth = Ethernet(s)
assert eth.cfi == 1
assert eth.vlanid == 32
assert eth.priority == 4
assert len(eth.vlan_tags) == 1
assert eth.vlan_tags[0].type == ETH_TYPE_IP
assert isinstance(eth.data, ip.IP)
# construction
assert str(eth) == str(s), 'pack 1'
assert str(eth) == str(s), 'pack 2'
assert len(eth) == len(s)
# construction with kwargs
eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags, data=eth.data)
assert str(eth2) == str(s)
# construction w/o the tag
del eth.vlan_tags, eth.cfi, eth.vlanid, eth.priority
assert str(eth) == str(s[:12] + b'\x08\x00' + s[18:])
def test_eth_802dot1q_stacked(): # 2 VLAN tags
from binascii import unhexlify
import pytest
from . import ip
s = unhexlify(
'001bd41ba4d80013c3dfae18810000768100000a0800'
'45000064000f0000ff01929b0a760a010a760a020800'
'ceb70003000000000000001faf70abcdabcdabcdabcd'
'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'
'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'
'abcdabcdabcdabcdabcdabcd'
)
eth = Ethernet(s)
assert eth.type == ETH_TYPE_8021Q
assert len(eth.vlan_tags) == 2
assert eth.vlan_tags[0].id == 118
assert eth.vlan_tags[1].id == 10
assert eth.vlan_tags[0].type == ETH_TYPE_8021Q
assert eth.vlan_tags[1].type == ETH_TYPE_IP
assert [t.as_tuple() for t in eth.vlan_tags] == [(118, 0, 0), (10, 0, 0)]
assert isinstance(eth.data, ip.IP)
# construction
assert len(eth) == len(s)
assert bytes(eth) == s
# test packing failure with too many tags
eth.vlan_tags += eth.vlan_tags[0] # just duplicate the first tag
with pytest.raises(dpkt.PackError, match='maximum is 2 VLAN tags per Ethernet frame'):
bytes(eth)
# construction with kwargs
eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags[:2], data=eth.data)
# construction sets ip.type to 802.1ad instead of 802.1q so account for it
assert str(eth2) == str(s[:12] + b'\x88\xa8' + s[14:])
# construction w/o the tags
del eth.vlan_tags, eth.cfi, eth.vlanid, eth.priority
assert str(eth) == str(s[:12] + b'\x08\x00' + s[22:])
def test_eth_vlan_arp():
from . import arp
# 2 VLAN tags + ARP
s = (b'\xff\xff\xff\xff\xff\xff\xca\x03\x0d\xb4\x00\x1c\x81\x00\x00\x64\x81\x00\x00\xc8\x08\x06'
b'\x00\x01\x08\x00\x06\x04\x00\x01\xca\x03\x0d\xb4\x00\x1c\xc0\xa8\x02\xc8\x00\x00\x00\x00'
b'\x00\x00\xc0\xa8\x02\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
eth = Ethernet(s)
assert len(eth.vlan_tags) == 2
assert eth.vlan_tags[0].type == ETH_TYPE_8021Q
assert eth.vlan_tags[1].type == ETH_TYPE_ARP
assert isinstance(eth.data, arp.ARP)
def test_eth_mpls_stacked(): # Eth - MPLS - MPLS - IP - ICMP
from . import ip
from . import icmp
s = (b'\x00\x30\x96\xe6\xfc\x39\x00\x30\x96\x05\x28\x38\x88\x47\x00\x01\x20\xff\x00\x01\x01\xff'
b'\x45\x00\x00\x64\x00\x50\x00\x00\xff\x01\xa7\x06\x0a\x1f\x00\x01\x0a\x22\x00\x01\x08\x00'
b'\xbd\x11\x0f\x65\x12\xa0\x00\x00\x00\x00\x00\x53\x9e\xe0' + b'\xab\xcd' * 32)
eth = Ethernet(s)
assert len(eth.mpls_labels) == 2
assert eth.mpls_labels[0].val == 18
assert eth.mpls_labels[1].val == 16
assert eth.labels == [(18, 0, 255), (16, 0, 255)]
assert isinstance(eth.data, ip.IP)
assert isinstance(eth.data.data, icmp.ICMP)
# exercise .pprint() for the coverage tests
eth.pprint()
# construction
assert str(eth) == str(s), 'pack 1'
assert str(eth) == str(s), 'pack 2'
assert len(eth) == len(s)
# construction with kwargs
eth2 = Ethernet(src=eth.src, dst=eth.dst, mpls_labels=eth.mpls_labels, data=eth.data)
assert str(eth2) == str(s)
# construction w/o labels
del eth.labels, eth.mpls_labels
assert str(eth) == str(s[:12] + b'\x08\x00' + s[22:])
def test_eth_mpls_ipv6(): # Eth - MPLS - IP6 - TCP
from . import ip6
from . import tcp
s = ( b'\x00\x30\x96\xe6\xfc\x39\x00\x30\x96\x05\x28\x38\x88\x47\x00\x01'
b'\x01\xff\x62\x8c\xed\x7b\x00\x28\x06\xfd\x22\x22\x22\x22\x03\x3f'
b'\x53\xd3\x48\xfb\x8b\x5a\x41\x7f\xe6\x17\x11\x11\x11\x11\x40\x0b'
b'\x08\x09\x00\x00\x00\x00\x00\x00\x20\x0e\xa1\x8e\x01\xbb\xd6\xde'
b'\x73\x17\x00\x00\x00\x00\xa0\x02\xff\xff\x58\x7f\x00\x00\x02\x04'
b'\x05\x8c\x04\x02\x08\x0a\x69\x23\xe8\x63\x00\x00\x00\x00\x01\x03'
b'\x03\x0a\xaf\x9c\xb6\x93')
eth = Ethernet(s)
assert len(eth.mpls_labels) == 1
assert eth.mpls_labels[0].val == 16
assert eth.labels == [(16, 0, 255)]
assert isinstance(eth.data, ip6.IP6)
assert isinstance(eth.data.data, tcp.TCP)
def test_isl_eth_llc_stp(): # ISL - 802.3 Ethernet(w/FCS) - LLC - STP
from . import stp
s = (b'\x01\x00\x0c\x00\x00\x03\x00\x02\xfd\x2c\xb8\x97\x00\x00\xaa\xaa\x03\x00\x00\x00\x02\x9b'
b'\x00\x00\x00\x00\x01\x80\xc2\x00\x00\x00\x00\x02\xfd\x2c\xb8\x98\x00\x26\x42\x42\x03\x00'
b'\x00\x00\x00\x00\x80\x00\x00\x02\xfd\x2c\xb8\x83\x00\x00\x00\x00\x80\x00\x00\x02\xfd\x2c'
b'\xb8\x83\x80\x26\x00\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x41\xc6'
b'\x75\xd6')
eth = Ethernet(s)
assert eth.vlan == 333
assert len(eth.vlan_tags) == 1
assert eth.vlan_tags[0].id == 333
assert eth.vlan_tags[0].pri == 3
# check that FCS and padding were decoded
assert eth.fcs == 0x41c675d6
assert eth.padding == b'\x00' * 8
# stack
assert isinstance(eth.data, llc.LLC)
assert isinstance(eth.data.data, stp.STP)
# construction
assert str(eth) == str(s), 'pack 1'
assert str(eth) == str(s), 'pack 2'
assert len(eth) == len(s)
# construction with kwargs
eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags, data=eth.data)
eth2.padding = b'\x00' * 8
# test FCS computation
eth2.fcs = None
assert str(eth2) == str(s)
# TODO: test padding construction
# eth2.padding = None
# assert str(eth2) == str(s)
# construction w/o the ISL tag
del eth.vlan_tags, eth.vlan
assert str(eth) == str(s[26:])
def test_eth_llc_snap_cdp(): # 802.3 Ethernet - LLC/SNAP - CDP
from . import cdp
s = (b'\x01\x00\x0c\xcc\xcc\xcc\xc4\x022k\x00\x00\x01T\xaa\xaa\x03\x00\x00\x0c \x00\x02\xb4,B'
b'\x00\x01\x00\x06R2\x00\x05\x00\xffCisco IOS Software, 3700 Software (C3745-ADVENTERPRI'
b'SEK9_SNA-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.'
b'cisco.com/techsupport\nCopyright (c) 1986-2010 by Cisco Systems, Inc.\nCompiled Wed 18'
b'-Aug-10 08:18 by prod_rel_team\x00\x06\x00\x0eCisco 3745\x00\x02\x00\x11\x00\x00\x00\x01'
b'\x01\x01\xcc\x00\x04\n\x00\x00\x02\x00\x03\x00\x13FastEthernet0/0\x00\x04\x00\x08\x00'
b'\x00\x00)\x00\t\x00\x04\x00\x0b\x00\x05\x00')
eth = Ethernet(s)
# stack
assert isinstance(eth.data, llc.LLC)
assert isinstance(eth.data.data, cdp.CDP)
assert len(eth.data.data.tlvs) == 8 # number of CDP TLVs; ensures they are decoded
assert str(eth) == str(s), 'pack 1'
assert str(eth) == str(s), 'pack 2'
assert len(eth) == len(s)
def test_eth_llc_ipx(): # 802.3 Ethernet - LLC - IPX
from . import ipx
s = (b'\xff\xff\xff\xff\xff\xff\x00\xb0\xd0\x22\xf7\xf3\x00\x54\xe0\xe0\x03\xff\xff\x00\x50\x00'
b'\x14\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x04\x55\x00\x00\x00\x00\x00\xb0\xd0\x22\xf7'
b'\xf3\x04\x55\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x02\x5f\x5f\x4d\x53\x42'
b'\x52\x4f\x57\x53\x45\x5f\x5f\x02\x01\x00')
eth = Ethernet(s)
# stack
assert isinstance(eth.data, llc.LLC)
assert isinstance(eth.data.data, ipx.IPX)
assert eth.data.data.pt == 0x14
assert str(eth) == str(s), 'pack 1'
assert str(eth) == str(s), 'pack 2'
assert len(eth) == len(s)
def test_eth_pppoe(): # Eth - PPPoE - IPv6 - UDP - DHCP6
from . import ip6
from . import ppp
from . import pppoe
from . import udp
s = (b'\xca\x01\x0e\x88\x00\x06\xcc\x05\x0e\x88\x00\x00\x88\x64\x11\x00\x00\x11\x00\x64\x57\x6e'
b'\x00\x00\x00\x00\x3a\x11\xff\xfe\x80\x00\x00\x00\x00\x00\x00\xce\x05\x0e\xff\xfe\x88\x00'
b'\x00\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x02\x22\x02\x23\x00'
b'\x3a\x1a\x67\x01\xfc\x24\xab\x00\x08\x00\x02\x05\xe9\x00\x01\x00\x0a\x00\x03\x00\x01\xcc'
b'\x05\x0e\x88\x00\x00\x00\x06\x00\x06\x00\x19\x00\x17\x00\x18\x00\x19\x00\x0c\x00\x09\x00'
b'\x01\x00\x00\x00\x00\x00\x00\x00\x00')
eth = Ethernet(s)
# stack
assert isinstance(eth.data, pppoe.PPPoE)
assert isinstance(eth.data.data, ppp.PPP)
assert isinstance(eth.data.data.data, ip6.IP6)
assert isinstance(eth.data.data.data.data, udp.UDP)
# construction
assert str(eth) == str(s)
assert len(eth) == len(s)
def test_eth_2mpls_ecw_eth_llc_stp(): # Eth - MPLS - MPLS - PW ECW - 802.3 Eth(no FCS) - LLC - STP
from . import stp
s = (b'\xcc\x01\x0d\x5c\x00\x10\xcc\x00\x0d\x5c\x00\x10\x88\x47\x00\x01\x20\xfe\x00\x01\x01\xff'
b'\x00\x00\x00\x00\x01\x80\xc2\x00\x00\x00\xcc\x04\x0d\x5c\xf0\x00\x00\x26\x42\x42\x03\x00'
b'\x00\x00\x00\x00\x80\x00\xcc\x04\x0d\x5c\x00\x00\x00\x00\x00\x00\x80\x00\xcc\x04\x0d\x5c'
b'\x00\x00\x80\x01\x00\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00')
eth = Ethernet(s)
assert len(eth.mpls_labels) == 2
assert eth.mpls_labels[0].val == 18
assert eth.mpls_labels[1].val == 16
# stack
eth2 = eth.data
assert isinstance(eth2, Ethernet)
assert eth2.len == 38 # 802.3 Ethernet
# no FCS, no trailer, just 8 bytes of padding (60=38+14+8)
assert not hasattr(eth2, 'fcs')
assert eth2.padding == b'\x00' * 8
assert isinstance(eth2.data, llc.LLC)
assert isinstance(eth2.data.data, stp.STP)
assert eth2.data.data.port_id == 0x8001
# construction
# XXX - FIXME: make packing account for the ECW
# assert str(eth) == str(s)
# QinQ: Eth - 802.1ad - 802.1Q - IP
def test_eth_802dot1ad_802dot1q_ip():
from . import ip
s = (b'\x00\x10\x94\x00\x00\x0c\x00\x10\x94\x00\x00\x14\x88\xa8\x00\x1e\x81\x00\x00\x64\x08\x00'
b'\x45\x00\x05\xc2\x54\xb0\x00\x00\xff\xfd\xdd\xbf\xc0\x55\x01\x16\xc0\x55\x01\x0e' +
1434 * b'\x00' + b'\x4f\xdc\xcd\x64\x20\x8d\xb6\x4e\xa8\x45\xf8\x80\xdd\x0c\xf9\x72\xc4'
b'\xd0\xcf\xcb\x46\x6d\x62\x7a')
eth = Ethernet(s)
assert eth.type == ETH_TYPE_8021AD
assert eth.vlan_tags[0].id == 30
assert eth.vlan_tags[1].id == 100
assert isinstance(eth.data, ip.IP)
e1 = Ethernet(s[:-1458]) # strip IP data
# construction
e2 = Ethernet(
dst=b'\x00\x10\x94\x00\x00\x0c', src=b'\x00\x10\x94\x00\x00\x14',
type=ETH_TYPE_8021AD,
vlan_tags=[
VLANtag8021Q(pri=0, id=30, cfi=0),
VLANtag8021Q(pri=0, id=100, cfi=0)
],
data=ip.IP(
len=1474, id=21680, ttl=255, p=253, sum=56767,
src=b'\xc0U\x01\x16', dst=b'\xc0U\x01\x0e', opts=b''
)
)
assert str(e1) == str(e2)
def test_eth_pack():
eth = Ethernet(data=b'12345')
assert str(eth)
def test_eth_802dot1q_with_unfamiliar_data():
profinet_data = (
b'\xfe\xff\x05\x01\x05\x01\x00\x02\x00\x00\x00\x6c\x02'
b'\x05\x00\x12\x00\x00\x02\x01\x02\x02\x02\x03\x02\x04\x02\x05\x02'
b'\x06\x01\x01\x01\x02\x02\x01\x00\x08\x00\x00\x53\x37\x2d\x33\x30'
b'\x30\x02\x02\x00\x22\x00\x00\x70\x6c\x63\x78\x62\x33\x30\x30\x78'
b'\x6b\x63\x70\x75\x78\x61\x33\x31\x37\x2d\x32\x78\x61\x70\x6e\x78'
b'\x72\x64\x70\x32\x32\x63\x66\x02\x03\x00\x06\x00\x00\x00\x2a\x01'
b'\x01\x02\x04\x00\x04\x00\x00\x02\x00\x01\x02\x00\x0e\x00\x01\xc0'
b'\xa8\x3c\x87\xff\xff\xff\x00\xc0\xa8\x3c\x87')
s = (b'\x00\x0c\x29\x65\x1c\x29\x00\x0e\x8c\x8a\xa2\x5e\x81\x00\x00\x00'
b'\x88\x92' + profinet_data)
eth = Ethernet(s)
assert eth.type == ETH_TYPE_8021Q
assert len(eth.vlan_tags) == 1
assert eth.vlan_tags[0].type == ETH_TYPE_PROFINET
assert isinstance(eth.data, bytes)
assert eth.data == profinet_data
def test_eth_802dot1q_with_arp_data(): # https://github.com/kbandla/dpkt/issues/460
from .arp import ARP
e = Ethernet(src=b'foobar', dst=b'\xff' * 6)
v = VLANtag8021Q(pri=0, cfi=0, id=1)
e.vlan_tags = [v]
a = ARP(sha=b'foobar', spa=b'\x0a\x0a\x0a\x0a',
tha=b'', tpa=b'\x0a\x0a\x0a\x05')
e.data = a
assert bytes(e) == (
b'\xff\xff\xff\xff\xff\xfffoobar\x81\x00\x00\x01\x08\x06' # 0x0806 = next layer is ARP
b'\x00\x01\x08\x00\x06\x04\x00\x01foobar\x0a\x0a\x0a\x0a'
b'\x00\x00\x00\x00\x00\x00\x0a\x0a\x0a\x05')
# 802.3 Ethernet - LLC/STP - Padding - FCS - Metamako trailer
def test_eth_8023_llc_trailer(): # https://github.com/kbandla/dpkt/issues/438
d = (b'\x01\x80\xc2\x00\x00\x00\x78\x0c\xf0\xb4\xd8\x91\x00\x27\x42\x42\x03\x00\x00\x02\x02\x3c'
b'\x00\x01\x2c\x33\x11\xf2\x39\xc1\x00\x00\x00\x02\x80\x01\x78\x0c\xf0\xb4\xd8\xbc\x80\xaa'
b'\x01\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\xb9\x81\x20\x5c\x1e'
b'\x5f\xba\x3a\xa5\x47\xfa\x01\x8e\x52\x03')
eth = Ethernet(d)
assert eth.len == 39
assert eth.padding == b'\x00\x00\x00\x00\x00\x00\x00'
assert eth.fcs == 0x4db98120
assert eth.trailer == b'\x5c\x1e\x5f\xba\x3a\xa5\x47\xfa\x01\x8e\x52\x03'
assert isinstance(eth.data, llc.LLC)
# packing
assert bytes(eth) == d
# FCS computation
eth.fcs = None
assert bytes(eth) == d
def test_eth_novell():
from binascii import unhexlify
import dpkt
buf = unhexlify(
'010203040506' # dst
'0708090a0b0c' # src
'0000' # type (ignored)
'ffff' # indicates Novell
# IPX packet
'0000' # sum
'0001' # len
'02' # tc
'03' # pt
'0102030405060708090a0b0c' # dst
'0102030405060708090a0b0c' # src
)
eth = Ethernet(buf)
assert isinstance(eth.data, dpkt.ipx.IPX)
assert eth.data.tc == 2
assert eth.data.data == b''

View File

@@ -0,0 +1,247 @@
# $Id: gre.py 75 2010-08-03 14:42:19Z jon.oberheide $
# -*- coding: utf-8 -*-
"""Generic Routing Encapsulation."""
from __future__ import absolute_import
import struct
import codecs
from . import dpkt
from . import ethernet
from .compat import compat_izip
GRE_CP = 0x8000 # Checksum Present
GRE_RP = 0x4000 # Routing Present
GRE_KP = 0x2000 # Key Present
GRE_SP = 0x1000 # Sequence Present
GRE_SS = 0x0800 # Strict Source Route
GRE_AP = 0x0080 # Acknowledgment Present
GRE_opt_fields = (
(GRE_CP | GRE_RP, 'sum', 'H'), (GRE_CP | GRE_RP, 'off', 'H'),
(GRE_KP, 'key', 'I'), (GRE_SP, 'seq', 'I'), (GRE_AP, 'ack', 'I')
)
class GRE(dpkt.Packet):
"""Generic Routing Encapsulation.
Generic Routing Encapsulation, or GRE, is a protocol for encapsulating data packets that use one routing protocol
inside the packets of another protocol. "Encapsulating" means wrapping one data packet within another data packet,
like putting a box inside another box. GRE is one way to set up a direct point-to-point connection across a network,
for the purpose of simplifying connections between separate networks. It works with a variety of network layer
protocols.
Attributes:
__hdr__: Header fields of GRE.
flags: (int): Flag bits. (2 bytes)
p: (int): Protocol Type (2 bytes)
"""
__hdr__ = (
('flags', 'H', 0),
('p', 'H', 0x0800), # ETH_TYPE_IP
)
sre = ()
@property
def v(self):
return self.flags & 0x7
@v.setter
def v(self, v):
self.flags = (self.flags & ~0x7) | (v & 0x7)
@property
def recur(self):
"""Recursion control bits. (3 bits)"""
return (self.flags >> 5) & 0x7
@recur.setter
def recur(self, v):
self.flags = (self.flags & ~0xe0) | ((v & 0x7) << 5)
class SRE(dpkt.Packet):
__hdr__ = [
('family', 'H', 0),
('off', 'B', 0),
('len', 'B', 0)
]
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
self.data = self.data[:self.len]
def opt_fields_fmts(self):
if self.v == 0:
fields, fmts = [], []
opt_fields = GRE_opt_fields
else:
fields, fmts = ['len', 'callid'], ['H', 'H']
opt_fields = GRE_opt_fields[-2:]
for flags, field, fmt in opt_fields:
if self.flags & flags:
fields.append(field)
fmts.append(fmt)
return fields, fmts
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
fields, fmts = self.opt_fields_fmts()
if fields:
fmt = ''.join(fmts)
fmtlen = struct.calcsize(fmt)
vals = struct.unpack("!" + fmt, self.data[:fmtlen])
self.data = self.data[fmtlen:]
self.__dict__.update(dict(compat_izip(fields, vals)))
if self.flags & GRE_RP:
l_ = []
while True:
sre = self.SRE(self.data)
self.data = self.data[len(sre):]
l_.append(sre)
if not sre.len:
break
self.sre = l_
try:
self.data = ethernet.Ethernet._typesw[self.p](self.data)
setattr(self, self.data.__class__.__name__.lower(), self.data)
except (KeyError, dpkt.UnpackError):
# data already set
pass
def __len__(self):
opt_fmtlen = struct.calcsize(''.join(self.opt_fields_fmts()[1]))
return self.__hdr_len__ + opt_fmtlen + sum(map(len, self.sre)) + len(self.data)
def __bytes__(self):
fields, fmts = self.opt_fields_fmts()
if fields:
vals = []
for f in fields:
vals.append(getattr(self, f))
opt_s = struct.pack('!' + ''.join(fmts), *vals)
else:
opt_s = b''
return self.pack_hdr() + opt_s + b''.join(map(bytes, self.sre)) + bytes(self.data)
def test_gre_v1():
# Runs all the test associated with this class/file
s = codecs.decode("3081880a0067178000068fb100083a76", 'hex') + b"A" * 103
g = GRE(s)
assert g.v == 1
assert g.p == 0x880a
assert g.seq == 430001
assert g.ack == 539254
assert g.callid == 6016
assert g.len == 103
assert g.data == b"A" * 103
assert len(g) == len(s)
s = codecs.decode("3001880a00b2001100083ab8", 'hex') + b"A" * 178
g = GRE(s)
assert g.v == 1
assert g.p == 0x880a
assert g.seq == 539320
assert g.callid == 17
assert g.len == 178
assert g.data == b"A" * 178
assert len(g) == len(s)
def test_gre_len():
from binascii import unhexlify
gre = GRE()
assert len(gre) == 4
buf = unhexlify("3081880a0067178000068fb100083a76") + b"\x41" * 103
gre = GRE(buf)
assert bytes(gre) == buf
assert len(gre) == len(buf)
def test_gre_accessors():
gre = GRE()
for attr in ['v', 'recur']:
print(attr)
assert hasattr(gre, attr)
assert getattr(gre, attr) == 0
setattr(gre, attr, 1)
assert getattr(gre, attr) == 1
def test_sre_creation():
from binascii import unhexlify
buf = unhexlify(
'0000' # family
'00' # off
'02' # len
'ffff'
)
sre = GRE.SRE(buf)
assert sre.data == b'\xff\xff'
assert len(sre) == 6
assert bytes(sre) == buf
def test_gre_nested_sre():
from binascii import unhexlify
buf = unhexlify(
'4000' # flags (GRE_RP)
'0800' # p (ETH_TYPE_IP)
'0001' # sum
'0002' # off
# SRE entry
'0003' # family
'04' # off
'02' # len
'ffff'
# SRE entry (no len => last element)
'0006' # family
'00' # off
'00' # len
)
gre = GRE(buf)
assert hasattr(gre, 'sre')
assert isinstance(gre.sre, list)
assert len(gre.sre) == 2
assert len(gre) == len(buf)
assert bytes(gre) == buf
assert gre.data == b''
def test_gre_next_layer():
from binascii import unhexlify
from . import ipx
buf = unhexlify(
'0000' # flags (NONE)
'8137' # p (ETH_TYPE_IPX)
# IPX packet
'0000' # sum
'0001' # len
'02' # tc
'03' # pt
'0102030405060708090a0b0c' # dst
'c0b0a0908070605040302010' # src
)
gre = GRE(buf)
assert hasattr(gre, 'ipx')
assert isinstance(gre.data, ipx.IPX)
assert gre.data.tc == 2
assert gre.data.src == unhexlify('c0b0a0908070605040302010')
assert gre.data.dst == unhexlify('0102030405060708090a0b0c')
assert len(gre) == len(buf)
assert bytes(gre) == buf

View File

@@ -0,0 +1,336 @@
# $Id: gzip.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""GNU zip."""
from __future__ import print_function
from __future__ import absolute_import
import struct
import zlib
from . import dpkt
# RFC 1952
GZIP_MAGIC = b'\x1f\x8b'
# Compression methods
GZIP_MSTORED = 0
GZIP_MCOMPRESS = 1
GZIP_MPACKED = 2
GZIP_MLZHED = 3
GZIP_MDEFLATE = 8
# Flags
GZIP_FTEXT = 0x01
GZIP_FHCRC = 0x02
GZIP_FEXTRA = 0x04
GZIP_FNAME = 0x08
GZIP_FCOMMENT = 0x10
GZIP_FENCRYPT = 0x20
GZIP_FRESERVED = 0xC0
# OS
GZIP_OS_MSDOS = 0
GZIP_OS_AMIGA = 1
GZIP_OS_VMS = 2
GZIP_OS_UNIX = 3
GZIP_OS_VMCMS = 4
GZIP_OS_ATARI = 5
GZIP_OS_OS2 = 6
GZIP_OS_MACOS = 7
GZIP_OS_ZSYSTEM = 8
GZIP_OS_CPM = 9
GZIP_OS_TOPS20 = 10
GZIP_OS_WIN32 = 11
GZIP_OS_QDOS = 12
GZIP_OS_RISCOS = 13
GZIP_OS_UNKNOWN = 255
GZIP_FENCRYPT_LEN = 12
class GzipExtra(dpkt.Packet):
__byte_order__ = '<'
__hdr__ = (
('id', '2s', b''),
('len', 'H', 0)
)
class Gzip(dpkt.Packet):
__byte_order__ = '<'
__hdr__ = (
('magic', '2s', GZIP_MAGIC),
('method', 'B', GZIP_MDEFLATE),
('flags', 'B', 0),
('mtime', 'I', 0),
('xflags', 'B', 0),
('os', 'B', GZIP_OS_UNIX),
)
def __init__(self, *args, **kwargs):
self.extra = None
self.filename = None
self.comment = None
super(Gzip, self).__init__(*args, **kwargs)
def unpack(self, buf):
super(Gzip, self).unpack(buf)
if self.flags & GZIP_FEXTRA:
if len(self.data) < 2:
raise dpkt.NeedData('Gzip extra')
n = struct.unpack('<H', self.data[:2])[0]
if len(self.data) < 2 + n:
raise dpkt.NeedData('Gzip extra')
self.extra = GzipExtra(self.data[2:2 + n])
self.data = self.data[2 + n:]
if self.flags & GZIP_FNAME:
n = self.data.find(b'\x00')
if n == -1:
raise dpkt.NeedData('Gzip end of file name not found')
self.filename = self.data[:n].decode('utf-8')
self.data = self.data[n + 1:]
if self.flags & GZIP_FCOMMENT:
n = self.data.find(b'\x00')
if n == -1:
raise dpkt.NeedData('Gzip end of comment not found')
self.comment = self.data[:n]
self.data = self.data[n + 1:]
if self.flags & GZIP_FENCRYPT:
if len(self.data) < GZIP_FENCRYPT_LEN:
raise dpkt.NeedData('Gzip encrypt')
self.data = self.data[GZIP_FENCRYPT_LEN:] # XXX - skip
if self.flags & GZIP_FHCRC:
if len(self.data) < 2:
raise dpkt.NeedData('Gzip hcrc')
self.data = self.data[2:] # XXX - skip
def pack_hdr(self):
l_ = []
if self.extra:
self.flags |= GZIP_FEXTRA
s = bytes(self.extra)
l_.append(struct.pack('<H', len(s)))
l_.append(s)
if self.filename:
self.flags |= GZIP_FNAME
l_.append(self.filename.encode('utf-8'))
l_.append(b'\x00')
if self.comment:
self.flags |= GZIP_FCOMMENT
l_.append(self.comment)
l_.append(b'\x00')
l_.insert(0, super(Gzip, self).pack_hdr())
return b''.join(l_)
def compress(self):
"""Compress self.data."""
c = zlib.compressobj(
zlib.Z_BEST_COMPRESSION,
zlib.DEFLATED,
-zlib.MAX_WBITS,
zlib.DEF_MEM_LEVEL,
zlib.Z_DEFAULT_STRATEGY,
)
c.compress(self.data)
# .compress will return nothing if len(self.data) < the window size.
self.data = c.flush()
def decompress(self):
"""Return decompressed payload."""
d = zlib.decompressobj(-zlib.MAX_WBITS)
return d.decompress(self.data)
class TestGzip(object):
"""This data is created with the gzip command line tool"""
@classmethod
def setup_class(cls):
from binascii import unhexlify
cls.data = unhexlify(
b'1F8B' # magic
b'080880C185560003' # header
b'68656C6C6F2E74787400' # filename
b'F348CDC9C95728CF2FCA4951E40200' # data
b'41E4A9B20D000000' # checksum
)
cls.p = Gzip(cls.data)
def test_method(self):
assert (self.p.method == GZIP_MDEFLATE)
def test_flags(self):
assert (self.p.flags == GZIP_FNAME)
def test_mtime(self):
# Fri Jan 01 00:00:00 2016 UTC
assert (self.p.mtime == 0x5685c180)
def test_xflags(self):
assert (self.p.xflags == 0)
def test_os(self):
assert (self.p.os == GZIP_OS_UNIX)
def test_filename(self):
assert (self.p.filename == "hello.txt") # always str (utf-8)
def test_decompress(self):
assert (self.p.decompress() == b"Hello world!\n") # always bytes
def test_flags_extra():
import pytest
from binascii import unhexlify
buf = unhexlify(
'1F8B' # magic
'08' # method
'04' # flags (GZIP_FEXTRA)
'80C18556' # mtime
'00' # xflags
'03' # os
)
# not enough data to extract
with pytest.raises(dpkt.NeedData, match='Gzip extra'):
Gzip(buf)
buf += unhexlify('0400') # append the length of the fextra
# not enough data to extract in extra section
with pytest.raises(dpkt.NeedData, match='Gzip extra'):
Gzip(buf)
buf += unhexlify('494401000102')
gzip = Gzip(buf)
assert gzip.extra.id == b'ID'
assert gzip.extra.len == 1
assert gzip.data == unhexlify('0102')
assert bytes(gzip) == buf
def test_flags_filename():
import pytest
from binascii import unhexlify
buf = unhexlify(
'1F8B' # magic
'08' # method
'08' # flags (GZIP_FNAME)
'80C18556' # mtime
'00' # xflags
'03' # os
'68656C6C6F2E747874' # filename
)
# no trailing null character so unpacking fails
with pytest.raises(dpkt.NeedData, match='Gzip end of file name not found'):
Gzip(buf)
buf += unhexlify('00')
gzip = Gzip(buf)
assert gzip.filename == 'hello.txt'
assert gzip.data == b''
assert bytes(gzip) == buf
def test_flags_comment():
import pytest
from binascii import unhexlify
buf = unhexlify(
'1F8B' # magic
'08' # method
'10' # flags (GZIP_FCOMMENT)
'80C18556' # mtime
'00' # xflags
'03' # os
'68656C6C6F2E747874' # comment
)
# no trailing null character so unpacking fails
with pytest.raises(dpkt.NeedData, match='Gzip end of comment not found'):
Gzip(buf)
buf += unhexlify('00')
gzip = Gzip(buf)
assert gzip.comment == b'hello.txt'
assert gzip.data == b''
assert bytes(gzip) == buf
def test_flags_encrypt():
import pytest
from binascii import unhexlify
buf_header = unhexlify(
'1F8B' # magic
'08' # method
'20' # flags (GZIP_FENCRYPT)
'80C18556' # mtime
'00' # xflags
'03' # os
)
# not enough data
with pytest.raises(dpkt.NeedData, match='Gzip encrypt'):
Gzip(buf_header)
encrypted_buffer = unhexlify('0102030405060708090a0b0c')
data = unhexlify('0123456789abcdef')
gzip = Gzip(buf_header + encrypted_buffer + data)
assert gzip.data == data
assert bytes(gzip) == buf_header + data
def test_flags_hcrc():
import pytest
from binascii import unhexlify
buf_header = unhexlify(
'1F8B' # magic
'08' # method
'02' # flags (GZIP_FHCRC)
'80C18556' # mtime
'00' # xflags
'03' # os
)
# not enough data
with pytest.raises(dpkt.NeedData, match='Gzip hcrc'):
Gzip(buf_header)
hcrc = unhexlify('0102')
data = unhexlify('0123456789abcdef')
gzip = Gzip(buf_header + hcrc + data)
assert gzip.data == data
assert bytes(gzip) == buf_header + data
def test_compress():
from binascii import unhexlify
buf_header = unhexlify(
'1F8B' # magic
'08' # method
'00' # flags (NONE)
'80C18556' # mtime
'00' # xflags
'03' # os
)
plain_text = b'Hello world!\n'
compressed_text = unhexlify('F348CDC9C95728CF2FCA4951E40200')
gzip = Gzip(buf_header + plain_text)
assert gzip.data == plain_text
gzip.compress()
assert gzip.data == compressed_text
assert bytes(gzip) == buf_header + compressed_text
assert gzip.decompress() == plain_text

View File

@@ -0,0 +1,300 @@
# $Id: h225.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""ITU-T H.225.0 Call Signaling."""
from __future__ import print_function
from __future__ import absolute_import
import struct
from . import dpkt
from . import tpkt
# H225 Call Signaling
#
# Call messages and information elements (IEs) are defined by Q.931:
# http://cvsup.de.openbsd.org/historic/comp/doc/standards/itu/Q/Q.931.ps.gz
#
# The User-to-User IEs of H225 are encoded by PER of ASN.1.
# Call Establishment Messages
ALERTING = 1
CALL_PROCEEDING = 2
CONNECT = 7
CONNECT_ACKNOWLEDGE = 15
PROGRESS = 3
SETUP = 5
SETUP_ACKNOWLEDGE = 13
# Call Information Phase Messages
RESUME = 38
RESUME_ACKNOWLEDGE = 46
RESUME_REJECT = 34
SUSPEND = 37
SUSPEND_ACKNOWLEDGE = 45
SUSPEND_REJECT = 33
USER_INFORMATION = 32
# Call Clearing Messages
DISCONNECT = 69
RELEASE = 77
RELEASE_COMPLETE = 90
RESTART = 70
RESTART_ACKNOWLEDGE = 78
# Miscellaneous Messages
SEGMENT = 96
CONGESTION_CONTROL = 121
INFORMATION = 123
NOTIFY = 110
STATUS = 125
STATUS_ENQUIRY = 117
# Type 1 Single Octet Information Element IDs
RESERVED = 128
SHIFT = 144
CONGESTION_LEVEL = 176
REPEAT_INDICATOR = 208
# Type 2 Single Octet Information Element IDs
MORE_DATA = 160
SENDING_COMPLETE = 161
# Variable Length Information Element IDs
SEGMENTED_MESSAGE = 0
BEARER_CAPABILITY = 4
CAUSE = 8
CALL_IDENTITY = 16
CALL_STATE = 20
CHANNEL_IDENTIFICATION = 24
PROGRESS_INDICATOR = 30
NETWORK_SPECIFIC_FACILITIES = 32
NOTIFICATION_INDICATOR = 39
DISPLAY = 40
DATE_TIME = 41
KEYPAD_FACILITY = 44
SIGNAL = 52
INFORMATION_RATE = 64
END_TO_END_TRANSIT_DELAY = 66
TRANSIT_DELAY_SELECTION_AND_INDICATION = 67
PACKET_LAYER_BINARY_PARAMETERS = 68
PACKET_LAYER_WINDOW_SIZE = 69
PACKET_SIZE = 70
CLOSED_USER_GROUP = 71
REVERSE_CHARGE_INDICATION = 74
CALLING_PARTY_NUMBER = 108
CALLING_PARTY_SUBADDRESS = 109
CALLED_PARTY_NUMBER = 112
CALLED_PARTY_SUBADDRESS = 113
REDIRECTING_NUMBER = 116
TRANSIT_NETWORK_SELECTION = 120
RESTART_INDICATOR = 121
LOW_LAYER_COMPATIBILITY = 124
HIGH_LAYER_COMPATIBILITY = 125
USER_TO_USER = 126
ESCAPE_FOR_EXTENSION = 127
class H225(dpkt.Packet):
"""ITU-T H.225.0 Call Signaling.
H.225.0 is a key protocol in the H.323 VoIP architecture defined by ITU-T. H.225.0 describes how audio, video,
data and control information on a packet based network can be managed to provide conversational services in H.323
equipment. H.225.0 has two major parts: Call signaling and RAS (Registration, Admission and Status).
Attributes:
__hdr__: Header fields of H225.
proto: (int): Protocol Discriminator. The Protocol Discriminator identifies the Layer 3 protocol. (1 byte)
ref_len: (int): Call Reference Value. Contains the length of the Call Reference Value (CRV) field. (1 byte)
"""
__hdr__ = (
('proto', 'B', 8),
('ref_len', 'B', 2)
)
def unpack(self, buf):
# TPKT header
self.tpkt = tpkt.TPKT(buf)
if self.tpkt.v != 3:
raise dpkt.UnpackError('invalid TPKT version')
if self.tpkt.rsvd != 0:
raise dpkt.UnpackError('invalid TPKT reserved value')
n = self.tpkt.len - self.tpkt.__hdr_len__
if n > len(self.tpkt.data):
raise dpkt.UnpackError('invalid TPKT length')
buf = self.tpkt.data
# Q.931 payload
dpkt.Packet.unpack(self, buf)
buf = buf[self.__hdr_len__:]
self.ref_val = buf[:self.ref_len]
buf = buf[self.ref_len:]
self.type = struct.unpack('B', buf[:1])[0]
buf = buf[1:]
# Information Elements
l_ = []
while buf:
ie = self.IE(buf)
l_.append(ie)
buf = buf[len(ie):]
self.data = l_
def __len__(self):
return self.tpkt.__hdr_len__ + self.__hdr_len__ + sum(map(len, self.data))
def __bytes__(self):
return self.tpkt.pack_hdr() + self.pack_hdr() + self.ref_val + \
struct.pack('B', self.type) + b''.join(map(bytes, self.data))
class IE(dpkt.Packet):
__hdr__ = (
('type', 'B', 0),
)
def unpack(self, buf):
dpkt.Packet.unpack(self, buf)
buf = buf[self.__hdr_len__:]
# single-byte IE
if self.type & 0x80:
self.len = 0
self.data = b''
# multi-byte IE
else:
# special PER-encoded UUIE
if self.type == USER_TO_USER:
self.len = struct.unpack('>H', buf[:2])[0]
buf = buf[2:]
# normal TLV-like IE
else:
self.len = struct.unpack('B', buf[:1])[0]
buf = buf[1:]
self.data = buf[:self.len]
def __len__(self):
if self.type & 0x80:
n = 0
else:
if self.type == USER_TO_USER:
n = 2
else:
n = 1
return self.__hdr_len__ + self.len + n
def __bytes__(self):
if self.type & 0x80:
length_str = b''
else:
if self.type == USER_TO_USER:
length_str = struct.pack('>H', self.len)
else:
length_str = struct.pack('B', self.len)
return struct.pack('B', self.type) + length_str + self.data
__s = (
b'\x03\x00\x04\x11\x08\x02\x54\x2b\x05\x04\x03\x88\x93\xa5\x28\x0e\x4a\x6f\x6e\x20\x4f\x62\x65\x72\x68\x65\x69\x64\x65'
b'\x00\x7e\x03\xf0\x05\x20\xb8\x06\x00\x08\x91\x4a\x00\x04\x01\x40\x0c\x00\x4a\x00\x6f\x00\x6e\x00\x20\x00\x4f\x00\x62'
b'\x00\x65\x00\x72\x00\x68\x00\x65\x00\x69\x00\x64\x00\x65\x22\xc0\x09\x00\x00\x3d\x06\x65\x6b\x69\x67\x61\x00\x00\x14'
b'\x32\x2e\x30\x2e\x32\x20\x28\x4f\x50\x41\x4c\x20\x76\x32\x2e\x32\x2e\x32\x29\x00\x00\x00\x01\x40\x15\x00\x74\x00\x63'
b'\x00\x70\x00\x24\x00\x68\x00\x33\x00\x32\x00\x33\x00\x2e\x00\x76\x00\x6f\x00\x78\x00\x67\x00\x72\x00\x61\x00\x74\x00'
b'\x69\x00\x61\x00\x2e\x00\x6f\x00\x72\x00\x67\x00\x42\x87\x23\x2c\x06\xb8\x00\x6a\x8b\x1d\x0c\xb7\x06\xdb\x11\x9e\xca'
b'\x00\x10\xa4\x89\x6d\x6a\x00\xc5\x1d\x80\x04\x07\x00\x0a\x00\x01\x7a\x75\x30\x11\x00\x5e\x88\x1d\x0c\xb7\x06\xdb\x11'
b'\x9e\xca\x00\x10\xa4\x89\x6d\x6a\x82\x2b\x0e\x30\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65'
b'\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41'
b'\x13\x8b\x26\x00\x00\x64\x0c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80'
b'\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x2a\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x09\x69\x4c'
b'\x42\x43\x2d\x31\x33\x6b\x33\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x20\x00\x00'
b'\x65\x0c\x10\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b'
b'\x00\x20\x40\x00\x00\x06\x04\x01\x00\x4e\x0c\x03\x00\x83\x00\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98'
b'\xa0\x26\x41\x13\x8b\x16\x00\x00\x66\x0e\x0c\x03\x00\x83\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x4b'
b'\x40\x00\x00\x06\x04\x01\x00\x4c\x10\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08'
b'\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x11'
b'\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x41\x00\x00\x67\x0c\x10\xb5\x00\x53\x4c\x2a\x02'
b'\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06'
b'\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x32\x40\x00\x00'
b'\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x11\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80'
b'\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x28\x00\x00\x68\x0c\x10\x09\x00\x00\x3d\x11'
b'\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b'
b'\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x60\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41'
b'\x13\x8b\x13\x00\x00\x69\x0c\x60\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x1d\x40\x00\x00\x06\x04\x01'
b'\x00\x4c\x20\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x13\x00\x00\x6a\x0c\x20'
b'\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x01\x00\x01\x00\x01\x00\x01\x00\x81\x03\x02\x80\xf8\x02\x70'
b'\x01\x06\x00\x08\x81\x75\x00\x0b\x80\x13\x80\x01\xf4\x00\x01\x00\x00\x01\x00\x00\x01\x00\x00\x0c\xc0\x01\x00\x01\x80'
b'\x0b\x80\x00\x00\x20\x20\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x00\x01'
b'\x20\x20\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x00\x02\x24\x18\x03\x00\xe6\x00\x80\x00\x03\x20'
b'\x20\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01'
b'\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x00\x04\x20\x20\x09\x00\x00\x3d\x11'
b'\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x00\x05\x20\xc0\xef\x80\x00\x06\x20\x40\xef'
b'\x80\x00\x07\x08\xe0\x03\x51\x00\x80\x01\x00\x80\x00\x08\x08\xd0\x03\x51\x00\x80\x01\x00\x80\x00\x09\x83\x01\x50\x80'
b'\x00\x0a\x83\x01\x10\x80\x00\x0b\x83\x01\x40\x00\x80\x01\x03\x06\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00'
b'\x06\x01\x00\x07\x00\x08\x00\x00\x09\x01\x00\x0a\x00\x0b\x07\x01\x00\x32\x80\xa6\xff\x4c\x02\x80\x01\x80'
)
def test_pack():
h = H225(__s)
assert (__s == bytes(h))
assert len(h) == 1038 # len(__s) == 1041
def test_unpack():
h = H225(__s)
assert (h.tpkt.v == 3)
assert (h.tpkt.rsvd == 0)
assert (h.tpkt.len == 1041)
assert (h.proto == 8)
assert (h.type == SETUP)
assert (len(h.data) == 3)
ie = h.data[0]
assert (ie.type == BEARER_CAPABILITY)
assert (ie.len == 3)
ie = h.data[1]
assert (ie.type == DISPLAY)
assert (ie.len == 14)
ie = h.data[2]
assert (ie.type == USER_TO_USER)
assert (ie.len == 1008)
def test_tpkt_unpack_errors():
import pytest
from binascii import unhexlify
# invalid version
buf_tpkt_version0 = unhexlify(
'00' # v
'00' # rsvd
'0000' # len
)
with pytest.raises(dpkt.UnpackError, match="invalid TPKT version"):
H225(buf_tpkt_version0)
# invalid reserved value
buf_tpkt_rsvd = unhexlify(
'03' # v
'ff' # rsvd
'0000' # len
)
with pytest.raises(dpkt.UnpackError, match="invalid TPKT reserved value"):
H225(buf_tpkt_rsvd)
# invalid len
buf_tpkt_len = unhexlify(
'03' # v
'00' # rsvd
'ffff' # len
)
with pytest.raises(dpkt.UnpackError, match="invalid TPKT length"):
H225(buf_tpkt_len)
def test_unpack_ie():
ie = H225.IE(b'\x80')
assert ie.len == 0
assert ie.data == b''
assert len(ie) == 1
assert bytes(ie) == b'\x80'

View File

@@ -0,0 +1,56 @@
# $Id: hsrp.py 23 2006-11-08 15:45:33Z dugsong $
# -*- coding: utf-8 -*-
"""Cisco Hot Standby Router Protocol."""
from __future__ import absolute_import
from . import dpkt
# Opcodes
HELLO = 0
COUP = 1
RESIGN = 2
# States
INITIAL = 0x00
LEARN = 0x01
LISTEN = 0x02
SPEAK = 0x04
STANDBY = 0x08
ACTIVE = 0x10
class HSRP(dpkt.Packet):
"""Cisco Hot Standby Router Protocol.
It is a Cisco proprietary redundancy protocol for establishing a fault-tolerant default gateway. Version 1 of the
protocol was described in RFC 2281 in 1998. Version 2 of the protocol includes improvements and supports IPv6 but
there is no corresponding RFC published for this version.
Attributes:
__hdr__: Header fields of HSRP.
version: (int): Version. HSRP version number. (1 byte)
opcode: (int): Operation code. (Hello - 0, Coup - 1, Resign - 2) (1 byte)
state: (int): State. This field describes the current state of the router sending the message. (1 byte)
hello: (int): Hellotime. This field is only meaningful in Hello messages. It contains the approximate period
between the Hello messages that the router sends. The time is given in seconds.(1 byte)
hold: (int): Holdtime. This field is only meaningful in Hello messages. It contains the amount of time that
the current Hello message should be considered valid. The time is given in seconds. (1 byte)
priority: (int): Priority. This field is used to elect the active and standby routers. (1 byte)
group: (int): Group. This field identifies the standby group. (1 byte)
rsvd: (int): Reserved. (1 byte)
auth: (bytes): Authentication Data. This field contains a clear text 8 character reused password. (8 bytes)
vip: (bytes): Virtual IP Address. The virtual IP address used by this group. (4 bytes)
"""
__hdr__ = (
('version', 'B', 0),
('opcode', 'B', 0),
('state', 'B', 0),
('hello', 'B', 0),
('hold', 'B', 0),
('priority', 'B', 0),
('group', 'B', 0),
('rsvd', 'B', 0),
('auth', '8s', b'cisco'),
('vip', '4s', b'')
)

View File

@@ -0,0 +1,594 @@
# $Id: http.py 86 2013-03-05 19:25:19Z andrewflnr@gmail.com $
# -*- coding: utf-8 -*-
"""Hypertext Transfer Protocol."""
from __future__ import print_function
from __future__ import absolute_import
from collections import OrderedDict
from . import dpkt
from .compat import BytesIO, iteritems
def parse_headers(f):
"""Return dict of HTTP headers parsed from a file object."""
d = OrderedDict()
while 1:
# The following logic covers two kinds of loop exit criteria.
# 1) If the header is valid, when we reached the end of the header,
# f.readline() would return with '\r\n', then after strip(),
# we can break the loop.
# 2) If this is a weird header, which do not ends with '\r\n',
# f.readline() would return with '', then after strip(),
# we still get an empty string, also break the loop.
line = f.readline().strip().decode("ascii", "ignore")
if not line:
break
l_ = line.split(':', 1)
if len(l_[0].split()) != 1:
raise dpkt.UnpackError('invalid header: %r' % line)
k = l_[0].lower()
v = len(l_) != 1 and l_[1].lstrip() or ''
if k in d:
if not type(d[k]) is list:
d[k] = [d[k]]
d[k].append(v)
else:
d[k] = v
return d
def parse_body(f, headers):
"""Return HTTP body parsed from a file object, given HTTP header dict."""
if headers.get('transfer-encoding', '').lower() == 'chunked':
l_ = []
found_end = False
while 1:
try:
sz = f.readline().split(None, 1)[0]
except IndexError:
raise dpkt.UnpackError('missing chunk size')
try:
n = int(sz, 16)
except ValueError:
raise dpkt.UnpackError('invalid chunk size')
if n == 0:
found_end = True
buf = f.read(n)
if f.readline().strip():
break
if n and len(buf) == n:
l_.append(buf)
else:
# only possible when len(buf) < n, which will happen if the
# file object ends before reading a complete file chunk
break
if not found_end:
raise dpkt.NeedData('premature end of chunked body')
body = b''.join(l_)
elif 'content-length' in headers:
n = int(headers['content-length'])
body = f.read(n)
if len(body) != n:
raise dpkt.NeedData('short body (missing %d bytes)' % (n - len(body)))
elif 'content-type' in headers:
body = f.read()
else:
# XXX - need to handle HTTP/0.9
body = b''
return body
class Message(dpkt.Packet):
"""Hypertext Transfer Protocol headers + body.
HTTP messages are how data is exchanged between a server and a client. There are two types of messages: requests
sent by the client to trigger an action on the server, and responses, the answer from the server. HTTP messages are
composed of textual information encoded in ASCII, and span over multiple lines.
Attributes:
__hdr__: Header fields of HTTP.
The start-line and HTTP headers of the HTTP message are collectively known as the head of the requests,
whereas its payload is known as the body.
"""
__metaclass__ = type
__hdr_defaults__ = {}
headers = None
body = None
def __init__(self, *args, **kwargs):
if args:
self.unpack(args[0])
else:
self.headers = OrderedDict()
self.body = b''
self.data = b''
# NOTE: changing this to iteritems breaks py3 compatibility
for k, v in self.__hdr_defaults__.items():
setattr(self, k, v)
for k, v in iteritems(kwargs):
setattr(self, k, v)
def unpack(self, buf, is_body_allowed=True):
f = BytesIO(buf)
# Parse headers
self.headers = parse_headers(f)
# Parse body
if is_body_allowed:
self.body = parse_body(f, self.headers)
else:
self.body = b''
# Save the rest
self.data = f.read()
def pack_hdr(self):
return ''.join(['%s: %s\r\n' % t for t in iteritems(self.headers)])
def __len__(self):
return len(str(self))
def __str__(self):
return '%s\r\n%s' % (self.pack_hdr(), self.body.decode("utf8", "ignore"))
def __bytes__(self):
return self.pack_hdr().encode("ascii", "ignore") + b'\r\n' + (self.body or b'')
class Request(Message):
"""Hypertext Transfer Protocol Request.
HTTP requests are messages sent by the client to initiate an action on the server. Their start-line contain three
elements. An HTTP method, a verb (like GET, PUT or POST) or a noun (like HEAD or OPTIONS), The request target,
usually a URL, or the absolute path of the protocol, port, and domain are usually characterized by the request
context and The HTTP version, which defines the structure of the remaining message, acting as an indicator of the
expected version to use for the response.
Attributes:
__hdr__: Header fields of HTTP request.
Many headers can appear in requests. They can be divided in several groups:
General headers, like Via, apply to the message as a whole.
Request headers, like User-Agent or Accept, modify the request by specifying it further (like Accept-
Language), by giving context (like Referer), or by conditionally restricting it (like If-None).
Representation headers like Content-Type that describe the original format of the message data and
any encoding applied (only present if the message has a body).
"""
__hdr_defaults__ = {
'method': 'GET',
'uri': '/',
'version': '1.0',
}
__methods = dict.fromkeys((
'GET', 'PUT', 'ICY',
'COPY', 'HEAD', 'LOCK', 'MOVE', 'POLL', 'POST',
'BCOPY', 'BMOVE', 'MKCOL', 'TRACE', 'LABEL', 'MERGE',
'DELETE', 'SEARCH', 'UNLOCK', 'REPORT', 'UPDATE', 'NOTIFY',
'BDELETE', 'CONNECT', 'OPTIONS', 'CHECKIN',
'PROPFIND', 'CHECKOUT', 'CCM_POST',
'SUBSCRIBE', 'PROPPATCH', 'BPROPFIND',
'BPROPPATCH', 'UNCHECKOUT', 'MKACTIVITY',
'MKWORKSPACE', 'UNSUBSCRIBE', 'RPC_CONNECT',
'VERSION-CONTROL',
'BASELINE-CONTROL'
))
__proto = 'HTTP'
def unpack(self, buf):
f = BytesIO(buf)
line = f.readline().decode("ascii", "ignore")
l_ = line.strip().split()
if len(l_) < 2:
raise dpkt.UnpackError('invalid request: %r' % line)
if l_[0] not in self.__methods:
raise dpkt.UnpackError('invalid http method: %r' % l_[0])
if len(l_) == 2:
# HTTP/0.9 does not specify a version in the request line
self.version = '0.9'
else:
if not l_[2].startswith(self.__proto):
raise dpkt.UnpackError('invalid http version: %r' % l_[2])
self.version = l_[2][len(self.__proto) + 1:]
self.method = l_[0]
self.uri = l_[1]
Message.unpack(self, f.read())
def __str__(self):
return '%s %s %s/%s\r\n' % (self.method, self.uri, self.__proto,
self.version) + Message.__str__(self)
def __bytes__(self):
str_out = '%s %s %s/%s\r\n' % (self.method, self.uri, self.__proto,
self.version)
return str_out.encode("ascii", "ignore") + Message.__bytes__(self)
class Response(Message):
"""Hypertext Transfer Protocol Response.
The start line of an HTTP response, called the status line, contains the following information. The protocol
version, usually HTTP/1.1, a status code, indicating success or failure of the request. Common status codes are 200,
404, or 302, a status text. A brief, purely informational, textual description of the status code to help a human
understand the HTTP message. A typical status line looks like: HTTP/1.1 404 Not Found.
Attributes:
__hdr__: Header fields of HTTP Response.
Many headers can appear in responses. These can be divided into several groups:
General headers, like Via, apply to the whole message.
Response headers, like Vary and Accept-Ranges, give additional information about the server which
doesn't fit in the status line.
Representation headers like Content-Type that describe the original format of the message data and any
encoding applied (only present if the message has a body).
"""
__hdr_defaults__ = {
'version': '1.0',
'status': '200',
'reason': 'OK'
}
__proto = 'HTTP'
def unpack(self, buf):
f = BytesIO(buf)
line = f.readline()
l_ = line.strip().decode("ascii", "ignore").split(None, 2)
if len(l_) < 2 or not l_[0].startswith(self.__proto) or not l_[1].isdigit():
raise dpkt.UnpackError('invalid response: %r' % line)
self.version = l_[0][len(self.__proto) + 1:]
self.status = l_[1]
self.reason = l_[2] if len(l_) > 2 else ''
# RFC Sec 4.3.
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3.
# For response messages, whether or not a message-body is included with
# a message is dependent on both the request method and the response
# status code (section 6.1.1). All responses to the HEAD request method
# MUST NOT include a message-body, even though the presence of entity-
# header fields might lead one to believe they do. All 1xx
# (informational), 204 (no content), and 304 (not modified) responses
# MUST NOT include a message-body. All other responses do include a
# message-body, although it MAY be of zero length.
is_body_allowed = int(self.status) >= 200 and 204 != int(self.status) != 304
Message.unpack(self, f.read(), is_body_allowed)
def __str__(self):
return '%s/%s %s %s\r\n' % (self.__proto, self.version, self.status,
self.reason) + Message.__str__(self)
def __bytes__(self):
str_out = '%s/%s %s %s\r\n' % (self.__proto, self.version, self.status,
self.reason)
return str_out.encode("ascii", "ignore") + Message.__bytes__(self)
def test_parse_request():
s = (b"""POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\nReferer: http://www.email.com/login/snap/login.jhtml\r\n"""
b"""Connection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\n"""
b"""Host: ltd.snap.com\r\nAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"""
b"""Accept-Encoding: gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\n"""
b"""Content-type: application/x-www-form-urlencoded\r\nContent-length: 61\r\n\r\n"""
b"""sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www""")
r = Request(s)
assert r.method == 'POST'
assert r.uri == '/main/redirect/ab/1,295,,00.html'
assert r.body == b'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www'
assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
Request(s[:60])
def test_format_request():
r = Request()
assert str(r) == 'GET / HTTP/1.0\r\n\r\n'
r.method = 'POST'
r.uri = '/foo/bar/baz.html'
r.headers['content-type'] = 'text/plain'
r.headers['content-length'] = '5'
r.body = b'hello'
s = str(r)
assert s.startswith('POST /foo/bar/baz.html HTTP/1.0\r\n')
assert s.endswith('\r\n\r\nhello')
assert '\r\ncontent-length: 5\r\n' in s
assert '\r\ncontent-type: text/plain\r\n' in s
s = bytes(r)
assert s.startswith(b'POST /foo/bar/baz.html HTTP/1.0\r\n')
assert s.endswith(b'\r\n\r\nhello')
assert b'\r\ncontent-length: 5\r\n' in s
assert b'\r\ncontent-type: text/plain\r\n' in s
r = Request(bytes(r))
assert bytes(r) == s
def test_chunked_response():
from binascii import unhexlify
header = (
b"HTTP/1.1 200 OK\r\n"
b"Cache-control: no-cache\r\n"
b"Pragma: no-cache\r\n"
b"Content-Type: text/javascript; charset=utf-8\r\n"
b"Content-Encoding: gzip\r\n"
b"Transfer-Encoding: chunked\r\n"
b"Set-Cookie: S=gmail=agg:gmail_yj=v2s:gmproxy=JkU; Domain=.google.com; Path=/\r\n"
b"Server: GFE/1.3\r\n"
b"Date: Mon, 12 Dec 2005 22:33:23 GMT\r\n"
b"\r\n"
)
body = unhexlify(
'610d0a1f8b08000000000000000d0a3135320d0a6d914d4fc4201086effe0a82c99e58'
'4a4be9b6eec1e81e369e34f1e061358652da12596880bafaef85ee1a2ff231990cef30'
'3cc381a0c301e610c13ca765595435a1a4ace1db153aa49d0cfa354b00f62eaaeb86d5'
'79cd485995348ebc2a688c8e214c3759e627eb82575acf3e381e6487853158d863e6bc'
'175a898fac208465de0a215d961769b5027b7bc27a301e0f23379c77337699329dfcc2'
'6338ea5b2f4550d6bcce84d0ceabf760271fac53d2c7d2fb94024edc040feeba195803'
'547457d7b4d9920abc58a73bb09b2710243f46fdf3437a50748a55efb8c88b2d18edec'
'3ce083850821f8225bb0d36a826893b8cfd89bbadad09214a4610d630d654dfd873d58'
'3b68d96a3be0646217c202bdb046c2696e23fb3ab6c47815d69f8aafcf290b5ebce769'
'11808b004401d82f8278f6d8f74a28ae2f11701f2bc470093afefddfa359faae347f00'
'c5a595a1e20100000d0a300d0a0d0a'
)
buf = header + body
r = Response(buf)
assert r.version == '1.1'
assert r.status == '200'
assert r.reason == 'OK'
def test_multicookie_response():
s = (b"""HTTP/1.x 200 OK\r\nSet-Cookie: first_cookie=cookie1; path=/; domain=.example.com\r\n"""
b"""Set-Cookie: second_cookie=cookie2; path=/; domain=.example.com\r\nContent-Length: 0\r\n\r\n""")
r = Response(s)
assert type(r.headers['set-cookie']) is list
assert len(r.headers['set-cookie']) == 2
def test_noreason_response():
s = b"""HTTP/1.1 200 \r\n\r\n"""
r = Response(s)
assert r.reason == ''
assert bytes(r) == s
def test_response_with_body():
r = Response()
r.body = b'foo'
assert str(r) == 'HTTP/1.0 200 OK\r\n\r\nfoo'
assert bytes(r) == b'HTTP/1.0 200 OK\r\n\r\nfoo'
repr(r)
def test_body_forbidden_response():
s = b'HTTP/1.1 304 Not Modified\r\n'\
b'Content-Type: text/css\r\n'\
b'Last-Modified: Wed, 14 Jan 2009 16:42:11 GMT\r\n'\
b'ETag: "3a7-496e15e3"\r\n'\
b'Cache-Control: private, max-age=414295\r\n'\
b'Date: Wed, 22 Sep 2010 17:55:54 GMT\r\n'\
b'Connection: keep-alive\r\n'\
b'Vary: Accept-Encoding\r\n\r\n'\
b'HTTP/1.1 200 OK\r\n'\
b'Server: Sun-ONE-Web-Server/6.1\r\n'\
b'ntCoent-length: 257\r\n'\
b'Content-Type: application/x-javascript\r\n'\
b'Last-Modified: Wed, 06 Jan 2010 19:34:06 GMT\r\n'\
b'ETag: "101-4b44e5ae"\r\n'\
b'Accept-Ranges: bytes\r\n'\
b'Content-Encoding: gzip\r\n'\
b'Cache-Control: private, max-age=439726\r\n'\
b'Date: Wed, 22 Sep 2010 17:55:54 GMT\r\n'\
b'Connection: keep-alive\r\n'\
b'Vary: Accept-Encoding\r\n'
result = []
while s:
msg = Response(s)
s = msg.data
result.append(msg)
# the second HTTP response should be an standalone message
assert len(result) == 2
def test_request_version():
s = b"""GET / HTTP/1.0\r\n\r\n"""
r = Request(s)
assert r.method == 'GET'
assert r.uri == '/'
assert r.version == '1.0'
s = b"""GET /\r\n\r\n"""
r = Request(s)
assert r.method == 'GET'
assert r.uri == '/'
assert r.version == '0.9'
import pytest
s = b"""GET / CHEESE/1.0\r\n\r\n"""
with pytest.raises(dpkt.UnpackError, match="invalid http version: u?'CHEESE/1.0'"):
Request(s)
def test_valid_header():
# valid header.
s = b'POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\n' \
b'Referer: http://www.email.com/login/snap/login.jhtml\r\n' \
b'Connection: Keep-Alive\r\n' \
b'User-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\n' \
b'Host: ltd.snap.com\r\n' \
b'Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n' \
b'Accept-Encoding: gzip\r\n' \
b'Accept-Language: en\r\n' \
b'Accept-Charset: iso-8859-1,*,utf-8\r\n' \
b'Content-type: application/x-www-form-urlencoded\r\n' \
b'Content-length: 61\r\n\r\n' \
b'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www'
r = Request(s)
assert r.method == 'POST'
assert r.uri == '/main/redirect/ab/1,295,,00.html'
assert r.body == b'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www'
assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
def test_weird_end_header():
s_weird_end = b'POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\n' \
b'Referer: http://www.email.com/login/snap/login.jhtml\r\n' \
b'Connection: Keep-Alive\r\n' \
b'User-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\n' \
b'Host: ltd.snap.com\r\n' \
b'Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n' \
b'Accept-Encoding: gzip\r\n' \
b'Accept-Language: en\r\n' \
b'Accept-Charset: iso-8859-1,*,utf-8\r\n' \
b'Content-type: application/x-www-form-urlencoded\r\n' \
b'Cookie: TrackID=1PWdcr3MO_C611BGW'
r = Request(s_weird_end)
assert r.method == 'POST'
assert r.uri == '/main/redirect/ab/1,295,,00.html'
assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
def test_gzip_response():
import zlib
# valid response, compressed using gzip
s = b'HTTP/1.0 200 OK\r\n' \
b'Server: SimpleHTTP/0.6 Python/2.7.12\r\n' \
b'Date: Fri, 10 Mar 2017 20:43:08 GMT\r\n' \
b'Content-type: text/plain\r\n' \
b'Content-Encoding: gzip\r\n' \
b'Content-Length: 68\r\n' \
b'Last-Modified: Fri, 10 Mar 2017 20:40:43 GMT\r\n\r\n' \
b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\x03\x0b\xc9\xc8,V\x00\xa2D' \
b'\x85\xb2\xd4\xa2J\x85\xe2\xdc\xc4\x9c\x1c\x85\xb4\xcc\x9cT\x85\x92' \
b'|\x85\x92\xd4\xe2\x12\x85\xf4\xaa\xcc\x02\x85\xa2\xd4\xe2\x82\xfc' \
b'\xbc\xe2\xd4b=.\x00\x01(m\xad2\x00\x00\x00'
r = Response(s)
assert r.version == '1.0'
assert r.status == '200'
assert r.reason == 'OK'
# Make a zlib compressor with the appropriate gzip options
decompressor = zlib.decompressobj(16 + zlib.MAX_WBITS)
body = decompressor.decompress(r.body)
assert body.startswith(b'This is a very small file')
def test_message():
# s = b'Date: Fri, 10 Mar 2017 20:43:08 GMT\r\n' # FIXME - unused
r = Message(content_length=68)
assert r.content_length == 68
assert len(r) == 2
def test_invalid():
import pytest
s = b'INVALID / HTTP/1.0\r\n'
with pytest.raises(dpkt.UnpackError, match="invalid http method: u?'INVALID'"):
Request(s)
s = b'A'
with pytest.raises(dpkt.UnpackError, match="invalid response: b?'A'"):
Response(s)
s = b'HTTT 200 OK'
with pytest.raises(dpkt.UnpackError, match="invalid response: b?'HTTT 200 OK'"):
Response(s)
s = b'HTTP TWO OK'
with pytest.raises(dpkt.UnpackError, match="invalid response: b?'HTTP TWO OK'"):
Response(s)
s = (
b'HTTP/1.0 200 OK\r\n'
b'Invalid Header: invalid\r\n'
)
with pytest.raises(dpkt.UnpackError, match="invalid header: "):
Response(s)
s = (
b"HTTP/1.1 200 OK\r\n"
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b"\r\n"
)
with pytest.raises(dpkt.UnpackError, match="missing chunk size"):
Response(s)
s = (
b"HTTP/1.1 200 OK\r\n"
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b"\x01\r\na"
)
with pytest.raises(dpkt.UnpackError, match="invalid chunk size"):
Response(s)
s = (
b"HTTP/1.1 200 OK\r\n"
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b"2\r\n"
b"abcd"
)
with pytest.raises(dpkt.NeedData, match="premature end of chunked body"):
Response(s)
s = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Length: 68\r\n"
b"\r\n"
b"a\r\n"
)
with pytest.raises(dpkt.NeedData, match=r"short body \(missing 65 bytes\)"):
Response(s)
# messy header.
s_messy_header = b'aaaaaaaaa\r\nbbbbbbbbb'
with pytest.raises(dpkt.UnpackError, match="invalid request: u?'aaaaaaaa"):
Request(s_messy_header)
def test_response_str():
s = (
b'HTTP/1.0 200 OK\r\n'
b'Server: SimpleHTTP/0.6 Python/2.7.12\r\n'
b'Date: Fri, 10 Mar 2017 20:43:08 GMT\r\n'
b'Content-type: text/plain\r\n'
)
# the headers are processed to lowercase keys
resp = [
'HTTP/1.0 200 OK',
'server: SimpleHTTP/0.6 Python/2.7.12',
'date: Fri, 10 Mar 2017 20:43:08 GMT',
'content-type: text/plain',
'',
'',
]
r_str = str(Response(s))
s_arr = sorted(resp)
resp_arr = sorted(r_str.split('\r\n'))
for line1, line2 in zip(s_arr, resp_arr):
assert line1 == line2
def test_request_str():
s = b'GET / HTTP/1.0\r\n'
r = Request(s)
req = 'GET / HTTP/1.0\r\n\r\n'
assert req == str(r)
def test_parse_body():
import pytest
from .compat import BytesIO
buf = BytesIO(
b'05\r\n' # size
b'ERR' # longer than size
)
buf.seek(0)
headers = {
'transfer-encoding': 'chunked',
}
with pytest.raises(dpkt.NeedData, match="premature end of chunked body"):
parse_body(buf, headers)

Some files were not shown because too many files have changed in this diff Show More