blob: 1d42cabf2c11fd634c0e0d197b168402b6bc2c55 [file] [log] [blame]
Chetan Gaonker25470972016-02-26 08:52:15 -08001import unittest
2from nose.tools import *
3from nose.twistedtools import reactor, deferred
4from twisted.internet import defer
5from scapy.all import *
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -08006import time, monotonic
Chetan Gaonker25470972016-02-26 08:52:15 -08007import os, sys
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -08008import tempfile
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -08009import random
10import threading
Chetan Gaonkere88c95c2016-03-02 05:21:47 -080011from IGMP import *
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080012from McastTraffic import *
13from Stats import Stats
Chetan Gaonker4a25e2b2016-03-04 14:45:15 -080014from OnosCtrl import OnosCtrl
Chetan Gaonker4eb12072016-03-28 15:04:15 -070015from Channels import IgmpChannel
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080016log.setLevel('INFO')
Chetan Gaonker25470972016-02-26 08:52:15 -080017
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080018IGMP_DST_MAC = "01:00:5e:00:01:01"
19IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
20IP_SRC = '1.2.3.4'
21IP_DST = '224.0.1.1'
22
23igmp_eth = Ether(dst = IGMP_DST_MAC, src = IGMP_SRC_MAC, type = ETH_P_IP)
24igmp_ip = IP(dst = IP_DST, src = IP_SRC)
25
Chetan Gaonker25470972016-02-26 08:52:15 -080026class IGMPTestState:
27
28 def __init__(self, groups = [], df = None, state = 0):
29 self.df = df
30 self.state = state
31 self.counter = 0
32 self.groups = groups
33 self.group_map = {} ##create a send/recv count map
34 for g in groups:
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080035 self.group_map[g] = (Stats(), Stats())
36
37 def update(self, group, tx = 0, rx = 0, t = 0):
38 self.counter += 1
Chetan Gaonker25470972016-02-26 08:52:15 -080039 index = 0 if rx == 0 else 1
40 v = tx if rx == 0 else rx
41 if self.group_map.has_key(group):
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080042 self.group_map[group][index].update(packets = v, t = t)
Chetan Gaonker25470972016-02-26 08:52:15 -080043
44 def update_state(self):
45 self.state = self.state ^ 1
Chetan Gaonker25470972016-02-26 08:52:15 -080046
47class igmp_exchange(unittest.TestCase):
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080048
49 IGMP_TEST_TIMEOUT = 5
Chetan Gaonker1f7c3f82016-03-08 12:17:37 -080050 IGMP_QUERY_TIMEOUT = 30
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080051 MCAST_TRAFFIC_TIMEOUT = 10
Chetan Gaonker4eb12072016-03-28 15:04:15 -070052 PORT_TX_DEFAULT = 2
53 PORT_RX_DEFAULT = 1
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080054 max_packets = 100
Chetan Gaonker4a25e2b2016-03-04 14:45:15 -080055 app = 'org.onosproject.igmp'
56
57 def setUp(self):
58 ''' Activate the dhcp app'''
59 self.onos_ctrl = OnosCtrl(self.app)
60 status, _ = self.onos_ctrl.activate()
61 assert_equal(status, True)
62 time.sleep(3)
Chetan Gaonker4eb12072016-03-28 15:04:15 -070063 self.igmp_channel = IgmpChannel()
Chetan Gaonker4a25e2b2016-03-04 14:45:15 -080064
65 def teardown(self):
66 '''Deactivate the dhcp app'''
67 self.onos_ctrl.deactivate()
68
69 def onos_load_config(self, config):
70 status, code = self.onos_ctrl.config(config)
71 if status is False:
72 log.info('JSON request returned status %d' %code)
73 assert_equal(status, True)
74 time.sleep(2)
75
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080076 def onos_ssm_table_load(self, groups, src_list):
77 ssm_dict = {'apps' : { 'org.onosproject.igmp' : { 'ssmTranslate' : [] } } }
78 ssm_xlate_list = ssm_dict['apps']['org.onosproject.igmp']['ssmTranslate']
79 for g in groups:
80 for s in src_list:
81 d = {}
82 d['source'] = s
83 d['group'] = g
84 ssm_xlate_list.append(d)
Chetan Gaonker4a25e2b2016-03-04 14:45:15 -080085 self.onos_load_config(ssm_dict)
Chetan Gaonker4eb12072016-03-28 15:04:15 -070086 cord_port_map = {}
87 for g in groups:
88 cord_port_map[g] = (self.PORT_TX_DEFAULT, self.PORT_RX_DEFAULT)
89 self.igmp_channel.cord_port_table_load(cord_port_map)
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080090 time.sleep(2)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080091
92 def igmp_verify_join(self, igmpStateList):
93 sendState, recvState = igmpStateList
Chetan Gaonker25470972016-02-26 08:52:15 -080094 ## check if the send is received for the groups
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080095 for g in sendState.groups:
96 tx_stats = sendState.group_map[g][0]
97 tx = tx_stats.count
Chetan Gaonker25470972016-02-26 08:52:15 -080098 assert_greater(tx, 0)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -080099 rx_stats = recvState.group_map[g][1]
100 rx = rx_stats.count
101 assert_greater(rx, 0)
102 log.info('Receive stats %s for group %s' %(rx_stats, g))
103
104 log.info('IGMP test verification success')
Chetan Gaonker25470972016-02-26 08:52:15 -0800105
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800106 def igmp_verify_leave(self, igmpStateList, leave_groups):
107 sendState, recvState = igmpStateList[0], igmpStateList[1]
108 ## check if the send is received for the groups
109 for g in sendState.groups:
110 tx_stats = sendState.group_map[g][0]
111 rx_stats = recvState.group_map[g][1]
112 tx = tx_stats.count
113 rx = rx_stats.count
114 assert_greater(tx, 0)
115 if g not in leave_groups:
116 log.info('Received %d packets for group %s' %(rx, g))
117 for g in leave_groups:
118 rx = recvState.group_map[g][1].count
119 assert_equal(rx, 0)
120
121 log.info('IGMP test verification success')
Chetan Gaonker25470972016-02-26 08:52:15 -0800122
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800123 def mcast_traffic_timer(self):
124 self.mcastTraffic.stopReceives()
125
126 def send_mcast_cb(self, send_state):
127 for g in send_state.groups:
128 send_state.update(g, tx = 1)
Chetan Gaonker25470972016-02-26 08:52:15 -0800129 return 0
130
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800131 ##Runs in the context of twisted reactor thread
Chetan Gaonker25470972016-02-26 08:52:15 -0800132 def igmp_recv(self, igmpState, iface = 'veth0'):
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800133 p = self.recv_socket.recv()
134 send_time = float(p.payload.load)
135 recv_time = monotonic.monotonic()
136 #log.info( 'Recv in %.6f secs' %(recv_time - send_time))
137 igmpState.update(p.dst, rx = 1, t = recv_time - send_time)
Chetan Gaonker25470972016-02-26 08:52:15 -0800138 return 0
139
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800140 def send_igmp_join(self, groups, src_list = ['1.2.3.4'], iface = 'veth0', delay = 2):
141 self.onos_ssm_table_load(groups, src_list)
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800142 igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
143 gaddr='224.0.1.1')
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800144 for g in groups:
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800145 gr = IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr=g)
146 gr.sources = src_list
147 igmp.grps.append(gr)
148
149 pkt = igmp_eth/igmp_ip/igmp
150 IGMPv3.fixup(pkt)
151 sendp(pkt, iface=iface)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800152 if delay != 0:
153 time.sleep(delay)
154
155 def send_igmp_leave(self, groups, src_list = ['1.2.3.4'], iface = 'veth0', delay = 2):
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800156 igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
157 gaddr='224.0.1.1')
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800158 for g in groups:
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800159 gr = IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr=g)
160 gr.sources = src_list
161 igmp.grps.append(gr)
162
163 pkt = igmp_eth/igmp_ip/igmp
164 IGMPv3.fixup(pkt)
165 sendp(pkt, iface = iface)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800166 if delay != 0:
167 time.sleep(delay)
Chetan Gaonker25470972016-02-26 08:52:15 -0800168
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800169 @deferred(timeout=MCAST_TRAFFIC_TIMEOUT+10)
170 def test_igmp_join_verify_traffic(self):
Chetan Gaonker25470972016-02-26 08:52:15 -0800171 groups = ['224.0.1.1', '225.0.0.1']
Chetan Gaonker25470972016-02-26 08:52:15 -0800172 df = defer.Deferred()
173 igmpState = IGMPTestState(groups = groups, df = df)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800174 igmpStateRecv = IGMPTestState(groups = groups, df = df)
175 igmpStateList = (igmpState, igmpStateRecv)
176 mcastTraffic = McastTraffic(groups, iface= 'veth2', cb = self.send_mcast_cb, arg = igmpState)
177 self.df = df
178 self.mcastTraffic = mcastTraffic
179 self.recv_socket = L3PacketSocket(iface = 'veth0', type = ETH_P_IP)
180
181 def igmp_srp_task(stateList):
182 igmpSendState, igmpRecvState = stateList
183 if not mcastTraffic.isRecvStopped():
184 result = self.igmp_recv(igmpRecvState)
185 reactor.callLater(0, igmp_srp_task, stateList)
186 else:
187 self.mcastTraffic.stop()
188 self.recv_socket.close()
189 self.igmp_verify_join(stateList)
190 self.df.callback(0)
191
192 self.send_igmp_join(groups)
193 mcastTraffic.start()
194 self.test_timer = reactor.callLater(self.MCAST_TRAFFIC_TIMEOUT, self.mcast_traffic_timer)
195 reactor.callLater(0, igmp_srp_task, igmpStateList)
Chetan Gaonker25470972016-02-26 08:52:15 -0800196 return df
197
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800198 @deferred(timeout=MCAST_TRAFFIC_TIMEOUT+10)
199 def test_igmp_leave_verify_traffic(self):
200 groups = ['224.0.1.10', '225.0.0.10']
201 leave_groups = ['224.0.1.10']
202 df = defer.Deferred()
203 igmpState = IGMPTestState(groups = groups, df = df)
204 igmpStateRecv = IGMPTestState(groups = groups, df = df)
205 igmpStateList = (igmpState, igmpStateRecv)
206 mcastTraffic = McastTraffic(groups, iface= 'veth2', cb = self.send_mcast_cb,
207 arg = igmpState)
208 self.df = df
209 self.mcastTraffic = mcastTraffic
210 self.recv_socket = L3PacketSocket(iface = 'veth0', type = ETH_P_IP)
211
212 def igmp_srp_task(stateList):
213 igmpSendState, igmpRecvState = stateList
214 if not mcastTraffic.isRecvStopped():
215 result = self.igmp_recv(igmpRecvState)
216 reactor.callLater(0, igmp_srp_task, stateList)
217 else:
218 self.mcastTraffic.stop()
219 self.recv_socket.close()
220 self.igmp_verify_leave(stateList, leave_groups)
221 self.df.callback(0)
222
223 self.send_igmp_join(groups)
224 self.send_igmp_leave(leave_groups, delay = 3)
225 mcastTraffic.start()
226 self.test_timer = reactor.callLater(self.MCAST_TRAFFIC_TIMEOUT, self.mcast_traffic_timer)
227 reactor.callLater(0, igmp_srp_task, igmpStateList)
228 return df
229
230 @deferred(timeout=100)
231 def test_igmp_leave_join_loop(self):
232 self.groups = ['226.0.1.1', '227.0.0.1', '228.0.0.1', '229.0.0.1', '230.0.0.1' ]
233 self.src_list = ['3.4.5.6', '7.8.9.10']
234 df = defer.Deferred()
235 self.df = df
236 self.iterations = 0
237 self.num_groups = len(self.groups)
238 self.MAX_TEST_ITERATIONS = 10
239
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800240 def igmp_srp_task(v):
241 if self.iterations < self.MAX_TEST_ITERATIONS:
242 if v == 1:
243 ##join test
244 self.num_groups = random.randint(0, len(self.groups))
245 self.send_igmp_join(self.groups[:self.num_groups],
246 src_list = self.src_list,
247 iface = 'veth0', delay = 0)
248 else:
249 self.send_igmp_leave(self.groups[:self.num_groups],
250 src_list = self.src_list,
251 iface = 'veth0', delay = 0)
252 self.iterations += 1
253 v ^= 1
254 reactor.callLater(1.0 + 0.5*self.num_groups,
255 igmp_srp_task, v)
256 else:
257 self.df.callback(0)
258
259 reactor.callLater(0, igmp_srp_task, 1)
260 return df
261
262 def igmp_join_task(self, intf, groups, state, src_list = ['1.2.3.4']):
263 self.onos_ssm_table_load(groups, src_list)
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800264 igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
265 gaddr='224.0.1.1')
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800266 for g in groups:
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800267 gr = IGMPv3gr(rtype = IGMP_V3_GR_TYPE_EXCLUDE, mcaddr = g)
268 gr.sources = src_list
269 igmp.grps.append(gr)
270
271 for g in groups:
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800272 state.group_map[g][0].update(1, t = monotonic.monotonic())
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800273
274 pkt = igmp_eth/igmp_ip/igmp
275 IGMPv3.fixup(pkt)
276 sendp(pkt, iface=intf)
Chetan Gaonkereb2b24b2016-03-01 14:04:45 -0800277 log.debug('Returning from join task')
278
279 def igmp_recv_task(self, intf, groups, join_state):
280 recv_socket = L3PacketSocket(iface = intf, type = ETH_P_IP)
281 group_map = {}
282 for g in groups:
283 group_map[g] = [0,0]
284
285 while True:
286 p = recv_socket.recv()
287 if p.dst in groups and group_map[p.dst][0] == 0:
288 group_map[p.dst][0] += 1
289 group_map[p.dst][1] = monotonic.monotonic()
290 c = 0
291 for g in groups:
292 c += group_map[g][0]
293 if c == len(groups):
294 break
295 for g in groups:
296 join_start = join_state.group_map[g][0].start
297 recv_time = group_map[g][1] * 1000000
298 delta = (recv_time - join_start)
299 log.info('Join for group %s received in %.3f usecs' %
300 (g, delta))
301
302 recv_socket.close()
303 log.debug('Returning from recv task')
304
305 def group_latency_check(self, groups):
306 tasks = []
307 self.send_igmp_leave(groups = groups)
308 join_state = IGMPTestState(groups = groups)
309 tasks.append(threading.Thread(target=self.igmp_join_task, args = ('veth0', groups, join_state,)))
310 traffic_state = IGMPTestState(groups = groups)
311 mcast_traffic = McastTraffic(groups, iface= 'veth2', cb = self.send_mcast_cb,
312 arg = traffic_state)
313 mcast_traffic.start()
314 tasks.append(threading.Thread(target=self.igmp_recv_task, args = ('veth0', groups, join_state)))
315 for t in tasks:
316 t.start()
317 for t in tasks:
318 t.join()
319
320 mcast_traffic.stop()
321 self.send_igmp_leave(groups = groups)
322 return
323
324 def test_igmp_1group_join_latency(self):
325 groups = ['239.0.1.1']
326 self.group_latency_check(groups)
327
328 def test_igmp_2group_join_latency(self):
329 groups = ['239.0.1.1', '240.0.1.1']
330 self.group_latency_check(groups)
331
332 def test_igmp_Ngroup_join_latency(self):
333 groups = ['239.0.1.1', '240.0.1.1', '241.0.1.1', '242.0.1.1']
334 self.group_latency_check(groups)
335
336
Chetan Gaonker1f7c3f82016-03-08 12:17:37 -0800337 def test_igmp_join_rover(self):
338 '''Keep sending joins across multicast range of addresses'''
339 '''For now, restricting it to 50/100'''
340 s = (224 << 24) | 1
341 #e = (225 << 24) | (255 << 16) | (255 << 16) | 255
Chetan Gaonkercd86bdd2016-03-17 00:08:12 -0700342 e = (224 << 24) | 25
Chetan Gaonker1f7c3f82016-03-08 12:17:37 -0800343 for i in xrange(s, e+1):
344 if i&0xff:
345 ip = '%d.%d.%d.%d'%((i>>24)&0xff, (i>>16)&0xff, (i>>8)&0xff, i&0xff)
346 self.send_igmp_join([ip], delay = 0)
347
348 @deferred(timeout=IGMP_QUERY_TIMEOUT + 10)
349 def test_igmp_query(self):
350 groups = ['224.0.0.1'] ##igmp query group
351 df = defer.Deferred()
352 self.df = df
353 self.recv_socket = L2Socket(iface = 'veth0', type = ETH_P_IP)
354
355 def igmp_query_timeout():
356
357 def igmp_query_cb(pkt):
Chetan Gaonkerbd4390f2016-03-09 18:56:52 -0800358 log.info('Got IGMP query packet from %s for %s' %(pkt[IP].src, pkt[IP].dst))
359 assert_equal(pkt[IP].dst, '224.0.0.1')
360
361 sniff(prn = igmp_query_cb, count=1, lfilter = lambda p: p[IP].dst in groups,
362 opened_socket = self.recv_socket)
Chetan Gaonker1f7c3f82016-03-08 12:17:37 -0800363 self.recv_socket.close()
364 self.df.callback(0)
365
366 self.send_igmp_join(groups)
367 self.test_timer = reactor.callLater(self.IGMP_QUERY_TIMEOUT, igmp_query_timeout)
368 return df
369