--- /dev/null
+#include <bcc/proto.h>
+
+struct ipkey {
+ u32 inner_sip;
+ u32 inner_dip;
+ u32 outer_sip;
+ u32 outer_dip;
+ u32 vni;
+};
+struct counters {
+ u64 tx_pkts;
+ u64 rx_pkts;
+ u64 tx_bytes;
+ u64 rx_bytes;
+};
+
+BPF_TABLE("hash", struct ipkey, struct counters, stats, 1024);
+BPF_TABLE("prog", int, int, parser, 10);
+
+enum cb_index {
+ CB_FLAGS = 0,
+ CB_SIP,
+ CB_DIP,
+ CB_VNI,
+ CB_OFFSET,
+};
+
+// helper func to swap two memory locations
+static inline
+void swap32(u32 *a, u32 *b) {
+ u32 t = *a;
+ *a = *b;
+ *b = t;
+}
+
+// helper to swap the fields in an ipkey to give consistent ordering
+static inline
+void swap_ipkey(struct ipkey *key) {
+ swap32(&key->outer_sip, &key->outer_dip);
+ swap32(&key->inner_sip, &key->inner_dip);
+}
+
+#define IS_INGRESS 0x1
+// initial handler for each packet on an ingress tc filter
+int handle_ingress(struct __sk_buff *skb) {
+ skb->cb[CB_FLAGS] = IS_INGRESS;
+ parser.call(skb, 1); // jump to generic packet parser
+ return 1;
+}
+
+// initial handler for each packet on an egress tc filter
+int handle_egress(struct __sk_buff *skb) {
+ skb->cb[CB_FLAGS] = 0;
+ parser.call(skb, 1); // jump to generic packet parser
+ return 1;
+}
+
+// parse the outer vxlan frame
+int handle_outer(struct __sk_buff *skb) {
+ BEGIN(ethernet);
+ PROTO(ethernet) {
+ // filter bcast/mcast from the stats
+ if (ethernet->dst & (1ull << 40))
+ return 1;
+ switch (ethernet->type) {
+ case 0x0800: goto ip;
+ }
+ goto EOP;
+ }
+ PROTO(ip) {
+ skb->cb[CB_SIP] = ip->src;
+ skb->cb[CB_DIP] = ip->dst;
+ switch (ip->nextp) {
+ case 17: goto udp;
+ }
+ goto EOP;
+ }
+ PROTO(udp) {
+ switch (udp->dport) {
+ case 4789: goto vxlan;
+ }
+ goto EOP;
+ }
+ PROTO(vxlan) {
+ skb->cb[CB_VNI] = vxlan->key;
+ skb->cb[CB_OFFSET] = (u64)vxlan + sizeof(*vxlan);
+ parser.call(skb, 2);
+ goto EOP;
+ }
+EOP:
+ return 1;
+}
+
+// Parse the inner frame, whatever it may be. If it is ipv4, add the inner
+// source/dest ip to the key, for finer grained stats
+int handle_inner(struct __sk_buff *skb) {
+ int is_ingress = skb->cb[CB_FLAGS] & IS_INGRESS;
+ struct ipkey key = {
+ .vni=skb->cb[CB_VNI],
+ .outer_sip = skb->cb[CB_SIP],
+ .outer_dip = skb->cb[CB_DIP]
+ };
+ BEGIN_OFFSET(ethernet, skb->cb[CB_OFFSET]);
+ PROTO(ethernet) {
+ switch (ethernet->type) {
+ case 0x0800: goto ip;
+ }
+ goto EOP;
+ }
+ PROTO(ip) {
+ key.inner_sip = ip->src;
+ key.inner_dip = ip->dst;
+ goto EOP;
+ }
+EOP:
+ // consistent ordering
+ if (key.outer_dip < key.outer_sip)
+ swap_ipkey(&key);
+ struct counters zleaf = {0};
+ struct counters *leaf = stats.lookup_or_init(&key, &zleaf);
+ if (is_ingress) {
+ lock_xadd(&leaf->rx_pkts, 1);
+ lock_xadd(&leaf->rx_bytes, skb->len);
+ } else {
+ lock_xadd(&leaf->tx_pkts, 1);
+ lock_xadd(&leaf->tx_bytes, skb->len);
+ }
+ return 1;
+}
--- /dev/null
+#!/usr/bin/env python
+# Copyright (c) PLUMgrid, Inc.
+# Licensed under the Apache License, Version 2.0 (the "License")
+
+from bpf import BPF
+from ctypes import c_uint, c_int, c_ulonglong, Structure
+import json
+from netaddr import IPAddress
+from os import rename
+from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
+import sys
+from time import sleep
+
+ipr = IPRoute()
+ipdb = IPDB(nl=ipr)
+
+b = BPF(src_file="tunnel_monitor.c", debug=0)
+ingress_fn = b.load_func("handle_ingress", BPF.SCHED_CLS)
+egress_fn = b.load_func("handle_egress", BPF.SCHED_CLS)
+outer_fn = b.load_func("handle_outer", BPF.SCHED_CLS)
+inner_fn = b.load_func("handle_inner", BPF.SCHED_CLS)
+stats = b.get_table("stats")
+parser = b.get_table("parser")
+parser[c_int(1)] = c_int(outer_fn.fd)
+parser[c_int(2)] = c_int(inner_fn.fd)
+
+ifc = ipdb.interfaces.eth0
+
+# monitor one host...move this inside the netns to be more realistic
+ipr.tc("add", "ingress", ifc.index, "ffff:")
+ipr.tc("add-filter", "bpf", ifc.index, ":1", fd=ingress_fn.fd,
+ name=ingress_fn.name, parent="ffff:", action="ok", classid=1)
+ipr.tc("add", "sfq", ifc.index, "1:")
+ipr.tc("add-filter", "bpf", ifc.index, ":1", fd=egress_fn.fd,
+ name=egress_fn.name, parent="1:", action="ok", classid=1)
+
+def stats2json(k, v):
+ return {
+ "vni": int(k.vni),
+ "outer_sip": str(IPAddress(k.outer_sip)),
+ "outer_dip": str(IPAddress(k.outer_dip)),
+ "inner_sip": str(IPAddress(k.inner_sip)),
+ "inner_dip": str(IPAddress(k.inner_dip)),
+ "tx_pkts": v.tx_pkts, "tx_bytes": v.tx_bytes,
+ "rx_pkts": v.rx_pkts, "rx_bytes": v.rx_bytes,
+ }
+
+def delta_stats(v, oldv):
+ return stats.Leaf(v.tx_pkts - oldv.tx_pkts, v.rx_pkts - oldv.rx_pkts,
+ v.tx_bytes - oldv.tx_bytes, v.rx_bytes - oldv.rx_bytes)
+def key2str(k):
+ return "%s,%s,%d,%s,%s" % (IPAddress(k.outer_sip), IPAddress(k.outer_dip), k.vni,
+ IPAddress(k.inner_sip), IPAddress(k.inner_dip))
+
+prev = {}
+
+while True:
+ result_total = []
+ result_delta = []
+ tmp = {}
+ # compute both the total and last-N-seconds statistics
+ for k, v in stats.items():
+ # subtract the previous totals from the current, or 0 if none exists
+ v2 = delta_stats(v, prev.get(key2str(k), stats.Leaf(0, 0, 0, 0)))
+ if v2.tx_pkts != 0 or v2.rx_pkts != 0:
+ result_delta.append(stats2json(k, v2))
+ tmp[key2str(k)] = v
+ result_total.append(stats2json(k, v))
+
+ prev = tmp
+
+ with open("/root/chord-transitions/data/tunnel.json.new", "w") as f:
+ json.dump(result_total, f)
+ rename("/root/chord-transitions/data/tunnel.json.new", "/root/chord-transitions/data/tunnel.json")
+ with open("/root/chord-transitions/data/tunnel-delta.json.new", "w") as f:
+ json.dump(result_delta, f)
+ rename("/root/chord-transitions/data/tunnel-delta.json.new", "/root/chord-transitions/data/tunnel-delta.json")
+ sleep(5)
+ipdb.release()
+
--- /dev/null
+#!/usr/bin/env python
+# Copyright (c) PLUMgrid, Inc.
+# Licensed under the Apache License, Version 2.0 (the "License")
+
+from builtins import input
+from netaddr import IPNetwork
+from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
+from random import choice, randint
+from simulation import Simulation
+from socket import htons
+import sys
+
+ipr = IPRoute()
+ipdb = IPDB(nl=ipr)
+
+num_hosts = 9
+num_vnis = 4
+null = open("/dev/null", "w")
+
+class TunnelSimulation(Simulation):
+ def __init__(self, ipdb):
+ super(TunnelSimulation, self).__init__(ipdb)
+ self.available_ips = [list(IPNetwork("192.168.%d.0/24" % i)[1:-1])
+ for i in range(0, num_vnis)]
+
+ def start(self):
+ # each entry is tuple of ns_ipdb, out_ifc, in_ifc
+ host_info = []
+ for i in range(0, num_hosts):
+ ipaddr = "172.16.1.%d/24" % (100 + i)
+ host_info.append(self._create_ns("host%d" % i, ipaddr=ipaddr))
+ with self.ipdb.create(ifname="br100", kind="bridge") as br100:
+ for host in host_info: br100.add_port(host[1])
+ br100.up()
+ # create a vxlan device inside each namespace
+ for host in host_info:
+ cmd = ["netserver", "-D"]
+ self.processes.append(NSPopen(host[0].nl.netns, cmd, stdout=null))
+ for i in range(0, num_vnis):
+ with host[0].create(ifname="vxlan%d" % i, kind="vxlan", vxlan_id=10000 + i,
+ vxlan_link=host[0].interfaces.eth0,
+ vxlan_port=htons(4789), vxlan_group="239.1.1.%d" % (1 + i)) as vx:
+ vx.up()
+ with host[0].create(ifname="br%d" % i, kind="bridge") as br:
+ br.add_port(host[0].interfaces["vxlan%d" % i])
+ br.up()
+ with host[0].create(ifname="c%da" % i, kind="veth",
+ peer="c%db" % i) as c:
+ c.up()
+ c.add_ip("%s/24" % self.available_ips[i].pop(0))
+ c.mtu = 1450
+ br.add_port(host[0].interfaces["c%db" % i])
+ host[0].interfaces["c%db" % i].up().commit()
+
+ # pick one host to start the monitor in
+ host = host_info[0]
+ cmd = ["python", "tunnel_monitor.py"]
+ p = NSPopen(host[0].nl.netns, cmd)
+ self.processes.append(p)
+
+try:
+ sim = TunnelSimulation(ipdb)
+ sim.start()
+ input("Press enter to quit:")
+finally:
+ if "br100" in ipdb.interfaces: ipdb.interfaces.br100.remove().commit()
+ sim.release()
+ ipdb.release()
+ null.close()
--- /dev/null
+#!/bin/bash
+
+B=/usr/bin/byobu
+S=tunnel1
+
+tmux has-session -t $S &> /dev/null
+
+if [[ $? != 0 ]]; then
+ $B new-session -s $S -n "c1" -d
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.1 -s512" C-m
+ tmux new-window -t $S -n "c2"
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.2 -s128" C-m
+ tmux new-window -t $S -n "c3"
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.3 -s1024" C-m
+ tmux new-window -t $S -n "c3"
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.4 -s128" C-m
+ tmux new-window -t $S -n "c3"
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.5 -s128" C-m
+ tmux new-window -t $S -n "c3"
+ tmux send -t $S "ip netns exec host0 ping 192.168.0.6 -s128" C-m
+ tmux new-window -t $S -n "c4"
+ tmux send -t $S "ip netns exec host0 ping 192.168.1.2 -s128" C-m
+ tmux new-window -t $S -n "c5"
+ tmux send -t $S "ip netns exec host0 ping 192.168.1.4 -s768" C-m
+ tmux new-window -t $S -n "c2"
+ tmux send -t $S "ip netns exec host0 ping 192.168.2.2 -s128" C-m
+ tmux new-window -t $S -n "c3"
+ tmux send -t $S "ip netns exec host0 ping 192.168.2.7 -s1024" C-m
+ tmux new-window -t $S -n "c4"
+ tmux send -t $S "ip netns exec host0 ping 192.168.2.2 -s128" C-m
+ tmux new-window -t $S -n "c5"
+ tmux send -t $S "ip netns exec host0 ping 192.168.3.8 -s768" C-m
+ tmux new-window -t $S -n "c5"
+ tmux send -t $S "ip netns exec host0 ping 192.168.3.9 -s768" C-m
+fi
+
+exec tmux attach -t $S
+
from bpf import BPF
from builtins import input
-from ctypes import c_uint, c_int, c_ulonglong, Structure
from pyroute2 import IPRoute, NetNS, IPDB, NSPopen
from random import shuffle
from time import sleep
#define BEGIN(next) \
u64 _parse_cursor = 0; \
goto next
+#define BEGIN_OFFSET(next, _base_offset) \
+ u64 _parse_cursor = (_base_offset); \
+ goto next
#define PROTO(name) \
goto EOP; \
unsigned short cksum; // byte 16
unsigned short urg_ptr;
} __attribute__((packed));
+
+struct vxlan_t {
+ unsigned int rsv1:4;
+ unsigned int iflag:1;
+ unsigned int rsv2:3;
+ unsigned int rsv3:24;
+ unsigned int key:24;
+ unsigned int rsv4:8;
+} __attribute__((packed));