katran/lib/testing/fplane_testing.py (176 lines of code) (raw):

# Copyright (C) 2018-present, Facebook, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import from __future__ import division from __future__ import print_function import argparse import time from multiprocessing import Process, Queue from scapy.all import ( Ether, IP, IPv6, TCP, UDP, ICMP, ICMPv6EchoRequest, ICMPv6PacketTooBig, ARP, sendp, sniff, ) QUEUE_READ_TIMEOUT = 5 RECVED_PCKTS = 0 MISSED_PCKTS = 1 INDEX_LEN = 2 TEST_PCKTS = [ # pkt 1; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.1") / UDP(sport=31337, dport=80) / "katran test pckt 01", # pkt 2; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.1") / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 02", # pkt 3; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.2") / TCP(sport=31337, dport=42, flags="A") / "katran test pckt 03", # pkt 4; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.3") / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 04", # pkt 5; reply expected Ether(src="0x002", dst="0x2") / IPv6(src="fc00:2::1", dst="fc00:1::1") / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 05", # pkt 6; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.3") / ICMP(type="echo-request") / "katran test pckt 06", # pkt 7; reply expected Ether(src="0x002", dst="0x2") / IPv6(src="fc00:2::1", dst="fc00:1::1") / ICMPv6EchoRequest() / "katran test pckt 07", # pkt 8; reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.100.1", dst="10.200.1.1") / ICMP(type="dest-unreach", code="fragmentation-needed") / IP(src="10.200.1.1", dst="192.168.1.1") / TCP(sport=80, dport=31337) / "katran test pckt 08", # pkt 9; reply expected Ether(src="0x002", dst="0x2") / IPv6(src="fc00:200::1", dst="fc00:1::1") / ICMPv6PacketTooBig() / IPv6(src="fc00:1::1", dst="fc00:2::1") / TCP(sport=80, dport=31337) / "katran test pckt 09", # pkt 10; will be droped on katran side Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.1", ihl=6) / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 10", # pkt 11; will be droped on katran side Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.1", ihl=5, flags="MF") / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 11", # pkt 12; will be droped on katran side Ether(src="0x002", dst="0x2") / IPv6(src="fc00:2::1", dst="fc00:1::1", nh=44) / TCP(sport=31337, dport=80, flags="A") / "katran test pckt 12", # pkt 13; will be passed to katran's tcp stack; no reply expected Ether(src="0x002", dst="0x2") / IP(src="192.168.1.1", dst="10.200.1.1", ihl=5) / TCP(sport=31337, dport=82, flags="A") / "katran test pckt 13", # pkt 14; will be passed to katran's tcp stack; no reply expected Ether(src="0x002", dst="0x2") / IPv6(src="fc00:2::1", dst="fc00:1::1") / TCP(sport=31337, dport=82, flags="A") / "katran test pckt 14", # pkt 15; will be passed to katran's tcp stack; no reply expected Ether(src="0x002", dst="0x2") / ARP(), ] # we are expecting replies for first 9 packets from TEST_PCKTS # keys in this dict are numbers after "katran test pckt" in rcved pckt EXPECTED_REPLY = { 1: "packet to UDP based v4 VIP (and v4 real)", 2: "packet to TCP based v4 VIP (and v4 real)", 3: "packet to TCP based v4 VIP (and v4 real; any dst ports)", 4: "packet to TCP based v4 VIP (and v6 real)", 5: "packet to TCP based v6 VIP (and v6 real)", 6: "v4 ICMP echo-request", 7: "v6 ICMP echo-request", 8: "v4 ICMP dest-unreachabe fragmentation-needed", 9: "v6 ICMP packet-too-big", } def parse_args(): parser = argparse.ArgumentParser( usage=""" This is a tool for forwarding plane (NIC driver's support for XDP) tests Topology: <pktgen> --- <l2 network> --- <katran host> Usage: 1) copy this file to pktgen host. 2) on katran host run: ./katranadm.par -Au 10.200.1.1:80 ./katranadm.par -au 10.200.1.1:80 -r 10.0.0.1 ./katranadm.par -au 10.200.1.1:80 -r 10.0.0.2 ./katranadm.par -au 10.200.1.1:80 -r 10.0.0.3 ./katranadm.par -At 10.200.1.1:80 ./katranadm.par -at 10.200.1.1:80 -r 10.0.0.1 ./katranadm.par -at 10.200.1.1:80 -r 10.0.0.2 ./katranadm.par -at 10.200.1.1:80 -r 10.0.0.3 ./katranadm.par -At 10.200.1.2:0 ./katranadm.par -at 10.200.1.2:0 -r 10.0.0.1 ./katranadm.par -at 10.200.1.2:0 -r 10.0.0.2 ./katranadm.par -at 10.200.1.2:0 -r 10.0.0.3 ./katranadm.par -At 10.200.1.3:80 ./katranadm.par -at 10.200.1.3:80 -r fc00::1 ./katranadm.par -at 10.200.1.3:80 -r fc00::2 ./katranadm.par -at 10.200.1.3:80 -r fc00::3 ./katranadm.par -At [fc00:1::1]:80 ./katranadm.par -at [fc00:1::1]:80 -r fc00::1 ./katranadm.par -at [fc00:1::1]:80 -r fc00::2 ./katranadm.par -at [fc00:1::1]:80 -r fc00::3 ./katranadm.par --change_mac <mac of the pktgen host> 3) on pktgen host: 3.1) run tcpdump -evni eth0 ether host <mac of katran> 3.2) run ./fplane-testing.par --katran-mac "<mac of katran>" 3.3) check that tcpdump output is sane """ ) parser.add_argument( "--katran-mac", type=str, help="Mac address of Katran load balancer" ) parser.add_argument( "--iface", type=str, default="eth0", help="interface to send packets from" ) parser.add_argument( "--verbose", action="store_true", help="print verbose info about recved pckts" ) args = parser.parse_args() return args class FplaneTester(object): def __init__(self, args, queue): self._verbose = args.verbose self._katran_mac = args.katran_mac self._iface = args.iface self._missed_pckts = EXPECTED_REPLY self._recved_pckts = {} self._queue = queue def sniff_packets(self): pcap_filter = "ether src host {}".format(self._katran_mac) sniff(filter=pcap_filter, iface=self._iface, prn=self.process_received_packet) def send_test_pckts(self): for pckt in TEST_PCKTS: pckt.dst = self._katran_mac sendp(pckt, iface=self._iface) def check_pckt(self, pckt): marker_line = b"katran test pckt " init_index = pckt.find(marker_line) if init_index < 0: return index_start = init_index + len(marker_line) index_end = index_start + INDEX_LEN test_num = pckt[index_start:index_end] try: test_num = int(test_num) except ValueError: # we cant convert index to int. probably some bogus packet return if test_num in self._missed_pckts: self._recved_pckts[test_num] = self._missed_pckts[test_num] del self._missed_pckts[test_num] self._queue.put((self._recved_pckts, self._missed_pckts)) def read_queue(self): msg = self._queue.get(timeout=QUEUE_READ_TIMEOUT) self._recved_pckts = msg[RECVED_PCKTS] self._missed_pckts = msg[MISSED_PCKTS] def print_test_results(self): for test in self._recved_pckts.values(): print("test: {:70} passed".format(test)) for test in self._missed_pckts.values(): print("test: {:70} failed".format(test)) def process_received_packet(self, packet): if self._verbose: print(packet.show()) self.check_pckt(bytes(packet)) def main(): q = Queue() args = parse_args() tester = FplaneTester(args, q) p = Process(target=tester.sniff_packets) p.start() print("in output make sure that packets from 1 to 9 are recved!") time.sleep(5) print("starting tests") tester.send_test_pckts() while True: try: tester.read_queue() except Exception: p.terminate() # read from queue timed out break print("tests complited") tester.print_test_results() if __name__ == "__main__": main()