test suite reviews and discussions
 help / color / mirror / Atom feed
* [dts] [PATCH 1/2] framework packet: support packet filter in sniff command
@ 2017-01-24  8:24 Marvin Liu
  2017-01-24  8:24 ` [dts] [PATCH 2/2] tests ipfrag: fix fragmented packet check incorrectly Marvin Liu
  0 siblings, 1 reply; 2+ messages in thread
From: Marvin Liu @ 2017-01-24  8:24 UTC (permalink / raw)
  To: dts; +Cc: Marvin Liu

This function can be helpful for filter out those unexpected packets.
Filter will be translated to bpf format and append into tcpdump command
line.

Signed-off-by: Marvin Liu <yong.liu@intel.com>

diff --git a/framework/packet.py b/framework/packet.py
index 05a3d1f..52b0821 100755
--- a/framework/packet.py
+++ b/framework/packet.py
@@ -41,6 +41,7 @@ import re
 import signal
 import random
 import subprocess
+import shlex        # separate command line for pipe
 from uuid import uuid4
 from settings import FOLDERS
 
@@ -95,6 +96,9 @@ SNIFF_PIDS = {}
 # used in pktgen or tgen
 PKTGEN_PIDS = {}
 
+# default filter for LLDP packet
+LLDP_FILTER = {'layer': 'ether', 'config': {'type': 'not lldp'}}
+
 
 class scapy(object):
     SCAPY_LAYERS = {
@@ -165,7 +169,7 @@ class scapy(object):
 
     def vlan(self, pkt_layer, vlan, prio=0, type=None):
         if pkt_layer.name != "802.1Q":
-           return
+            return
         pkt_layer.vlan = int(vlan)
         pkt_layer.prio = prio
         if type is not None:
@@ -183,7 +187,7 @@ class scapy(object):
 
     def etag(self, pkt_layer, ECIDbase=0, prio=0, type=None):
         if pkt_layer.name != "802.1BR":
-           return
+            return
         pkt_layer.ECIDbase = int(ECIDbase)
         pkt_layer.prio = prio
         if type is not None:
@@ -224,6 +228,8 @@ class scapy(object):
             value = layer.src
         elif element == 'dst':
             value = layer.dst
+        else:
+            value = layer.getfieldval(element)
 
         return value
 
@@ -237,6 +243,8 @@ class scapy(object):
             value = layer.sport
         elif element == 'dst':
             value = layer.dport
+        else:
+            value = layer.getfieldval(element)
 
         return value
 
@@ -303,7 +311,7 @@ class scapy(object):
         if payload is not None:
             pkt_layer.load = ''
             for hex1, hex2 in payload:
-                pkt_layer.load += struct.pack("=B", int('%s%s' %(hex1, hex2), 16))
+                pkt_layer.load += struct.pack("=B", int('%s%s' % (hex1, hex2), 16))
 
     def gre(self, pkt_layer, proto=None):
         if proto is not None:
@@ -499,11 +507,11 @@ class Packet(object):
             'VLAN': 'vlan',
             'ETAG': 'etag',
             'IP': 'ipv4',
-            'IPv4-TUNNEL':'inner_ipv4',
+            'IPv4-TUNNEL': 'inner_ipv4',
             'IPihl': 'ipv4ihl',
             'IPFRAG': 'ipv4_ext',
             'IPv6': 'ipv6',
-            'IPv6-TUNNEL':'inner_ipv6',
+            'IPv6-TUNNEL': 'inner_ipv6',
             'IPv6FRAG': 'ipv6_frag',
             'IPv6EXT': 'ipv6_ext',
             'IPv6EXT2': 'ipv6_ext2',
@@ -706,14 +714,68 @@ def save_packets(pkts=None, filename=None):
         pass
 
 
-def sniff_packets(intf, count=0, timeout=5):
+def get_ether_type(eth_type=""):
+    # need add more types later
+    if eth_type.lower() == "lldp":
+        return '0x88cc'
+    elif eth_type.lower() == "ip":
+        return '0x0800'
+    elif eth_type.lower() == "ipv6":
+        return '0x86dd'
+
+    return 'not support'
+
+
+def get_filter_cmd(filters=[]):
+    """
+    Return bpd formated filter string, only support ether layer now
+    """
+    filter_sep = " and "
+    filter_cmds = ""
+    for pktfilter in filters:
+        filter_cmd = ""
+        if pktfilter['layer'] == 'ether':
+            if pktfilter['config'].keys()[0] == 'dst':
+                dmac = pktfilter['config']['dst']
+                filter_cmd = "ether dst %s" % dmac
+            elif pktfilter['config'].keys()[0] == 'src':
+                smac = pktfilter['config']['src']
+                filter_cmd = "ether src %s" % smac
+            elif pktfilter['config'].keys()[0] == 'type':
+                eth_type = pktfilter['config']['type']
+                eth_format = r"(\w+) (\w+)"
+                m = re.match(eth_format, eth_type)
+                if m:
+                    type_hex = get_ether_type(m.group(2))
+                    if type_hex == 'not support':
+                        continue
+                    if m.group(1) == 'is':
+                        filter_cmd = 'ether[12:2] = %s' % type_hex
+                    elif m.group(1) == 'not':
+                        filter_cmd = 'ether[12:2] != %s' % type_hex
+
+        if len(filter_cmds):
+            if len(filter_cmd):
+                filter_cmds += filter_sep
+                filter_cmds += filter_cmd
+        else:
+            filter_cmds = filter_cmd
+
+    if len(filter_cmds):
+        return ' \'' + filter_cmds + '\' '
+    else:
+        return ""
+
+
+def sniff_packets(intf, count=0, timeout=5, filters=[]):
     """
     sniff all packets for certain port in certain seconds
     """
     param = ""
     direct_param = r"(\s+)\[ -(\w) in\|out\|inout \]"
     tcpdump_help = subprocess.check_output("tcpdump -h; echo 0",
-                        stderr=subprocess.STDOUT, shell=True)
+                                           stderr=subprocess.STDOUT,
+                                           shell=True)
     for line in tcpdump_help.split('\n'):
         m = re.match(direct_param, line)
         if m:
@@ -722,16 +784,23 @@ def sniff_packets(intf, count=0, timeout=5):
     if len(param) == 0:
         print "tcpdump not support direction chioce!!!"
 
-    sniff_cmd = 'tcpdump -i %(INTF)s %(IN_PARAM)s -w %(FILE)s'
+    if LLDP_FILTER not in filters:
+        filters.append(LLDP_FILTER)
+
+    filter_cmd = get_filter_cmd(filters)
+
+    sniff_cmd = 'tcpdump -i %(INTF)s %(FILTER)s %(IN_PARAM)s -w %(FILE)s'
     options = {'INTF': intf, 'COUNT': count, 'IN_PARAM': param,
-               'FILE': '/tmp/sniff_%s.pcap' % intf}
+               'FILE': '/tmp/sniff_%s.pcap' % intf,
+               'FILTER': filter_cmd}
     if count:
         sniff_cmd += ' -c %(COUNT)d'
         cmd = sniff_cmd % options
     else:
         cmd = sniff_cmd % options
 
-    args = cmd.split()
+    args = shlex.split(cmd)
+
     pipe = subprocess.Popen(args)
     index = str(time.time())
     SNIFF_PIDS[index] = (pipe, intf, timeout)
@@ -807,6 +876,7 @@ def compare_pktload(pkt1=None, pkt2=None, layer="L2"):
     else:
         return False
 
+
 def strip_pktload(pkt=None, layer="L2"):
     if layer == "L2":
         l_idx = 0
@@ -827,6 +897,8 @@ def strip_pktload(pkt=None, layer="L2"):
 ###############################################################################
 ###############################################################################
 if __name__ == "__main__":
+    inst = sniff_packets("lo", timeout=5, filters=[{'layer': 'ether',
+                         'config': {'dst': 'FF:FF:FF:FF:FF:FF'}}])
     inst = sniff_packets("lo", timeout=5)
     pkt = Packet(pkt_type='UDP')
     pkt.send_pkt(tx_port='lo')
@@ -858,6 +930,6 @@ if __name__ == "__main__":
                        'vxlan', 'inner_mac', 'inner_ipv4', 'inner_udp', 'raw'])
     # config packet
     pkt.config_layers([('ether', {'dst': '00:11:22:33:44:55'}), ('ipv4', {'dst': '1.1.1.1'}),
-                        ('vxlan', {'vni': 2}), ('raw', {'payload': ['01'] * 18})])
+                       ('vxlan', {'vni': 2}), ('raw', {'payload': ['01'] * 18})])
 
     pkt.send_pkt(tx_port='lo')
-- 
1.9.1

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [dts] [PATCH 2/2] tests ipfrag: fix fragmented packet check incorrectly
  2017-01-24  8:24 [dts] [PATCH 1/2] framework packet: support packet filter in sniff command Marvin Liu
@ 2017-01-24  8:24 ` Marvin Liu
  0 siblings, 0 replies; 2+ messages in thread
From: Marvin Liu @ 2017-01-24  8:24 UTC (permalink / raw)
  To: dts; +Cc: Marvin Liu

1. Utilize packet module to capture packets
2. Check fragmented flag and offset in packet header
3. No fragment packets should not be forwarded by sample

Signed-off-by: Marvin Liu <yong.liu@intel.com>

diff --git a/tests/TestSuite_ipfrag.py b/tests/TestSuite_ipfrag.py
index b2684f1..d2a1dbb 100644
--- a/tests/TestSuite_ipfrag.py
+++ b/tests/TestSuite_ipfrag.py
@@ -38,6 +38,8 @@ import utils
 import string
 import re
 import time
+from settings import HEADER_SIZE
+from packet import Packet, sniff_packets, load_sniff_packets
 
 lpm_table_ipv4 = [
     "{IPv4(100,10,0,0), 16, P1}",
@@ -122,11 +124,7 @@ l3fwd_ipv4_route_array[] = {\\\n"
         self.verify("Error" not in out, "compilation error 1")
         self.verify("No such file" not in out, "compilation error 2")
 
-    def functional_check_ipv4(self, cores, pkt_sizes, burst=1, flag=None, funtion=None):
-        """
-        Perform functional fragmentation checks.
-        """
-
+        cores = self.dut.get_core_list("1S/1C/2T")
         coremask = utils.create_mask(cores)
         portmask = utils.create_mask([P0, P1])
         numPortThread = len([P0, P1]) / len(cores)
@@ -137,112 +135,120 @@ l3fwd_ipv4_route_array[] = {\\\n"
         self.dut.send_expect("examples/ip_fragmentation/build/ip_fragmentation -c %s -n %d -- -p %s -q %s" % (
             coremask, self.dut.get_memory_channels(), portmask, numPortThread), "IP_FRAG:", 120)
 
-        txItf = self.tester.get_interface(self.tester.get_local_port(P0))
-        rxItf = self.tester.get_interface(self.tester.get_local_port(P1))
-        dmac = self.dut.get_mac_address(P0)
+        time.sleep(2)
+        self.txItf = self.tester.get_interface(self.tester.get_local_port(P0))
+        self.rxItf = self.tester.get_interface(self.tester.get_local_port(P1))
+        self.dmac = self.dut.get_mac_address(P0)
+
+    def functional_check_ipv4(self, pkt_sizes, burst=1, flag=None):
+        """
+        Perform functional fragmentation checks.
+        """
         for size in pkt_sizes[::burst]:
             # simulate to set TG properties
             if flag == 'frag':
-                # do fragment
-                expPkts = (1517 + size) / 1518
+                # do fragment, each packet max length 1518 - 18 - 20 = 1480
+                expPkts = (size - HEADER_SIZE['eth'] - HEADER_SIZE['ip']) / 1480
+                if (size - HEADER_SIZE['eth'] - HEADER_SIZE['ip']) % 1480:
+                    expPkts += 1
                 val = 0
+            elif flag == 'nofrag':
+                expPkts = 0
+                val = 2
             else:
                 expPkts = 1
                 val = 2
 
-            # set wait packet
-            self.tester.scapy_background()
-            self.tester.scapy_append('import string')
-            self.tester.scapy_append('p = sniff(iface="%s", count=%d, timeout=60)' % (rxItf, expPkts))
-            self.tester.scapy_append('nr_packets=len(p)')
-            self.tester.scapy_append('reslist = [p[i].sprintf("%IP.len%;%IP.id%;%IP.flags%;%IP.frag%") for i in range(nr_packets)]')
-            self.tester.scapy_append('RESULT = string.join(reslist, ",")')
-
+            inst = sniff_packets(intf=self.rxItf, timeout=5)
             # send packet
-            self.tester.scapy_foreground()
             for times in range(burst):
-                self.tester.scapy_append('sendp([Ether(dst="%s")/IP(dst="100.10.0.1",src="1.2.3.4",flags=%d)/Raw(load="X"*%d)], iface="%s")' % (dmac, val, pkt_sizes[pkt_sizes.index(size) + times] - 38, txItf))
-
-            self.tester.scapy_execute()
-            time.sleep(5)
-            out = self.tester.scapy_get_result()
-            nr_packets = len(out.split(','))
-            if funtion is not None:
-                if not funtion(size, out.split(',')):
-                    result = False
-                    errString = "failed on fragment check size " + str(size)
-                    break
-            elif nr_packets != expPkts:
-                result = False
-                errString = "Failed on forward packet size " + str(size)
-                break
-
-        self.dut.send_expect("^C", "#")
-        # verify on the bottom so as to keep safety quit application
-        self.verify(result, errString)
-
-    def functional_check_ipv6(self, cores, pkt_sizes, burst=1, flag=None, funtion=None):
+                pkt_size = pkt_sizes[pkt_sizes.index(size) + times]
+                pkt = Packet(pkt_type='UDP', pkt_len=pkt_size)
+                pkt.config_layer('ether', {'dst': self.dmac})
+                pkt.config_layer('ipv4', {'dst': '100.10.0.1', 'src': '1.2.3.4', 'flags': val})
+                pkt.send_pkt(tx_port=self.txItf)
+
+            # verify normal packet just by number, verify fragment packet by all elements
+            pkts = load_sniff_packets(inst)
+            self.verify(len(pkts) == expPkts, "Failed on forward packet size " + str(size))
+            if flag == 'frag':
+                idx = 1
+                for pkt in pkts:
+                    # packet index should be same
+                    pkt_id = pkt.strip_element_layer3("id")
+                    if idx == 1:
+                        prev_idx = pkt_id
+                    self.verify(prev_idx == pkt_id, "Fragmented packets index not match")
+                    prev_idx = pkt_id
+
+                    # last flags should be 0
+                    flags = pkt.strip_element_layer3("flags")
+                    if idx == expPkts:
+                        self.verify(flags == 0, "Fragmented last packet flags not match")
+                    else:
+                        self.verify(flags == 1, "Fragmented packets flags not match")
+
+                    # fragment offset should be correct
+                    frag = pkt.strip_element_layer3("frag")
+                    self.verify((frag == ((idx - 1) * 185)), "Fragment packet frag not match")
+                    idx += 1
+
+    def functional_check_ipv6(self, pkt_sizes, burst=1, flag=None, funtion=None):
         """
         Perform functional fragmentation checks.
         """
-        coremask = utils.create_mask(cores)
-        portmask = utils.create_mask([P0, P1])
-        numPortThread = len([P0, P1]) / len(cores)
-        result = True
-        errString = ''
-
-        # run ipv4_frag
-        self.dut.send_expect("examples/ip_fragmentation/build/ip_fragmentation -c %s -n %d -- -p %s -q %s" % (
-            coremask, self.dut.get_memory_channels(), portmask, numPortThread), "IP_FRAG:", 120)
-
-        txItf = self.tester.get_interface(self.tester.get_local_port(P0))
-        rxItf = self.tester.get_interface(self.tester.get_local_port(P1))
-        dmac = self.dut.get_mac_address(P0)
         for size in pkt_sizes[::burst]:
             # simulate to set TG properties
             if flag == 'frag':
-                # do fragment
-                expPkts = (1517 + size) / 1518
+                # each packet max len: 1518 - 18 (eth) - 40 (ipv6) - 8 (ipv6 ext hdr) = 1452
+                expPkts = (size - HEADER_SIZE['eth'] - HEADER_SIZE['ipv6']) / 1452
+                if (size - HEADER_SIZE['eth'] - HEADER_SIZE['ipv6']) % 1452:
+                    expPkts += 1
                 val = 0
             else:
                 expPkts = 1
                 val = 2
 
-            # set wait packet
-            self.tester.scapy_background()
-            self.tester.scapy_append('import string')
-            self.tester.scapy_append('p = sniff(iface="%s", count=%d, timeout=60)' % (rxItf, expPkts))
-            self.tester.scapy_append('nr_packets=len(p)')
-            self.tester.scapy_append('reslist = [p[i].sprintf("%IPv6.plen%;%IPv6.id%;%IPv6ExtHdrFragment.m%;%IPv6ExtHdrFragment.offset%") for i in range(nr_packets)]')
-            self.tester.scapy_append('RESULT = string.join(reslist, ",")')
-
+            inst = sniff_packets(intf=self.rxItf, timeout=5)
             # send packet
-            self.tester.scapy_foreground()
             for times in range(burst):
-                self.tester.scapy_append('sendp([Ether(dst="%s")/IPv6(dst="101:101:101:101:101:101:101:101",src="ee80:ee80:ee80:ee80:ee80:ee80:ee80:ee80")/Raw(load="X"*%d)], iface="%s")' % (dmac, pkt_sizes[pkt_sizes.index(size) + times] - 58, txItf))
-
-            self.tester.scapy_execute()
-            out = self.tester.scapy_get_result()
-            nr_packets = len(out.split(','))
-            if funtion is not None:
-                if not funtion(size, out.split(',')):
-                    result = False
-                    errString = "failed on fragment check size " + str(size)
-                    break
-            elif nr_packets != expPkts:
-                result = False
-                errString = "Failed on forward packet size " + str(size)
-                break
-
-        self.dut.send_expect("^C", "#")
-        # verify on the bottom so as to keep safety quit application
-        self.verify(result, errString)
+                pkt_size = pkt_sizes[pkt_sizes.index(size) + times]
+                pkt = Packet(pkt_type='IPv6_UDP', pkt_len=pkt_size)
+                pkt.config_layer('ether', {'dst': self.dmac})
+                pkt.config_layer('ipv6', {'dst': '101:101:101:101:101:101:101:101', 'src': 'ee80:ee80:ee80:ee80:ee80:ee80:ee80:ee80'})
+                pkt.send_pkt(tx_port=self.txItf)
+
+            # verify normal packet just by number, verify fragment packet by all elements
+            pkts = load_sniff_packets(inst)
+            self.verify(len(pkts) == expPkts, "Failed on forward packet size " + str(size))
+            if flag == 'frag':
+                idx = 1
+                for pkt in pkts:
+                    # packet index should be same
+                    pkt_id = pkt.strip_element_layer4("id")
+                    if idx == 1:
+                        prev_idx = pkt_id
+                    self.verify(prev_idx == pkt_id, "Fragmented packets index not match")
+                    prev_idx = pkt_id
+
+                    # last flags should be 0
+                    flags = pkt.strip_element_layer4("m")
+                    if idx == expPkts:
+                        self.verify(flags == 0, "Fragmented last packet flags not match")
+                    else:
+                        self.verify(flags == 1, "Fragmented packets flags not match")
+
+                    # fragment offset should be correct
+                    frag = pkt.strip_element_layer4("offset")
+                    self.verify((frag == int((idx - 1) * 181.5)), "Fragment packet frag not match")
+                    idx += 1
 
     def set_up(self):
         """
         Run before each test case.
         """
-        pass
+        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
+        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
 
     def test_ipfrag_normalfwd(self):
         """
@@ -250,25 +256,18 @@ l3fwd_ipv4_route_array[] = {\\\n"
         """
 
         sizelist = [64, 128, 256, 512, 1024, 1518]
-        cores = self.dut.get_core_list("1S/1C/2T")
 
-        self.functional_check_ipv4(cores, sizelist)
-        self.functional_check_ipv6(cores, sizelist)
+        self.functional_check_ipv4(sizelist)
+        self.functional_check_ipv6(sizelist)
 
     def test_ipfrag_nofragment(self):
         """
         Don't fragment test with 1519
         """
 
-        sizelist = [1519, 1518]
-        cores = self.dut.get_core_list("1S/1C/2T")
-        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
+        sizelist = [1519]
 
-        self.functional_check_ipv4(cores, sizelist, 2, 'nofrag')
-        self.functional_check_ipv6(cores, sizelist, 2, 'nofrag')
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
+        self.functional_check_ipv4(sizelist, 1, 'nofrag')
 
     def test_ipfrag_fragment(self):
         """
@@ -278,25 +277,8 @@ l3fwd_ipv4_route_array[] = {\\\n"
         sizelist = [1519, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000]
         cores = self.dut.get_core_list("1S/1C/2T")
 
-        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 9200" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
-
-        def chkfunc(size, output):
-            # check total size
-            if (1517 + size) / 1518 != len(output):
-                return False
-
-            # check every field in packet
-            for pkt in output:
-                _, _, _, _ = pkt.split(';')
-                # length, ID, fragoff, flags
-                pass
-            return True
-
-        self.functional_check_ipv4(cores, sizelist, 1, 'frag', chkfunc)
-        self.functional_check_ipv6(cores, sizelist, 1, 'frag', chkfunc)
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
+        self.functional_check_ipv4(sizelist, 1, 'frag')
+        self.functional_check_ipv6(sizelist, 1, 'frag')
 
     def benchmark(self, index, lcore, num_pthreads, size_list):
         """
@@ -358,9 +340,6 @@ l3fwd_ipv4_route_array[] = {\\\n"
         """
         Performance test for 64, 1518, 1519, 2k and 9k.
         """
-        self.tester.send_expect("ifconfig %s mtu 9600" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 9600" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
-
         sizes = [64, 1518, 1519, 2000, 9000]
 
         tblheader = ["Ports", "S/C/T", "SW threads"]
@@ -378,14 +357,12 @@ l3fwd_ipv4_route_array[] = {\\\n"
 
         self.result_table_print()
 
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
-        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
-
     def tear_down(self):
         """
         Run after each test case.
         """
-        pass
+        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P0)), "#")
+        self.tester.send_expect("ifconfig %s mtu 1500" % self.tester.get_interface(self.tester.get_local_port(P1)), "#")
 
     def tear_down_all(self):
         """
-- 
1.9.1

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2017-01-24  8:23 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-01-24  8:24 [dts] [PATCH 1/2] framework packet: support packet filter in sniff command Marvin Liu
2017-01-24  8:24 ` [dts] [PATCH 2/2] tests ipfrag: fix fragmented packet check incorrectly Marvin Liu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).