summaryrefslogtreecommitdiffstats
path: root/samples/pktgen/functions.sh
blob: c08cefb8eb1f5ad8a6eb177440c525e33a9037f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#
# Common functions used by pktgen scripts
#  - Depending on bash 3 (or higher) syntax
#
# Author: Jesper Dangaaard Brouer
# License: GPL

set -o errexit

## -- General shell logging cmds --
function err() {
    local exitcode=$1
    shift
    echo "ERROR: $@" >&2
    exit $exitcode
}

function warn() {
    echo "WARN : $@" >&2
}

function info() {
    if [[ -n "$VERBOSE" ]]; then
	echo "INFO : $@" >&2
    fi
}

## -- Pktgen proc config commands -- ##
export PROC_DIR=/proc/net/pktgen
#
# Three different shell functions for configuring the different
# components of pktgen:
#   pg_ctrl(), pg_thread() and pg_set().
#
# These functions correspond to pktgens different components.
# * pg_ctrl()   control "pgctrl" (/proc/net/pktgen/pgctrl)
# * pg_thread() control the kernel threads and binding to devices
# * pg_set()    control setup of individual devices
function pg_ctrl() {
    local proc_file="pgctrl"
    proc_cmd ${proc_file} "$@"
}

function pg_thread() {
    local thread=$1
    local proc_file="kpktgend_${thread}"
    shift
    proc_cmd ${proc_file} "$@"
}

function pg_set() {
    local dev=$1
    local proc_file="$dev"
    shift
    proc_cmd ${proc_file} "$@"
}

# More generic replacement for pgset(), that does not depend on global
# variable for proc file.
function proc_cmd() {
    local result
    local proc_file=$1
    local status=0
    # after shift, the remaining args are contained in $@
    shift
    local proc_ctrl=${PROC_DIR}/$proc_file
    if [[ ! -e "$proc_ctrl" ]]; then
	err 3 "proc file:$proc_ctrl does not exists (dev added to thread?)"
    else
	if [[ ! -w "$proc_ctrl" ]]; then
	    err 4 "proc file:$proc_ctrl not writable, not root?!"
	fi
    fi

    if [[ "$DEBUG" == "yes" ]]; then
	echo "cmd: $@ > $proc_ctrl"
    fi
    # Quoting of "$@" is important for space expansion
    echo "$@" > "$proc_ctrl" || status=$?

    if [[ "$proc_file" != "pgctrl" ]]; then
        result=$(grep "Result: OK:" $proc_ctrl) || true
        if [[ "$result" == "" ]]; then
            grep "Result:" $proc_ctrl >&2
        fi
    fi
    if (( $status != 0 )); then
	err 5 "Write error($status) occurred cmd: \"$@ > $proc_ctrl\""
    fi
}

# Old obsolete "pgset" function, with slightly improved err handling
function pgset() {
    local result

    if [[ "$DEBUG" == "yes" ]]; then
	echo "cmd: $1 > $PGDEV"
    fi
    echo $1 > $PGDEV
    local status=$?

    result=`cat $PGDEV | fgrep "Result: OK:"`
    if [[ "$result" == "" ]]; then
         cat $PGDEV | fgrep Result:
    fi
    if (( $status != 0 )); then
	err 5 "Write error($status) occurred cmd: \"$1 > $PGDEV\""
    fi
}

function trap_exit()
{
    # Cleanup pktgen setup on exit if thats not "append mode"
    if [[ -z "$APPEND" ]] && [[ $EUID -eq 0 ]]; then
        trap 'pg_ctrl "reset"' EXIT
    fi
}

## -- General shell tricks --

function root_check_run_with_sudo() {
    # Trick so, program can be run as normal user, will just use "sudo"
    #  call as root_check_run_as_sudo "$@"
    if [ "$EUID" -ne 0 ]; then
	if [ -x $0 ]; then # Directly executable use sudo
	    info "Not root, running with sudo"
            sudo -E "$0" "$@"
            exit $?
	fi
	err 4 "cannot perform sudo run of $0"
    fi
}

# Exact input device's NUMA node info
function get_iface_node()
{
    local node=$(</sys/class/net/$1/device/numa_node)
    if [[ $node == -1 ]]; then
        echo 0
    else
        echo $node
    fi
}

# Given an Dev/iface, get its queues' irq numbers
function get_iface_irqs()
{
	local IFACE=$1
	local queues="${IFACE}-.*TxRx"

	irqs=$(grep "$queues" /proc/interrupts | cut -f1 -d:)
	[ -z "$irqs" ] && irqs=$(grep $IFACE /proc/interrupts | cut -f1 -d:)
	[ -z "$irqs" ] && irqs=$(for i in `ls -Ux /sys/class/net/$IFACE/device/msi_irqs` ;\
	    do grep "$i:.*TxRx" /proc/interrupts | grep -v fdir | cut -f 1 -d : ;\
	    done)
	[ -z "$irqs" ] && err 3 "Could not find interrupts for $IFACE"

	echo $irqs
}

# Given a NUMA node, return cpu ids belonging to it.
function get_node_cpus()
{
	local node=$1
	local node_cpu_list
	local node_cpu_range_list=`cut -f1- -d, --output-delimiter=" " \
	                  /sys/devices/system/node/node$node/cpulist`

	for cpu_range in $node_cpu_range_list
	do
	    node_cpu_list="$node_cpu_list "`seq -s " " ${cpu_range//-/ }`
	done

	echo $node_cpu_list
}

# Check $1 is in between $2, $3 ($2 <= $1 <= $3)
function in_between() { [[ ($1 -ge $2) && ($1 -le $3) ]] ; }

# Extend shrunken IPv6 address.
# fe80::42:bcff:fe84:e10a => fe80:0:0:0:42:bcff:fe84:e10a
function extend_addr6()
{
    local addr=$1
    local sep=: sep2=::
    local sep_cnt=$(tr -cd $sep <<< $1 | wc -c)
    local shrink

    # separator count should be (2 <= $sep_cnt <= 7)
    if ! (in_between $sep_cnt 2 7); then
        err 5 "Invalid IP6 address: $1"
    fi

    # if shrink '::' occurs multiple, it's malformed.
    shrink=( $(grep -E -o "$sep{2,}" <<< $addr) )
    if [[ ${#shrink[@]} -ne 0 ]]; then
        if [[ ${#shrink[@]} -gt 1 || ( ${shrink[0]} != $sep2 ) ]]; then
            err 5 "Invalid IP6 address: $1"
        fi
    fi

    # add 0 at begin & end, and extend addr by adding :0
    [[ ${addr:0:1} == $sep ]] && addr=0${addr}
    [[ ${addr: -1} == $sep ]] && addr=${addr}0
    echo "${addr/$sep2/$(printf ':0%.s' $(seq $[8-sep_cnt])):}"
}

# Given a single IP(v4/v6) address, whether it is valid.
function validate_addr()
{
    # check function is called with (funcname)6
    [[ ${FUNCNAME[1]: -1} == 6 ]] && local IP6=6
    local bitlen=$[ IP6 ? 128 : 32 ]
    local len=$[ IP6 ? 8 : 4 ]
    local max=$[ 2**(len*2)-1 ]
    local net prefix
    local addr sep

    IFS='/' read net prefix <<< $1
    [[ $IP6 ]] && net=$(extend_addr6 $net)

    # if prefix exists, check (0 <= $prefix <= $bitlen)
    if [[ -n $prefix ]]; then
        if ! (in_between $prefix 0 $bitlen); then
            err 5 "Invalid prefix: /$prefix"
        fi
    fi

    # set separator for each IP(v4/v6)
    [[ $IP6 ]] && sep=: || sep=.
    IFS=$sep read -a addr <<< $net

    # array length
    if [[ ${#addr[@]} != $len ]]; then
        err 5 "Invalid IP$IP6 address: $1"
    fi

    # check each digit (0 <= $digit <= $max)
    for digit in "${addr[@]}"; do
        [[ $IP6 ]] && digit=$[ 16#$digit ]
        if ! (in_between $digit 0 $max); then
            err 5 "Invalid IP$IP6 address: $1"
        fi
    done

    return 0
}

function validate_addr6() { validate_addr $@ ; }

# Given a single IP(v4/v6) or CIDR, return minimum and maximum IP addr.
function parse_addr()
{
    # check function is called with (funcname)6
    [[ ${FUNCNAME[1]: -1} == 6 ]] && local IP6=6
    local net prefix
    local min_ip max_ip

    IFS='/' read net prefix <<< $1
    [[ $IP6 ]] && net=$(extend_addr6 $net)

    if [[ -z $prefix ]]; then
        min_ip=$net
        max_ip=$net
    else
        # defining array for converting Decimal 2 Binary
        # 00000000 00000001 00000010 00000011 00000100 ...
        local d2b='{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}'
        [[ $IP6 ]] && d2b+=$d2b
        eval local D2B=($d2b)

        local bitlen=$[ IP6 ? 128 : 32 ]
        local remain=$[ bitlen-prefix ]
        local octet=$[ IP6 ? 16 : 8 ]
        local min_mask max_mask
        local min max
        local ip_bit
        local ip sep

        # set separator for each IP(v4/v6)
        [[ $IP6 ]] && sep=: || sep=.
        IFS=$sep read -ra ip <<< $net

        min_mask="$(printf '1%.s' $(seq $prefix))$(printf '0%.s' $(seq $remain))"
        max_mask="$(printf '0%.s' $(seq $prefix))$(printf '1%.s' $(seq $remain))"

        # calculate min/max ip with &,| operator
        for i in "${!ip[@]}"; do
            digit=$[ IP6 ? 16#${ip[$i]} : ${ip[$i]} ]
            ip_bit=${D2B[$digit]}

            idx=$[ octet*i ]
            min[$i]=$[ 2#$ip_bit & 2#${min_mask:$idx:$octet} ]
            max[$i]=$[ 2#$ip_bit | 2#${max_mask:$idx:$octet} ]
            [[ $IP6 ]] && { min[$i]=$(printf '%X' ${min[$i]});
                            max[$i]=$(printf '%X' ${max[$i]}); }
        done

        min_ip=$(IFS=$sep; echo "${min[*]}")
        max_ip=$(IFS=$sep; echo "${max[*]}")
    fi

    echo $min_ip $max_ip
}

function parse_addr6() { parse_addr $@ ; }

# Given a single or range of port(s), return minimum and maximum port number.
function parse_ports()
{
    local port_str=$1
    local port_list
    local min_port
    local max_port

    IFS="-" read -ra port_list <<< $port_str

    min_port=${port_list[0]}
    max_port=${port_list[1]:-$min_port}

    echo $min_port $max_port
}

# Given a minimum and maximum port, verify port number.
function validate_ports()
{
    local min_port=$1
    local max_port=$2

    # 1 <= port <= 65535
    if (in_between $min_port 1 65535); then
	if (in_between $max_port 1 65535); then
	    if [[ $min_port -le $max_port ]]; then
		return 0
	    fi
	fi
    fi

    err 5 "Invalid port(s): $min_port-$max_port"
}