├── Makefile ├── README.md ├── container_utils └── host-ip.sh └── ctrlnet /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR=/usr/bin 2 | 3 | all: 4 | 5 | install: 6 | cp ctrlnet ${DESTDIR}/ctrlnet 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # testnet-utils 2 | 3 | Right now, this is just a set of shell scripts to control network conditions 4 | between docker containers. In the future, I might make this an actual binary 5 | or something. 6 | 7 | ## Installation 8 | Requires permissions to write to `/usr/bin` 9 | ``` 10 | $ make install 11 | ``` 12 | 13 | ## Usage 14 | Note: all commands require root permission. I use this tool in a VM and have 15 | setuid on the `tc` tool. 16 | 17 | ``` 18 | $ ctrlnet lat 150ms 19 | $ ctrlnet lat off 20 | $ ctrlnet lat 150ms 10ms # adds a 10ms jitter, normally distributed 21 | $ ctrlnet rate 10mbit # set 5mbit bandwidth cap 22 | ``` 23 | 24 | ## Try it out 25 | Clone the script down, and make sure docker is running, then: 26 | ``` 27 | $ docker pull ubuntu 28 | ``` 29 | 30 | Now, run this twice, in two separate terminals: 31 | ``` 32 | $ docker run -ti ubuntu /bin/bash 33 | ``` 34 | 35 | In one of them, use `ip addr` to find its ip, and then ping that ip from the 36 | other container. It should be quite low, less than 0.1ms. 37 | 38 | Now, on the host, run: 39 | ``` 40 | $ ctrlnet lat 40ms 41 | ``` 42 | 43 | You should see the rtt of the pings spike to 100ms 44 | 45 | Then try: 46 | ``` 47 | $ ctrlnet lat off 48 | ``` 49 | 50 | And it will return to normal. 51 | 52 | You can also specify your own (as seen above): 53 | ``` 54 | $ ctrlnet lat 20ms 55 | ``` 56 | This will set a 20ms delay on *each* link, meaning the total RTT will be 40ms. 57 | 58 | Having the same exact latency constantly isnt really how real network work, so 59 | if you like, you can throw in some jitter: 60 | ``` 61 | $ ctrlnet lat 50ms 5ms 62 | ``` 63 | 64 | This will add a 5ms jitter to each link, based on a normal distribution, meaning 65 | that the total RTT between the nodes should be from 90ms to 110ms 66 | -------------------------------------------------------------------------------- /container_utils/host-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /sbin/ip route | awk '/default/ { print $3 }' 3 | -------------------------------------------------------------------------------- /ctrlnet: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # lists out all veth interfaces (naming convention used by docker) 4 | list_veth_interfaces() { 5 | ls /sys/devices/virtual/net | grep veth 6 | } 7 | 8 | # set_latency [] 9 | # 10 | # sets the latency of a given interface 11 | set_latency() { 12 | iface=$1 13 | lat=$2 14 | opts= 15 | 16 | jitter=$3 17 | 18 | lat=${2:-50ms} 19 | if [ -n "$jitter" ] 20 | then 21 | lat="$lat $jitter distribution normal" 22 | fi 23 | 24 | # check if there is a bandwidth rate limit set already 25 | rateval=$(current_if_rate $iface) 26 | if [ -n "$rateval" ] 27 | then 28 | rateval="rate $rateval" 29 | fi 30 | 31 | echo setting latency on $iface to $lat 32 | tc qdisc change dev $iface root netem delay $lat $rateval 33 | } 34 | 35 | # current_if_latency 36 | # 37 | # returns the latency already applied to the given interface 38 | current_if_latency() { 39 | maybedelay=$(tc qdisc show dev $1 | grep delay) 40 | if [ -z "$maybedelay" ] 41 | then 42 | return 43 | fi 44 | nmargs=$(echo $maybedelay | sed 's/.*\sdelay\s\([^$]*\).*/\1/') 45 | mjitter=$(echo $nmargs | awk '{ print $2 }') 46 | if [ $mjitter == "rate" ] || [ -z $mjitter ] 47 | then 48 | echo $nmargs | awk '{ print $1 }' 49 | return 50 | fi 51 | 52 | echo $nmargs | awk '{ printf "%s %s distribution normal", $1, $2 }' 53 | } 54 | 55 | # current_if_rate 56 | # 57 | # returns the bandwidth rate limit already applied to the given interface 58 | current_if_rate() { 59 | tc qdisc show dev $1 | grep rate | sed 's/.*\srate\s\([^ ]*\).*/\1/' 60 | } 61 | 62 | # latency_control < [jitter] | off > 63 | # 64 | # returns the bandwidth rate limit already applied to the given interface 65 | latency_control() { 66 | com=$1 67 | shift 68 | args=$@ 69 | 70 | for i in `list_veth_interfaces` 71 | do 72 | init_interface $i 73 | case $com in 74 | *ms) 75 | set_latency $i $com $args 76 | ;; 77 | off) 78 | set_latency $i 0ms 79 | ;; 80 | *) 81 | echo not recognized: \'$com\' 82 | echo either specify a delay in ms or 'off' 83 | exit 1 84 | esac 85 | done 86 | } 87 | 88 | # init_interface 89 | # 90 | # init interface ensures that the qdisc has been created for the given 91 | # interface, if not, it creates one 92 | init_interface() { 93 | iface=$1 94 | if tc qdisc show dev $iface | grep root > /dev/null 95 | then 96 | return 97 | else 98 | tc qdisc add dev $iface root netem delay 0ms 99 | fi 100 | } 101 | 102 | # clears all settings on all interfaces 103 | clear_all() { 104 | for i in `list_veth_interfaces` 105 | do 106 | clear_iface $i 107 | done 108 | } 109 | 110 | # clear_iface 111 | clear_iface() { 112 | iface=$1 113 | tc qdisc del dev $iface root 114 | } 115 | 116 | 117 | # set_ratelimit 118 | # 119 | # sets the bandwidth limit on a given interface 120 | set_ratelimit() { 121 | iface=$1 122 | rate=$2 123 | 124 | # check if there is a latency set already 125 | latval=$(current_if_latency $iface) 126 | if [ -n "$latval" ] 127 | then 128 | latval="delay $latval" 129 | fi 130 | 131 | echo setting bandwidth on $iface to $rate with lat = $latval 132 | tc qdisc change dev $iface root netem $latval rate $rate 133 | } 134 | 135 | # rate_control < rate | off > 136 | # 137 | # sets bandwidth limit on all veth interfaces 138 | rate_control() { 139 | com=$1 140 | shift 141 | args=$@ 142 | 143 | for i in `list_veth_interfaces` 144 | do 145 | init_interface $i 146 | case $com in 147 | off) 148 | lat=$(current_if_latency $i) 149 | clear_iface $i 150 | set_latency $i $lat 151 | ;; 152 | *) 153 | set_ratelimit $i $com 154 | ;; 155 | esac 156 | done 157 | } 158 | 159 | # TODO: loss correctly 160 | set_loss() { 161 | iface=$1 162 | prob=$2 163 | tc qdisc change dev $iface root netem loss $prob 164 | } 165 | 166 | loss_control() { 167 | prob=$1 168 | if [ -z $prob ] 169 | then 170 | prob=0.1% 171 | fi 172 | for i in `list_veth_interfaces` 173 | do 174 | init_interface $i 175 | set_loss $i $prob 176 | done 177 | } 178 | 179 | # show_opts 180 | # 181 | # shows settings for all veth interfaces 182 | show_opts() { 183 | for i in `list_veth_interfaces` 184 | do 185 | lat=$(current_if_latency $i) 186 | lat=${lat:-unset} 187 | rate=$(current_if_rate $i) 188 | rate=${rate:-unset} 189 | echo $i: lat=\"$lat\" rate=\"$rate\" 190 | 191 | done 192 | } 193 | 194 | usage() { 195 | echo ctrlnet - a utility for simulating network conditions 196 | echo usage: 197 | echo 198 | echo Set some latency: 199 | echo \$ ctrlnet lat 50ms 200 | echo 201 | echo Now, with jitter: 202 | echo \$ ctrlnet lat 50ms 6ms 203 | echo 204 | echo Set some bandwidth limits 205 | echo \$ ctrlnet rate 100kbit 206 | } 207 | 208 | com=$1 209 | shift 210 | args=$@ 211 | 212 | if [ -z $com ] 213 | then 214 | usage 215 | exit 0 216 | fi 217 | 218 | case $com in 219 | lat) 220 | latency_control $args 221 | ;; 222 | loss) 223 | echo "packet loss control not working yet" 224 | exit 1 225 | loss_control $args 226 | ;; 227 | rate) 228 | rate_control $args 229 | ;; 230 | ls) 231 | show_opts 232 | ;; 233 | clear) 234 | clear_all 235 | ;; 236 | *) 237 | echo unrecognized command $com 238 | usage 239 | exit 1 240 | esac 241 | 242 | --------------------------------------------------------------------------------