github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/acceptance/localcluster/tc/tc.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 // Package tc contains utility methods for using the Linux tc (traffic control) 12 // command to mess with the network links between cockroach nodes running on 13 // the local machine. 14 // 15 // Requires passwordless sudo in order to run tc. 16 // 17 // Does not work on OS X due to the lack of the tc command (and even an 18 // alternative wouldn't work for the current use case of this code, which also 19 // requires being able to bind to multiple localhost addresses). 20 package tc 21 22 import ( 23 "fmt" 24 "os/exec" 25 "strings" 26 "time" 27 28 "github.com/cockroachdb/errors" 29 ) 30 31 const ( 32 rootHandle = 1 33 defaultClass = 1 34 ) 35 36 // Controller provides a way to add artificial latency to local traffic. 37 type Controller struct { 38 interfaces []string 39 nextClass int 40 } 41 42 // NewController creates and returns a controller that will modify the traffic 43 // routing for the provided interfaces. 44 func NewController(interfaces ...string) *Controller { 45 return &Controller{ 46 interfaces: interfaces, 47 nextClass: defaultClass + 1, 48 } 49 } 50 51 // Init prepares the local network interfaces so that we can later add per-node 52 // traffic shaping rules. 53 func (c *Controller) Init() error { 54 for _, ifce := range c.interfaces { 55 _, _ = exec.Command("sudo", strings.Split(fmt.Sprintf("tc qdisc del dev %s root", ifce), " ")...).Output() 56 out, err := exec.Command("sudo", strings.Split(fmt.Sprintf("tc qdisc add dev %s root handle %d: htb default %d", 57 ifce, rootHandle, defaultClass), " ")...).Output() 58 if err != nil { 59 return errors.Wrapf(err, "failed to create root tc qdisc for %q: %s", ifce, out) 60 } 61 // The 100mbit limitation is because classes of type htb (hierarchy token 62 // bucket) need a bandwidth limit, and we want an arbitrarily high one. Feel 63 // free to bump it up here and below if you think it's limiting you. 64 out, err = exec.Command("sudo", strings.Split(fmt.Sprintf("tc class add dev %s parent %d: classid %d:%d htb rate 100mbit", 65 ifce, rootHandle, rootHandle, defaultClass), " ")...).Output() 66 if err != nil { 67 return errors.Wrapf(err, "failed to create root tc class for %q: %s", ifce, out) 68 } 69 } 70 return nil 71 } 72 73 // AddLatency adds artificial latency between the specified source and dest 74 // addresses. 75 func (c *Controller) AddLatency(srcIP, dstIP string, latency time.Duration) error { 76 class := c.nextClass 77 handle := class * 10 78 c.nextClass++ 79 for _, ifce := range c.interfaces { 80 out, err := exec.Command("sudo", strings.Split(fmt.Sprintf("tc class add dev %s parent %d: classid %d:%d htb rate 100mbit", 81 ifce, rootHandle, rootHandle, class), " ")...).Output() 82 if err != nil { 83 return errors.Wrapf(err, "failed to add tc class %d: %s", class, out) 84 } 85 out, err = exec.Command("sudo", strings.Split(fmt.Sprintf("tc qdisc add dev %s parent %d:%d handle %d: netem delay %v", 86 ifce, rootHandle, class, handle, latency), " ")...).Output() 87 if err != nil { 88 return errors.Wrapf(err, "failed to add tc netem delay of %v: %s", latency, out) 89 } 90 out, err = exec.Command("sudo", strings.Split(fmt.Sprintf("tc filter add dev %s parent %d: protocol ip u32 match ip src %s/32 match ip dst %s/32 flowid %d:%d", 91 ifce, rootHandle, srcIP, dstIP, rootHandle, class), " ")...).Output() 92 if err != nil { 93 return errors.Wrapf(err, "failed to add tc filter rule between %s and %s: %s", srcIP, dstIP, out) 94 } 95 } 96 return nil 97 } 98 99 // CleanUp resets all interfaces back to their default tc policies. 100 func (c *Controller) CleanUp() error { 101 var err error 102 for _, ifce := range c.interfaces { 103 out, thisErr := exec.Command("sudo", strings.Split(fmt.Sprintf("tc qdisc del dev %s root", ifce), " ")...).Output() 104 if err != nil { 105 err = errors.CombineErrors(err, errors.Wrapf( 106 thisErr, "failed to remove tc rules for %q -- you may have to remove them manually: %s", ifce, out)) 107 } 108 } 109 return err 110 }