From f86ce2603d5bf63c79dc1de36e4fc8b8739ae405 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Sat, 17 Sep 2016 12:16:53 +0530 Subject: [PATCH] Bugfix: use default gateway for off subnet destinations in all cases - fixes #196 --- server/device.cpp | 37 ++++++++++--- test/emultest.py | 137 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 18 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index b1469ca..bb3eeb6 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -684,16 +684,20 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) uchar *pktData = pktBuf->push(20); uchar origTtl = pktData[8]; uchar ipProto = pktData[9]; - quint32 srcIp, dstIp; + quint32 srcIp, dstIp, tgtIp, mask; quint32 sum; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt - if (!arpTable_.contains(dstIp)) { + mask = ~0 << (32 - ip4PrefixLength_); + qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); + tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; + + if (!arpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv4 packet", - __FUNCTION__, qPrintable(QHostAddress(dstIp).toString())); + __FUNCTION__, qPrintable(QHostAddress(tgtIp).toString())); return; } @@ -712,7 +716,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf) sum = (sum & 0xFFFF) + (sum >> 16); *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum)); - encap(pktBuf, arpTable_.value(dstIp), 0x0800); + encap(pktBuf, arpTable_.value(tgtIp), 0x0800); transmitPacket(pktBuf); } @@ -796,7 +800,7 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) { int payloadLen = pktBuf->length(); uchar *p = pktBuf->push(kIp6HdrLen); - quint64 dstMac = ndpTable_.value(dstIp); + quint64 dstMac; if (!p) { qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, @@ -807,6 +811,15 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol) // In case of mcast, derive dstMac if ((dstIp.hi64() >> 56) == 0xff) dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); + else { + UInt128 mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + UInt128 tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + qDebug("dst %s mask %s self %s", + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + dstMac = ndpTable_.value(tgtIp); + } if (!dstMac) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", @@ -841,16 +854,22 @@ _error_exit: void Device::sendIp6Reply(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->push(kIp6HdrLen); - UInt128 srcIp, dstIp; + UInt128 srcIp, dstIp, tgtIp, mask; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt - if (!ndpTable_.contains(dstIp)) { + mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); + qDebug("dst %s mask %s self %s", + qPrintable(QHostAddress(dstIp.toArray()).toString()), + qPrintable(QHostAddress(mask.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; + if (!ndpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", __FUNCTION__, - qPrintable(QHostAddress(dstIp.toArray()).toString())); + qPrintable(QHostAddress(tgtIp.toArray()).toString())); return; } @@ -860,7 +879,7 @@ void Device::sendIp6Reply(PacketBuffer *pktBuf) // Reset TTL pktData[7] = 64; - encap(pktBuf, ndpTable_.value(dstIp), 0x86dd); + encap(pktBuf, ndpTable_.value(tgtIp), 0x86dd); transmitPacket(pktBuf); } diff --git a/test/emultest.py b/test/emultest.py index 1124354..4f52993 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -21,6 +21,7 @@ from rpc import RpcError from protocols.mac_pb2 import mac, Mac from protocols.ip4_pb2 import ip4, Ip4 from protocols.ip6_pb2 import ip6, Ip6 +from protocols.icmp_pb2 import icmp, Icmp from protocols.vlan_pb2 import vlan use_defaults = True @@ -348,6 +349,106 @@ def dut_vlans(request, dut_ports): request.addfinalizer(delete_vdev) +@pytest.fixture +def ping(request, drone, ip_ver, port_id, src_ip, dst_ip): + # create ICMP stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(port_id) + stream_id.stream_id.add().id = 0 + log.info('adding ping tx_stream %d' % stream_id.stream_id[0].id) + + drone.addStream(stream_id) + + # configure the ICMP echo tx stream(s) + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(port_id) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.core.frame_len = 128 + s.control.packets_per_sec = 1 + s.control.num_packets = 3 + + # setup stream protocols as mac:eth2:ip:icmp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve + p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + if ip_ver == 4: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + ip = None + ip = p.Extensions[ip4] + ip.src_ip = src_ip + ip.dst_ip = dst_ip + elif ip_ver == 6: + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber + ip = p.Extensions[ip6] + ip.src_addr_hi = src_ip.hi + ip.src_addr_lo = src_ip.lo + ip.dst_addr_hi = dst_ip.hi + ip.dst_addr_lo = dst_ip.lo + else: + assert False # unreachable + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIcmpFieldNumber + if ip_ver == 6: + p.Extensions[icmp].icmp_version = Icmp.kIcmp6 + p.Extensions[icmp].type = 128 # icmpv6 echo request + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring ping tx_stream %d' % stream_id.stream_id[0].id) + + drone.modifyStream(stream_cfg) + + # send ping packets + ports = ost_pb.PortIdList() + ports.port_id.add().id = port_id.id + drone.startCapture(ports) + drone.startTransmit(ports) + time.sleep(5) + drone.stopCapture(ports) + + # delete ping stream + drone.deleteStream(stream_id) + + # FIXME: workaround for bug#179 + stream_cfg.ClearField("stream") + drone.modifyStream(stream_cfg) + + # verify ICMP Replies are received + buff = drone.getCaptureBuffer(port_id) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer (all)') + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap']) + print(cap_pkts) + if ip_ver == 4: + filter = '(icmp.type == 0)' \ + ' && (icmp.code == 0)' \ + ' && (ip.src == ' + str(ipaddress.ip_address(dst_ip)) + ')' \ + ' && (ip.dst == ' + str(ipaddress.ip_address(src_ip)) + ')' \ + ' && !expert.severity' + elif ip_ver == 6: + filter = '(icmpv6.type == 129)' \ + ' && (icmpv6.code == 0)' \ + ' && (ipv6.src == ' \ + + str(ip6_address(dst_ip)) + ')' \ + ' && (ipv6.dst == ' \ + + str(ip6_address(src_ip)) + ')' \ + ' && !expert.severity' + log.info('dumping Rx capture buffer (filtered)') + print filter + cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap', + '-Y', filter]) + print(cap_pkts) + return cap_pkts.count('\n') > 1 # ================================================================= # # ----------------------------------------------------------------- # @@ -360,7 +461,7 @@ def dut_vlans(request, dut_ports): {'ip_ver': [6], 'mac_step': 1, 'ip_step': 1}, {'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5}, ]) -def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, +def test_multiEmulDevNoVlan(request, drone, ports, dut, dut_ports, dut_ip, stream_clear, emul_ports, dgid_list, dev_cfg): # ----------------------------------------------------------------- # # TESTCASE: Emulate multiple IPv4 devices (no vlans) @@ -381,9 +482,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip_step = dev_cfg['ip_step'] # configure the tx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) - dg = devgrp_cfg.device_group.add() + tx_devgrp_cfg = ost_pb.DeviceGroupConfigList() + tx_devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0]) + dg = tx_devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.tx.device_group_id[0].id dg.core.name = "Host1" dg.device_count = num_devs @@ -406,12 +507,12 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip.step.CopyFrom(ip6_address(ip_step).ip6) ip.default_gateway.CopyFrom(ip6addr.gateway) - drone.modifyDeviceGroup(devgrp_cfg) + drone.modifyDeviceGroup(tx_devgrp_cfg) # configure the rx device(s) - devgrp_cfg = ost_pb.DeviceGroupConfigList() - devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) - dg = devgrp_cfg.device_group.add() + rx_devgrp_cfg = ost_pb.DeviceGroupConfigList() + rx_devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0]) + dg = rx_devgrp_cfg.device_group.add() dg.device_group_id.id = dgid_list.rx.device_group_id[0].id dg.core.name = "Host1" dg.device_count = num_devs @@ -434,7 +535,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, ip.step.CopyFrom(ip6_address(ip_step).ip6) ip.default_gateway.CopyFrom(ip6addr.gateway) - drone.modifyDeviceGroup(devgrp_cfg) + drone.modifyDeviceGroup(rx_devgrp_cfg) + + # test end-to-end reachability - after resolving ARP/NDP + # FIXME: if and when ping RPC is added, move to where we ping DUT below + # FIXME: also add ping test to vlan case + time.sleep(10) # wait for DAD? otherwise we don't get replies for NS + drone.clearDeviceNeighbors(emul_ports) + drone.resolveDeviceNeighbors(emul_ports) + time.sleep(3) # wait for ARP resolution + # FIXME: if ping6/ping4 order is swapped, DUT does not send NS - have + # spent several hours trying to figure out why - to no avail :( + if has_ip6: + assert ping(request, drone, 6, emul_ports.port_id[0], + tx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address, + rx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address) + if has_ip4: + assert ping(request, drone, 4, emul_ports.port_id[0], + tx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address, + rx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address) # add the tx stream(s) - we may need more than one stream_id = ost_pb.StreamIdList()