+ # Set up a subclass that represents the node bandwidth cap. We
+ # allow each slice to borrow up to this rate, so it is also
+ # usually the "ceil" rate for each slice.
+ tc("class add dev %s parent 1:1 classid 1:10 htb rate %dbit ceil %dbit" % \
+ (dev, bwmin, bwcap))
+
+ # Set up a subclass that represents "exemption" from the node
+ # bandwidth cap. Once the node bandwidth cap is reached, bandwidth
+ # to exempt destinations can still be fairly shared up to bwmax.
+ tc("class add dev %s parent 1:1 classid 1:20 htb rate %dbit ceil %dbit" % \
+ (dev, bwmin, bwmax))
+
+ # Set up the root class (and tell VNET what it is). Packets sent
+ # by root end up here and are capped at the node bandwidth
+ # cap.
+ on(root_xid, dev, share = root_share)
+ file("/proc/sys/vnet/root_class", "w").write("%d" % ((1 << 16) | default_minor | root_xid))
+
+ # Set up the default class. Packets that fail classification end
+ # up here.
+ on(default_xid, dev, share = default_share)
+
+
+# Get the bandwidth limits for a particular slice xid as a tuple (xid,
+# share, minrate, maxrate), or all classes as a list of tuples.
+def get(xid = None, dev = dev):
+ if xid is None:
+ ret = []
+ else:
+ ret = None
+
+ # class htb 1:1002 parent 1:10 leaf 81b3: prio 1 rate 8bit ceil 5000Kbit burst 1600b cburst 4Kb
+ for line in tc("-d class show dev %s" % dev):
+ # Search for child classes of 1:10
+ m = re.match(r"class htb 1:([0-9a-f]+) parent 1:10", line)
+ if m is None:
+ continue
+
+ # If we are looking for a particular class
+ classid = int(m.group(1), 16) & default_xid
+ if xid is not None and xid != classid:
+ continue
+
+ # Parse share
+ share = 1
+ m = re.search(r"quantum (\d+)", line)
+ if m is not None:
+ share = int(m.group(1)) / quantum
+
+ # Parse minrate
+ minrate = bwmin
+ m = re.search(r"rate (\w+)", line)
+ if m is not None:
+ minrate = get_tc_rate(m.group(1))
+
+ # Parse maxrate
+ maxrate = bwmax
+ m = re.search(r"ceil (\w+)", line)
+ if m is not None:
+ maxrate = get_tc_rate(m.group(1))
+
+ if xid is None:
+ # Return a list of parameters
+ ret.append((classid, share, minrate, maxrate))
+ else:
+ # Return the parameters for this class
+ ret = (classid, share, minrate, maxrate)
+ break
+
+ return ret
+
+
+# Apply specified bandwidth limit to the specified slice xid
+def on(xid, dev = dev, share = None, minrate = None, maxrate = None):
+ # Get defaults from current state if available
+ cap = get(xid, dev)
+ if cap is not None:
+ if share is None:
+ share = cap[1]
+ if minrate is None:
+ minrate = cap[2]
+ if maxrate is None:
+ maxrate = cap[3]
+
+ # Figure out what the current node bandwidth cap is
+ bwcap = bwmax
+ for line in tc("-d class show dev %s" % dev):
+ # Search for 1:10
+ m = re.match(r"class htb 1:10.*ceil (\w+)", line)
+ if m is not None:
+ bwcap = get_tc_rate(m.group(1))
+ break
+
+ # Set defaults
+ if share is None:
+ share = default_share
+ if minrate is None:
+ minrate = bwmin
+ else:
+ minrate = get_tc_rate(minrate)
+ if maxrate is None:
+ maxrate = bwcap
+ else:
+ maxrate = get_tc_rate(maxrate)
+
+ # Sanity checks
+ if maxrate > bwcap:
+ maxrate = bwcap
+ if minrate > maxrate:
+ minrate = maxrate
+
+ # Set up subclasses for the slice
+ tc("class replace dev %s parent 1:10 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
+ (dev, default_minor | xid, minrate, maxrate, share * quantum))
+
+ tc("class replace dev %s parent 1:20 classid 1:%x htb rate %dbit ceil %dbit quantum %d" % \
+ (dev, exempt_minor | xid, minrate, bwmax, share * quantum))
+
+ # Attach a FIFO to each subclass, which helps to throttle back
+ # processes that are sending faster than the token buckets can
+ # support.
+ tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
+ (dev, default_minor | xid, default_minor | xid))
+
+ tc("qdisc replace dev %s parent 1:%x handle %x pfifo" % \
+ (dev, exempt_minor | xid, exempt_minor | xid))
+
+
+# Remove class associated with specified slice xid. If further packets
+# are seen from this slice, they will be classified into the default
+# class 1:1FFF.
+def off(xid, dev = dev):
+ cap = get(xid, dev)
+ if cap is not None:
+ tc("class del dev %s classid 1:%x" % (dev, default_minor | xid))
+ tc("class del dev %s classid 1:%x" % (dev, exempt_minor | xid))
+
+
+def exempt_init(group_name, node_ips):
+
+ # Clean up
+ iptables = "/sbin/iptables -t vnet %s POSTROUTING"
+ run(iptables % "-F")
+ run("/sbin/ipset -X " + group_name)
+
+ # Create a hashed IP set of all of these destinations
+ lines = ["-N %s iphash" % group_name]
+ add_cmd = "-A %s " % group_name
+ lines += [(add_cmd + ip) for ip in node_ips]
+ lines += ["COMMIT"]
+ restore = "\n".join(lines) + "\n"
+ run("/sbin/ipset -R", restore)
+
+ # Add rule to match on destination IP set
+ run((iptables + " -m set --set %s dst -j CLASSIFY --set-class 1:%x") %
+ ("-A", group_name, exempt_minor))
+
+
+def usage():
+ bwcap_description = format_tc_rate(get_bwcap())
+
+ print """
+Usage:
+
+%s [OPTION]... [COMMAND] [ARGUMENT]...
+
+Options:
+ -d device Network interface (default: %s)
+ -r rate Node bandwidth cap (default: %s)
+ -q quantum Share multiplier (default: %d bytes)
+ -h This message
+
+Commands:
+ init
+ (Re)initialize bandwidth caps.
+ on slice [share] [minrate] [maxrate]
+ Set bandwidth cap for the specified slice
+ off slice
+ Remove bandwidth caps for the specified slice
+ get
+ Get all bandwidth caps
+ get slice
+ Get bandwidth caps for the specified slice
+ getcap slice
+ Get maxrate for the specified slice
+ setcap slice maxrate
+ Set maxrate for the specified slice
+""" % (sys.argv[0], dev, bwcap_description, quantum)
+ sys.exit(1)