From 2cea0cd6eef395b8832d2f51a34ad30af9c5cf63 Mon Sep 17 00:00:00 2001 From: Brenden Blanco Date: Tue, 16 Jun 2015 13:50:16 -0700 Subject: [PATCH] Add example for BPF+tc rate limiting * Adding an example to address #59, simulation of bandwidth sharing Signed-off-by: Brenden Blanco --- examples/tc_neighbor_sharing.c | 61 ++++++++++++++++++++ examples/tc_neighbor_sharing.py | 122 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 examples/tc_neighbor_sharing.c create mode 100755 examples/tc_neighbor_sharing.py diff --git a/examples/tc_neighbor_sharing.c b/examples/tc_neighbor_sharing.c new file mode 100644 index 0000000..6ba7d35 --- /dev/null +++ b/examples/tc_neighbor_sharing.c @@ -0,0 +1,61 @@ +// Copyright (c) PLUMgrid, Inc. +// Licensed under the Apache License, Version 2.0 (the "License") + +#include + +struct ipkey { + u32 client_ip; +}; + +BPF_TABLE("hash", struct ipkey, int, learned_ips, 1024); + +// trivial action +int pass(struct __sk_buff *skb) { + return 1; +} + +// Process each wan packet, and determine if the packet is in the IP +// table or not. Learned IPs are rate-limited and unclassified are not. +// returns: > 0 when an IP is known +// = 0 when an IP is not known, or non-IP traffic +int classify_wan(struct __sk_buff *skb) { + BEGIN(ethernet); + PROTO(ethernet) { + switch (ethernet->type) { + case 0x0800: goto ip; + } + goto EOP; + } + PROTO(ip) { + u32 dip = ip->dst; + struct ipkey key = {.client_ip=dip}; + int *val = learned_ips.lookup(&key); + if (val) + return *val; + goto EOP; + } +EOP: + return 0; +} + +// Process each neighbor packet, and store the source IP in the learned table. +// Mark the inserted entry with a non-zero value to be used by the classify_wan +// lookup. +int classify_neighbor(struct __sk_buff *skb) { + BEGIN(ethernet); + PROTO(ethernet) { + switch (ethernet->type) { + case 0x0800: goto ip; + } + goto EOP; + } + PROTO(ip) { + u32 sip = ip->src; + struct ipkey key = {.client_ip=sip}; + int val = 1; + learned_ips.lookup_or_init(&key, &val); + goto EOP; + } +EOP: + return 1; +} diff --git a/examples/tc_neighbor_sharing.py b/examples/tc_neighbor_sharing.py new file mode 100755 index 0000000..5add98d --- /dev/null +++ b/examples/tc_neighbor_sharing.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# Copyright (c) PLUMgrid, Inc. +# Licensed under the Apache License, Version 2.0 (the "License") + +# This example shows how a combination of BPF programs can be used to perform +# per-IP classification and rate limiting. The simulation in this example +# shows an example where N+M devices are combined and use 1 WAN. Traffic sent +# from/to the "neighbor" devices have their combined bandwidth capped at +# 128kbit, and the rest of the traffic can use an additional 1Mbit. + +# This works by sharing a map between various tc ingress filters, each with +# a related set of bpf functions attached. The map stores a list of dynamically +# learned ip addresses that were seen on the neighbor devices and should be +# throttled. + +# /------------\ | +# neigh1 --|->->->->->->->-| | | +# neigh2 --|->->->->->->->-| <-128kb-| /------\ | +# neigh3 --|->->->->->->->-| | wan0 | wan | | +# | ^ | br100 |-<-<-<--| sim | | +# | clsfy_neigh() | | ^ \------/ | +# lan1 ----|->->->->->->->-| <--1Mb--| | | +# lan2 ----|->->->->->->->-| | classify_wan() | +# ^ \------------/ | +# pass() | + + +from bpf import BPF +from pyroute2 import IPRoute, NetNS, IPDB, NSPopen +import sys +from time import sleep + +ipr = IPRoute() +ipdb = IPDB(nl=ipr) +b = BPF(src_file="tc_neighbor_sharing.c", debug=0) + +wan_fn = b.load_func("classify_wan", BPF.SCHED_CLS) +pass_fn = b.load_func("pass", BPF.SCHED_CLS) +neighbor_fn = b.load_func("classify_neighbor", BPF.SCHED_CLS) + +num_neighbors = 3 +num_locals = 2 + +# class to build the simulation network +class SharedNetSimulation(object): + + def __init__(self): + self.ipdbs = [] + self.namespaces = [] + self.processes = [] + + # Create the wan namespace, and attach an ingress filter for throttling + # inbound (download) traffic + (self.wan, wan_if) = self._create_ns("wan0", "172.16.1.5/24", None) + ipr.tc("add", "ingress", wan_if["index"], "ffff:") + ipr.tc("add-filter", "bpf", wan_if["index"], ":1", fd=wan_fn.fd, + prio=1, name=wan_fn.name, parent="ffff:", action="drop", + classid=1, rate="128kbit", burst=1024 * 32, mtu=16 * 1024) + ipr.tc("add-filter", "bpf", wan_if["index"], ":2", fd=pass_fn.fd, + prio=2, name=pass_fn.name, parent="ffff:", action="drop", + classid=2, rate="1024kbit", burst=1024 * 32, mtu=16 * 1024) + self.wan_if = wan_if + + # helper function to create a namespace and a veth connecting it + def _create_ns(self, name, ipaddr, fn): + ns_ipdb = IPDB(nl=NetNS(name)) + ipdb.create(ifname="%sa" % name, kind="veth", peer="%sb" % name).commit() + with ipdb.interfaces["%sb" % name] as v: + v.net_ns_fd = ns_ipdb.nl.netns + with ipdb.interfaces["%sa" % name] as v: + v.up() + with ns_ipdb.interfaces["%sb" % name] as v: + v.ifname = "eth0" + v.add_ip("%s" % ipaddr) + v.up() + ifc = ipdb.interfaces["%sa" % name] + if fn: + ipr.tc("add", "ingress", ifc["index"], "ffff:") + ipr.tc("add-filter", "bpf", ifc["index"], ":1", fd=fn.fd, name=fn.name, + parent="ffff:", action="ok", classid=1) + self.ipdbs.append(ns_ipdb) + self.namespaces.append(ns_ipdb.nl) + cmd = ["iperf", "-s", "-B", ipaddr.split("/")[0]] + self.processes.append(NSPopen(ns_ipdb.nl.netns, cmd)) + return (ns_ipdb, ifc) + + # start the namespaces that compose the network, interconnect them with the bridge, + # and attach the tc filters + def start(self): + neighbor_list = [] + local_list = [] + for i in range(0, num_neighbors): + neighbor_list.append(self._create_ns("neighbor%d" % i, "172.16.1.%d/24" % (i + 100), neighbor_fn)) + for i in range(0, num_locals): + local_list.append(self._create_ns("local%d" % i, "172.16.1.%d/24" % (i + 150), pass_fn)) + + with ipdb.create(ifname="br100", kind="bridge") as br100: + for x in neighbor_list: + br100.add_port(x[1]) + for x in local_list: + br100.add_port(x[1]) + br100.add_port(self.wan_if) + br100.up() + + def release(self): + for p in self.processes: p.kill(); p.release() + for db in self.ipdbs: db.release() + for ns in self.namespaces: ns.remove() + +try: + sim = SharedNetSimulation() + sim.start() + print("Network ready. Create a shell in the wan0 namespace and test with iperf") + print(" e.g.: ip netns exec wan0 iperf -t 2 -c 172.16.1.100") + input("Press enter when finished: ") +finally: + if "sim" in locals(): sim.release() + if "br100" in ipdb.interfaces: ipdb.interfaces.br100.remove().commit() + sleep(2) + ipdb.release() + + -- 2.7.4