Use scapy interface to construct IGMP packets.
diff --git a/src/test/igmp/igmpTest.py b/src/test/igmp/igmpTest.py
index 1c06af5..b92b58b 100644
--- a/src/test/igmp/igmpTest.py
+++ b/src/test/igmp/igmpTest.py
@@ -13,11 +13,20 @@
CORD_TEST_UTILS = 'utils'
test_root = os.getenv('CORD_TEST_ROOT') or './'
sys.path.append(test_root + CORD_TEST_UTILS)
-from IGMP import *
+#from IGMP import *
+from IGMP_scapy import *
from McastTraffic import *
from Stats import Stats
log.setLevel('INFO')
+IGMP_DST_MAC = "01:00:5e:00:01:01"
+IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
+IP_SRC = '1.2.3.4'
+IP_DST = '224.0.1.1'
+
+igmp_eth = Ether(dst = IGMP_DST_MAC, src = IGMP_SRC_MAC, type = ETH_P_IP)
+igmp_ip = IP(dst = IP_DST, src = IP_SRC)
+
class IGMPTestState:
def __init__(self, groups = [], df = None, state = 0):
@@ -62,6 +71,8 @@
log.debug('Loading SSM config in file %s to ONOS.' %temp.name)
os.system('./igmp_ssm_load.sh %s' %temp.name)
os.unlink(temp.name)
+ ##Wait for ONOS to populate the SSM map before sending join.Huh
+ time.sleep(2)
def igmp_verify_join(self, igmpStateList):
sendState, recvState = igmpStateList
@@ -113,22 +124,30 @@
def send_igmp_join(self, groups, src_list = ['1.2.3.4'], iface = 'veth0', delay = 2):
self.onos_ssm_table_load(groups, src_list)
+ igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
+ gaddr='224.0.1.1')
for g in groups:
- igmp = IGMP(mtype = IGMPV3_REPORT,
- group = g,
- rtype = IGMP_INCLUDE,
- src_list = src_list)
- sendp(igmp.scapify(), iface = iface)
+ gr = IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr=g)
+ gr.sources = src_list
+ igmp.grps.append(gr)
+
+ pkt = igmp_eth/igmp_ip/igmp
+ IGMPv3.fixup(pkt)
+ sendp(pkt, iface=iface)
if delay != 0:
time.sleep(delay)
def send_igmp_leave(self, groups, src_list = ['1.2.3.4'], iface = 'veth0', delay = 2):
+ igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
+ gaddr='224.0.1.1')
for g in groups:
- igmp = IGMP(mtype = IGMPV3_REPORT,
- group = g,
- rtype = IGMP_EXCLUDE,
- src_list = src_list)
- sendp(igmp.scapify(), iface = iface)
+ gr = IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr=g)
+ gr.sources = src_list
+ igmp.grps.append(gr)
+
+ pkt = igmp_eth/igmp_ip/igmp
+ IGMPv3.fixup(pkt)
+ sendp(pkt, iface = iface)
if delay != 0:
time.sleep(delay)
@@ -203,7 +222,6 @@
self.num_groups = len(self.groups)
self.MAX_TEST_ITERATIONS = 10
- #self.onos_ssm_table_load(self.groups, self.src_list)
def igmp_srp_task(v):
if self.iterations < self.MAX_TEST_ITERATIONS:
if v == 1:
@@ -228,13 +246,19 @@
def igmp_join_task(self, intf, groups, state, src_list = ['1.2.3.4']):
self.onos_ssm_table_load(groups, src_list)
+ igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
+ gaddr='224.0.1.1')
for g in groups:
- igmp = IGMP(mtype = IGMPV3_REPORT,
- group = g,
- rtype = IGMP_INCLUDE,
- src_list = src_list)
+ gr = IGMPv3gr(rtype = IGMP_V3_GR_TYPE_EXCLUDE, mcaddr = g)
+ gr.sources = src_list
+ igmp.grps.append(gr)
+
+ for g in groups:
state.group_map[g][0].update(1, t = monotonic.monotonic())
- sendp(igmp.scapify(), iface = intf)
+
+ pkt = igmp_eth/igmp_ip/igmp
+ IGMPv3.fixup(pkt)
+ sendp(pkt, iface=intf)
log.debug('Returning from join task')
def igmp_recv_task(self, intf, groups, join_state):
diff --git a/src/test/utils/IGMP.py b/src/test/utils/IGMP.py
index 64aa27f..1b24de3 100644
--- a/src/test/utils/IGMP.py
+++ b/src/test/utils/IGMP.py
@@ -3,146 +3,232 @@
from scapy.all import *
from itertools import *
-IGMPV3_REPORT = 0x22
-IGMP_LEAVE = 0x17
-IGMP_EXCLUDE = 0x04
-IGMP_INCLUDE = 0x03
+IGMP_TYPE_MEMBERSHIP_QUERY = 0x11
+IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22
+IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12
+IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16
+IGMP_TYPE_V2_LEAVE_GROUP = 0x17
+
+IGMP_V3_GR_TYPE_INCLUDE = 0x01
+IGMP_V3_GR_TYPE_EXCLUDE = 0x02
+IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03
+IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04
+IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05
+IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06
+
+"""
IGMPV3_ALL_ROUTERS = '224.0.0.22'
IGMPv3 = 3
IP_SRC = '1.2.3.4'
ETHERTYPE_IP = 0x0800
IGMP_DST_MAC = "01:00:5e:00:01:01"
IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
+"""
-class IGMP:
- def __init__(self, mtype = None, group = '', rtype = None, src_list = []):
- self.version = IGMPv3
- self.mtype = mtype
- self.group = group
- self.src_list= src_list
- self.rtype = rtype
+class IGMPv3gr(Packet):
+ """IGMPv3 Group Record, used in membership report"""
- def checksum(self, msg):
- s = 0
- for i in range(0, len(msg), 2):
- w = ord(msg[i]) + (ord(msg[i+1]) << 8)
- c = s + w
- s = (c & 0xffff) + (c >> 16)
- return ~s & 0xffff
+ name = "IGMPv3gr"
- def update_igmp_checksum(self, pkt):
- cs = self.checksum(pkt)
- #print 'igmp checksum: ' + str(hex(cs))
- m = []
- for x in pkt:
- m.append(ord(x))
- higher = (cs >> 8) & 0xff
- lower = cs & 0xff
- m[2] = lower
- m[3] = higher
- m = pack("%dB" % len(m), *m)
- return m
+ igmp_v3_gr_types = {
+ IGMP_V3_GR_TYPE_INCLUDE: "Include Mode",
+ IGMP_V3_GR_TYPE_EXCLUDE: "Exclude Mode",
+ IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE: "Change to Include Mode",
+ IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE: "Change to Exclude Mode",
+ IGMP_V3_GR_TYPE_ALLOW_NEW: "Allow New Sources",
+ IGMP_V3_GR_TYPE_BLOCK_OLD: "Block Old Sources"
+ }
- def update_ip_checksum(self, pkt):
- cs = self.checksum(pkt)
- #print 'ip hdr checksum: ' + str(hex(cs))
- m = []
- for x in pkt:
- m.append(ord(x))
- higher = (cs >> 8) & 0xff
- lower = cs & 0xff
- m[10] = lower
- m[11] = higher
- m = pack("%dB" % len(m), *m)
- return m
+ fields_desc = [
+ ByteEnumField("rtype", IGMP_V3_GR_TYPE_INCLUDE, igmp_v3_gr_types),
+ ByteField("aux_data_len", 0),
+ FieldLenField("numsrc", None, count_of="sources"),
+ IPField("mcaddr", "0.0.0.0"),
+ FieldListField("sources", None, IPField("src", "0.0.0.0"), "numsrc")
+ ]
- def build_ip_hdr(self, s, d):
- ip_ihl_len = 0x46 #8 bits
- ip_dscp = 0xc0 #8 bits
- ip_hdr_total_len = 0x0028 #16 bits
- ip_id = 0x0000 #16 bits
- ip_flags = 0x4000 #16 bits
- ip_ttl = 1 #8 bits
- ip_protocol = 0x02 #8 bits
- ip_cs = 0x0000 #16 bits (should filled by kernel but seems not???)
- #ip_src #32 bits
- #ip_dst #32 bits
- ip_options = 0x94040000 #32 bits
- #total len 24 bytes
- ip_header = pack('!BBHHHBBH4s4sI', ip_ihl_len, ip_dscp, ip_hdr_total_len,
- ip_id, ip_flags, ip_ttl, ip_protocol, ip_cs, inet_aton(s),
- inet_aton(d), ip_options)
- return ip_header
-
- def dump_packet(self, data):
- i = 0
- for x in data:
- if i == 4:
- print ''
- i = 0
- i += 1
- sys.stdout.write(' %0.2x' % ord(x))
- print ''
-
- def build_igmp(self, msg_type = None, group = None, record_type = None, src_list = None):
- msg_type = self.mtype if msg_type == None else msg_type
- group = self.group if group == None else group
- record_type = self.rtype if record_type == None else record_type
- src_list = self.src_list if src_list == None else src_list
- if msg_type == IGMP_LEAVE:
- pkt = pack('!BBH4s', msg_type, 0, 0, inet_aton(group))
- elif msg_type == IGMPV3_REPORT:
- pkt = pack('!BBHHHBBH', msg_type, 0x00, 0x0000, 0x0000, 0x0001, record_type,
- 0x00, len(src_list))
- pkt += pack('!4s', inet_aton(group))
- for a in src_list:
- pkt += pack('!4s', inet_aton(a))
- else:
- print 'unsupported report type: ' + str(msg_type)
- return None
+ def post_build(self, pkt, payload):
+ pkt += payload
+ if self.aux_data_len != 0:
+ print "WARNING: Auxiliary Data Length must be zero (0)"
return pkt
- def build_join_msg(self, group = None, record_type = None, src_list = None):
- return self.build_igmp(msg_type = IGMPV3_REPORT,
- group = group,
- record_type = record_type,
- src_list = src_list)
- def build_leave_msg(self, group = None):
- return self.build_igmp(msg_type = IGMPV3_REPORT,
- group = group,
- record_type = IGMP_EXCLUDE,
- src_list = [])
+class IGMPv3(Packet):
- def build_ip_igmp(self,
- src = IP_SRC,
- msg_type = None,
- group = None,
- record_type = None,
- src_list = None):
+ name = "IGMPv3"
- igmp = self.build_igmp(msg_type = msg_type,
- group = group,
- record_type = record_type,
- src_list = src_list)
- igmp = self.update_igmp_checksum(igmp)
- ip_hdr = self.build_ip_hdr(src, IGMPV3_ALL_ROUTERS)
- p = ip_hdr + igmp
- p = self.update_ip_checksum(p)
- return p
+ igmp_v3_types = {
+ IGMP_TYPE_MEMBERSHIP_QUERY: "Membership Query",
+ IGMP_TYPE_V3_MEMBERSHIP_REPORT: " Version 3 Mebership Report",
+ IGMP_TYPE_V2_MEMBERSHIP_REPORT: " Version 2 Mebership Report",
+ IGMP_TYPE_V1_MEMBERSHIP_REPORT: " Version 1 Mebership Report",
+ IGMP_TYPE_V2_LEAVE_GROUP: "Version 2 Leave Group"
+ }
- def scapify(self,
- src = IP_SRC,
- msg_type = None,
- group = None,
- record_type = None,
- src_list = None):
+ fields_desc = [
+ ByteEnumField("type", IGMP_TYPE_MEMBERSHIP_QUERY, igmp_v3_types),
+ ByteField("max_resp_code", 0),
+ XShortField("checksum", None),
+ #IPField("group_address", "0.0.0.0"),
- ip_igmp = self.build_ip_igmp(src = src,
- msg_type = msg_type,
- group = group,
- record_type = record_type,
- src_list = src_list)
- eth = Ether(dst = IGMP_DST_MAC, src = IGMP_SRC_MAC, type = ETHERTYPE_IP)
- return eth/ip_igmp
+ # membership query fields
+ ConditionalField(IPField("gaddr", "0.0.0.0"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(BitField("resv", 0, 4), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(BitField("s", 0, 1), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(BitField("qrv", 0, 3), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(ByteField("qqic", 0), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(FieldLenField("numsrc", None, count_of="srcs"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+ ConditionalField(FieldListField("srcs", None, IPField("src", "0.0.0.0"), "numsrc"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
+
+ # membership report fields
+ ConditionalField(ShortField("resv2", 0), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
+ ConditionalField(FieldLenField("numgrp", None, count_of="grps"), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
+ ConditionalField(PacketListField("grps", [], IGMPv3gr), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT)
+
+ # TODO: v2 and v3 membership reports?
+
+ ]
+
+ def post_build(self, pkt, payload):
+
+ pkt += payload
+
+ if self.type in [IGMP_TYPE_V3_MEMBERSHIP_REPORT,]: # max_resp_code field is reserved (0)
+ mrc = 0
+ else:
+ mrc = self.encode_float(self.max_resp_code)
+ pkt = pkt[:1] + chr(mrc) + pkt[2:]
+
+ if self.checksum is None:
+ chksum = checksum(pkt)
+ pkt = pkt[:2] + chr(chksum >> 8) + chr(chksum & 0xff) + pkt[4:]
+
+ return pkt
+
+ def encode_float(self, value):
+ """Encode max response time value per RFC 3376."""
+ if value < 128:
+ return value
+ if value > 31743:
+ return 255
+ exp = 0
+ value >>= 3
+ while value > 31:
+ exp += 1
+ value >>= 1
+ return 0x80 | (exp << 4) | (value & 0xf)
+
+
+ def decode_float(self, code):
+ if code < 128:
+ return code
+ mant = code & 0xf
+ exp = (code >> 4) & 0x7
+ return (mant | 0x10) << (exp + 3)
+
+ @staticmethod
+ def is_valid_mcaddr(ip):
+ byte1 = atol(ip) >> 24 & 0xff
+ return (byte1 & 0xf0) == 0xe0
+
+ @staticmethod
+ def fixup(pkt):
+ """Fixes up the underlying IP() and Ether() headers."""
+ assert pkt.haslayer(IGMPv3), "This packet is not an IGMPv4 packet; cannot fix it up"
+
+ igmp = pkt.getlayer(IGMPv3)
+
+ if pkt.haslayer(IP):
+ ip = pkt.getlayer(IP)
+ ip.ttl = 1
+ ip.proto = 2
+ ip.tos = 0xc0
+ ip.options = [IPOption_Router_Alert()]
+
+ if igmp.type == IGMP_TYPE_MEMBERSHIP_QUERY:
+ if igmp.gaddr == "0.0.0.0":
+ ip.dst = "224.0.0.1"
+ else:
+ assert IGMPv3.is_valid_mcaddr(igmp.gaddr), "IGMP membership query with invalid mcast address"
+ ip.dst = igmp.gaddr
+
+ elif igmp.type == IGMP_TYPE_V2_LEAVE_GROUP and IGMPv3.is_valid_mcaddr(igmp.gaddr):
+ ip.dst = "224.0.0.2"
+
+ elif (igmp.type in (IGMP_TYPE_V1_MEMBERSHIP_REPORT, IGMP_TYPE_V2_MEMBERSHIP_REPORT) and
+ IGMPv3.is_valid_mcaddr(igmp.gaddr)):
+ ip.dst = igmp.gaddr
+
+ # We do not need to fixup the ether layer, it is done by scapy
+ #
+ # if pkt.haslayer(Ether):
+ # eth = pkt.getlayer(Ether)
+ # ip_long = atol(ip.dst)
+ # ether.dst = '01:00:5e:%02x:%02x:%02x' % ( (ip_long >> 16) & 0x7f, (ip_long >> 8) & 0xff, ip_long & 0xff )
+
+
+ return pkt
+
+
+bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0)
+bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2)
+bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2)
+
+
+if __name__ == "__main__":
+
+ print "test float encoding"
+ from math import log
+ max_expected_error = 1.0 / (2<<3) # four bit precision
+ p = IGMPv3()
+ for v in range(0, 31745):
+ c = p.encode_float(v)
+ d = p.decode_float(c)
+ rel_err = float(v-d)/v if v!=0 else 0.0
+ assert rel_err <= max_expected_error
+
+ print "construct membership query - general query"
+ mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120)
+ hexdump(str(mq))
+
+ print "construct membership query - group-specific query"
+ mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
+ hexdump(str(mq))
+
+ print "construct membership query - group-and-source-specific query"
+ mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
+ mq.srcs = ['1.2.3.4', '5.6.7.8']
+ hexdump(str(mq))
+
+ print "fixup"
+ mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY)
+ mq.srcs = ['1.2.3.4', '5.6.7.8']
+ pkt = Ether() / IP() / mq
+ print "before fixup:"
+ hexdump(str(pkt))
+
+ print "after fixup:"
+ IGMPv3.fixup(pkt)
+ hexdump(str(pkt))
+
+ print "construct v3 membership report - join a single group"
+ mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
+ mr.grps = [IGMPv3gr( rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30")]
+ hexdump(mr)
+
+ print "construct v3 membership report - join two groups"
+ mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
+ mr.grps = [
+ IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30"),
+ IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.31")
+ ]
+ hexdump(mr)
+
+ print "construct v3 membership report - leave a group"
+ mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
+ mr.grps = [IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr="229.10.20.30")]
+ hexdump(mr)
+
+ print "all ok"