diff options
Diffstat (limited to 'tools/bpf/bpftool')
| -rw-r--r-- | tools/bpf/bpftool/Documentation/Makefile | 34 | ||||
| -rw-r--r-- | tools/bpf/bpftool/Documentation/bpftool-map.rst | 131 | ||||
| -rw-r--r-- | tools/bpf/bpftool/Documentation/bpftool-prog.rst | 150 | ||||
| -rw-r--r-- | tools/bpf/bpftool/Documentation/bpftool.rst | 56 | ||||
| -rw-r--r-- | tools/bpf/bpftool/Makefile | 93 | ||||
| -rw-r--r-- | tools/bpf/bpftool/bash-completion/bpftool | 354 | ||||
| -rw-r--r-- | tools/bpf/bpftool/common.c | 405 | ||||
| -rw-r--r-- | tools/bpf/bpftool/jit_disasm.c | 162 | ||||
| -rw-r--r-- | tools/bpf/bpftool/json_writer.c | 356 | ||||
| -rw-r--r-- | tools/bpf/bpftool/json_writer.h | 72 | ||||
| -rw-r--r-- | tools/bpf/bpftool/main.c | 343 | ||||
| -rw-r--r-- | tools/bpf/bpftool/main.h | 123 | ||||
| -rw-r--r-- | tools/bpf/bpftool/map.c | 899 | ||||
| -rw-r--r-- | tools/bpf/bpftool/prog.c | 672 | 
14 files changed, 3850 insertions, 0 deletions
diff --git a/tools/bpf/bpftool/Documentation/Makefile b/tools/bpf/bpftool/Documentation/Makefile new file mode 100644 index 000000000000..37292bb5ce60 --- /dev/null +++ b/tools/bpf/bpftool/Documentation/Makefile @@ -0,0 +1,34 @@ +include ../../../scripts/Makefile.include +include ../../../scripts/utilities.mak + +INSTALL ?= install +RM ?= rm -f + +# Make the path relative to DESTDIR, not prefix +ifndef DESTDIR +prefix ?= /usr/local +endif +mandir ?= $(prefix)/share/man +man8dir = $(mandir)/man8 + +MAN8_RST = $(wildcard *.rst) + +_DOC_MAN8 = $(patsubst %.rst,%.8,$(MAN8_RST)) +DOC_MAN8 = $(addprefix $(OUTPUT),$(_DOC_MAN8)) + +man: man8 +man8: $(DOC_MAN8) + +$(OUTPUT)%.8: %.rst +	rst2man $< > $@ + +clean: +	$(call QUIET_CLEAN, Documentation) $(RM) $(DOC_MAN8) + +install: man +	$(call QUIET_INSTALL, Documentation-man) \ +		$(INSTALL) -d -m 755 $(DESTDIR)$(man8dir); \ +		$(INSTALL) -m 644 $(DOC_MAN8) $(DESTDIR)$(man8dir); + +.PHONY: man man8 clean install +.DEFAULT_GOAL := man diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst new file mode 100644 index 000000000000..9f51a268eb06 --- /dev/null +++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst @@ -0,0 +1,131 @@ +================ +bpftool-map +================ +------------------------------------------------------------------------------- +tool for inspection and simple manipulation of eBPF maps +------------------------------------------------------------------------------- + +:Manual section: 8 + +SYNOPSIS +======== + +	**bpftool** [*OPTIONS*] **map** *COMMAND* + +	*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } } + +	*COMMANDS* := +	{ **show** | **dump** | **update** | **lookup** | **getnext** | **delete** +	| **pin** | **help** } + +MAP COMMANDS +============= + +|	**bpftool** **map show**   [*MAP*] +|	**bpftool** **map dump**    *MAP* +|	**bpftool** **map update**  *MAP*  **key** *BYTES*   **value** *VALUE* [*UPDATE_FLAGS*] +|	**bpftool** **map lookup**  *MAP*  **key** *BYTES* +|	**bpftool** **map getnext** *MAP* [**key** *BYTES*] +|	**bpftool** **map delete**  *MAP*  **key** *BYTES* +|	**bpftool** **map pin**     *MAP*  *FILE* +|	**bpftool** **map help** +| +|	*MAP* := { **id** *MAP_ID* | **pinned** *FILE* } +|	*VALUE* := { *BYTES* | *MAP* | *PROGRAM* } +|	*UPDATE_FLAGS* := { **any** | **exist** | **noexist** } + +DESCRIPTION +=========== +	**bpftool map show**   [*MAP*] +		  Show information about loaded maps.  If *MAP* is specified +		  show information only about given map, otherwise list all +		  maps currently loaded on the system. + +		  Output will start with map ID followed by map type and +		  zero or more named attributes (depending on kernel version). + +	**bpftool map dump**    *MAP* +		  Dump all entries in a given *MAP*. + +	**bpftool map update**  *MAP*  **key** *BYTES*   **value** *VALUE* [*UPDATE_FLAGS*] +		  Update map entry for a given *KEY*. + +		  *UPDATE_FLAGS* can be one of: **any** update existing entry +		  or add if doesn't exit; **exist** update only if entry already +		  exists; **noexist** update only if entry doesn't exist. + +	**bpftool map lookup**  *MAP*  **key** *BYTES* +		  Lookup **key** in the map. + +	**bpftool map getnext** *MAP* [**key** *BYTES*] +		  Get next key.  If *key* is not specified, get first key. + +	**bpftool map delete**  *MAP*  **key** *BYTES* +		  Remove entry from the map. + +	**bpftool map pin**     *MAP*  *FILE* +		  Pin map *MAP* as *FILE*. + +		  Note: *FILE* must be located in *bpffs* mount. + +	**bpftool map help** +		  Print short help message. + +OPTIONS +======= +	-h, --help +		  Print short generic help message (similar to **bpftool help**). + +	-v, --version +		  Print version number (similar to **bpftool version**). + +	-j, --json +		  Generate JSON output. For commands that cannot produce JSON, this +		  option has no effect. + +	-p, --pretty +		  Generate human-readable JSON output. Implies **-j**. + +	-f, --bpffs +		  Show file names of pinned maps. + +EXAMPLES +======== +**# bpftool map show** +:: + +  10: hash  name some_map  flags 0x0 +	key 4B  value 8B  max_entries 2048  memlock 167936B + +**# bpftool map update id 10 key 13 00 07 00 value 02 00 00 00 01 02 03 04** + +**# bpftool map lookup id 10 key 0 1 2 3** + +:: + +  key: 00 01 02 03 value: 00 01 02 03 04 05 06 07 + + +**# bpftool map dump id 10** +:: + +  key: 00 01 02 03  value: 00 01 02 03 04 05 06 07 +  key: 0d 00 07 00  value: 02 00 00 00 01 02 03 04 +  Found 2 elements + +**# bpftool map getnext id 10 key 0 1 2 3** +:: + +  key: +  00 01 02 03 +  next key: +  0d 00 07 00 + +| +| **# mount -t bpf none /sys/fs/bpf/** +| **# bpftool map pin id 10 /sys/fs/bpf/map** +| **# bpftool map del pinned /sys/fs/bpf/map key 13 00 07 00** + +SEE ALSO +======== +	**bpftool**\ (8), **bpftool-prog**\ (8) diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst new file mode 100644 index 000000000000..36e8d1c3c40d --- /dev/null +++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst @@ -0,0 +1,150 @@ +================ +bpftool-prog +================ +------------------------------------------------------------------------------- +tool for inspection and simple manipulation of eBPF progs +------------------------------------------------------------------------------- + +:Manual section: 8 + +SYNOPSIS +======== + +	**bpftool** [*OPTIONS*] **prog** *COMMAND* + +	*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } } + +	*COMMANDS* := +	{ **show** | **dump xlated** | **dump jited** | **pin** | **help** } + +MAP COMMANDS +============= + +|	**bpftool** **prog show** [*PROG*] +|	**bpftool** **prog dump xlated** *PROG* [{**file** *FILE* | **opcodes**}] +|	**bpftool** **prog dump jited**  *PROG* [{**file** *FILE* | **opcodes**}] +|	**bpftool** **prog pin** *PROG* *FILE* +|	**bpftool** **prog help** +| +|	*PROG* := { **id** *PROG_ID* | **pinned** *FILE* | **tag** *PROG_TAG* } + +DESCRIPTION +=========== +	**bpftool prog show** [*PROG*] +		  Show information about loaded programs.  If *PROG* is +		  specified show information only about given program, otherwise +		  list all programs currently loaded on the system. + +		  Output will start with program ID followed by program type and +		  zero or more named attributes (depending on kernel version). + +	**bpftool prog dump xlated** *PROG* [{ **file** *FILE* | **opcodes** }] +		  Dump eBPF instructions of the program from the kernel. +		  If *FILE* is specified image will be written to a file, +		  otherwise it will be disassembled and printed to stdout. + +		  **opcodes** controls if raw opcodes will be printed. + +	**bpftool prog dump jited**  *PROG* [{ **file** *FILE* | **opcodes** }] +		  Dump jited image (host machine code) of the program. +		  If *FILE* is specified image will be written to a file, +		  otherwise it will be disassembled and printed to stdout. + +		  **opcodes** controls if raw opcodes will be printed. + +	**bpftool prog pin** *PROG* *FILE* +		  Pin program *PROG* as *FILE*. + +		  Note: *FILE* must be located in *bpffs* mount. + +	**bpftool prog help** +		  Print short help message. + +OPTIONS +======= +	-h, --help +		  Print short generic help message (similar to **bpftool help**). + +	-v, --version +		  Print version number (similar to **bpftool version**). + +	-j, --json +		  Generate JSON output. For commands that cannot produce JSON, this +		  option has no effect. + +	-p, --pretty +		  Generate human-readable JSON output. Implies **-j**. + +	-f, --bpffs +		  Show file names of pinned programs. + +EXAMPLES +======== +**# bpftool prog show** +:: + +  10: xdp  name some_prog  tag 005a3d2123620c8b +	loaded_at Sep 29/20:11  uid 0 +	xlated 528B  jited 370B  memlock 4096B  map_ids 10 + +**# bpftool --json --pretty prog show** + +:: + +    { +        "programs": [{ +                "id": 10, +                "type": "xdp", +                "tag": "005a3d2123620c8b", +                "loaded_at": "Sep 29/20:11", +                "uid": 0, +                "bytes_xlated": 528, +                "jited": true, +                "bytes_jited": 370, +                "bytes_memlock": 4096, +                "map_ids": [10 +                ] +            } +        ] +    } + +| +| **# bpftool prog dump xlated id 10 file /tmp/t** +| **# ls -l /tmp/t** +|   -rw------- 1 root root 560 Jul 22 01:42 /tmp/t + +**# bpftool prog dum jited tag 005a3d2123620c8b** + +:: + +    push   %rbp +    mov    %rsp,%rbp +    sub    $0x228,%rsp +    sub    $0x28,%rbp +    mov    %rbx,0x0(%rbp) + +| +| **# mount -t bpf none /sys/fs/bpf/** +| **# bpftool prog pin id 10 /sys/fs/bpf/prog** +| **# ls -l /sys/fs/bpf/** +|   -rw------- 1 root root 0 Jul 22 01:43 prog + +**# bpftool prog dum jited pinned /sys/fs/bpf/prog opcodes** + +:: + +    push   %rbp +    55 +    mov    %rsp,%rbp +    48 89 e5 +    sub    $0x228,%rsp +    48 81 ec 28 02 00 00 +    sub    $0x28,%rbp +    48 83 ed 28 +    mov    %rbx,0x0(%rbp) +    48 89 5d 00 + + +SEE ALSO +======== +	**bpftool**\ (8), **bpftool-map**\ (8) diff --git a/tools/bpf/bpftool/Documentation/bpftool.rst b/tools/bpf/bpftool/Documentation/bpftool.rst new file mode 100644 index 000000000000..926c03d5a8da --- /dev/null +++ b/tools/bpf/bpftool/Documentation/bpftool.rst @@ -0,0 +1,56 @@ +================ +BPFTOOL +================ +------------------------------------------------------------------------------- +tool for inspection and simple manipulation of eBPF programs and maps +------------------------------------------------------------------------------- + +:Manual section: 8 + +SYNOPSIS +======== + +	**bpftool** [*OPTIONS*] *OBJECT* { *COMMAND* | **help** } + +	**bpftool** **batch file** *FILE* + +	**bpftool** **version** + +	*OBJECT* := { **map** | **program** } + +	*OPTIONS* := { { **-V** | **--version** } | { **-h** | **--help** } +	| { **-j** | **--json** } [{ **-p** | **--pretty** }] } + +	*MAP-COMMANDS* := +	{ **show** | **dump** | **update** | **lookup** | **getnext** | **delete** +	| **pin** | **help** } + +	*PROG-COMMANDS* := { **show** | **dump jited** | **dump xlated** | **pin** +	| **help** } + +DESCRIPTION +=========== +	*bpftool* allows for inspection and simple modification of BPF objects +	on the system. + +	Note that format of the output of all tools is not guaranteed to be +	stable and should not be depended upon. + +OPTIONS +======= +	-h, --help +		  Print short help message (similar to **bpftool help**). + +	-v, --version +		  Print version number (similar to **bpftool version**). + +	-j, --json +		  Generate JSON output. For commands that cannot produce JSON, this +		  option has no effect. + +	-p, --pretty +		  Generate human-readable JSON output. Implies **-j**. + +SEE ALSO +======== +	**bpftool-map**\ (8), **bpftool-prog**\ (8) diff --git a/tools/bpf/bpftool/Makefile b/tools/bpf/bpftool/Makefile new file mode 100644 index 000000000000..ec3052c0b004 --- /dev/null +++ b/tools/bpf/bpftool/Makefile @@ -0,0 +1,93 @@ +include ../../scripts/Makefile.include + +include ../../scripts/utilities.mak + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +#$(info Determined 'srctree' to be $(srctree)) +endif + +ifneq ($(objtree),) +#$(info Determined 'objtree' to be $(objtree)) +endif + +ifneq ($(OUTPUT),) +#$(info Determined 'OUTPUT' to be $(OUTPUT)) +# Adding $(OUTPUT) as a directory to look for source files, +# because use generated output files as sources dependency +# for flex/bison parsers. +VPATH += $(OUTPUT) +export VPATH +endif + +ifeq ($(V),1) +  Q = +else +  Q = @ +endif + +BPF_DIR	= $(srctree)/tools/lib/bpf/ + +ifneq ($(OUTPUT),) +  BPF_PATH=$(OUTPUT) +else +  BPF_PATH=$(BPF_DIR) +endif + +LIBBPF = $(BPF_PATH)libbpf.a + +$(LIBBPF): FORCE +	$(Q)$(MAKE) -C $(BPF_DIR) OUTPUT=$(OUTPUT) $(OUTPUT)libbpf.a FEATURES_DUMP=$(FEATURE_DUMP_EXPORT) + +$(LIBBPF)-clean: +	$(call QUIET_CLEAN, libbpf) +	$(Q)$(MAKE) -C $(BPF_DIR) OUTPUT=$(OUTPUT) clean >/dev/null + +prefix = /usr/local +bash_compdir ?= /usr/share/bash-completion/completions + +CC = gcc + +CFLAGS += -O2 +CFLAGS += -W -Wall -Wextra -Wno-unused-parameter -Wshadow +CFLAGS += -D__EXPORTED_HEADERS__ -I$(srctree)/tools/include/uapi -I$(srctree)/tools/include -I$(srctree)/tools/lib/bpf -I$(srctree)/kernel/bpf/ +LIBS = -lelf -lbfd -lopcodes $(LIBBPF) + +include $(wildcard *.d) + +all: $(OUTPUT)bpftool + +SRCS=$(wildcard *.c) +OBJS=$(patsubst %.c,$(OUTPUT)%.o,$(SRCS)) $(OUTPUT)disasm.o + +$(OUTPUT)disasm.o: $(srctree)/kernel/bpf/disasm.c +	$(QUIET_CC)$(COMPILE.c) -MMD -o $@ $< + +$(OUTPUT)bpftool: $(OBJS) $(LIBBPF) +	$(QUIET_LINK)$(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +$(OUTPUT)%.o: %.c +	$(QUIET_CC)$(COMPILE.c) -MMD -o $@ $< + +clean: $(LIBBPF)-clean +	$(call QUIET_CLEAN, bpftool) +	$(Q)rm -rf $(OUTPUT)bpftool $(OUTPUT)*.o $(OUTPUT)*.d + +install: +	install -m 0755 -d $(prefix)/sbin +	install $(OUTPUT)bpftool $(prefix)/sbin/bpftool +	install -m 0755 -d $(bash_compdir) +	install -m 0644 bash-completion/bpftool $(bash_compdir) + +doc: +	$(Q)$(MAKE) -C Documentation/ + +doc-install: +	$(Q)$(MAKE) -C Documentation/ install + +FORCE: + +.PHONY: all clean FORCE install doc doc-install +.DEFAULT_GOAL := all diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool new file mode 100644 index 000000000000..7febee05c8e7 --- /dev/null +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -0,0 +1,354 @@ +# bpftool(8) bash completion                               -*- shell-script -*- +# +# Copyright (C) 2017 Netronome Systems, Inc. +# +# This software is dual licensed under the GNU General License +# Version 2, June 1991 as shown in the file COPYING in the top-level +# directory of this source tree or the BSD 2-Clause License provided +# below.  You have the option to license this software under the +# complete terms of either license. +# +# The BSD 2-Clause License: +# +#     Redistribution and use in source and binary forms, with or +#     without modification, are permitted provided that the following +#     conditions are met: +# +#      1. Redistributions of source code must retain the above +#         copyright notice, this list of conditions and the following +#         disclaimer. +# +#      2. Redistributions in binary form must reproduce the above +#         copyright notice, this list of conditions and the following +#         disclaimer in the documentation and/or other materials +#         provided with the distribution. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Author: Quentin Monnet <quentin.monnet@netronome.com> + +# Takes a list of words in argument; each one of them is added to COMPREPLY if +# it is not already present on the command line. Returns no value. +_bpftool_once_attr() +{ +    local w idx found +    for w in $*; do +        found=0 +        for (( idx=3; idx < ${#words[@]}-1; idx++ )); do +            if [[ $w == ${words[idx]} ]]; then +                found=1 +                break +            fi +        done +        [[ $found -eq 0 ]] && \ +            COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) ) +    done +} + +# Takes a list of words in argument; adds them all to COMPREPLY if none of them +# is already present on the command line. Returns no value. +_bpftool_one_of_list() +{ +    local w idx +    for w in $*; do +        for (( idx=3; idx < ${#words[@]}-1; idx++ )); do +            [[ $w == ${words[idx]} ]] && return 1 +        done +    done +    COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) ) +} + +_bpftool_get_map_ids() +{ +    COMPREPLY+=( $( compgen -W "$( bpftool -jp map  2>&1 | \ +        command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) ) +} + +_bpftool_get_prog_ids() +{ +    COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \ +        command sed -n 's/.*"id": \(.*\),$/\1/p' )" -- "$cur" ) ) +} + +_bpftool_get_prog_tags() +{ +    COMPREPLY+=( $( compgen -W "$( bpftool -jp prog 2>&1 | \ +        command sed -n 's/.*"tag": "\(.*\)",$/\1/p' )" -- "$cur" ) ) +} + +# For bpftool map update: retrieve type of the map to update. +_bpftool_map_update_map_type() +{ +    local keyword ref +    for (( idx=3; idx < ${#words[@]}-1; idx++ )); do +        if [[ ${words[$((idx-2))]} == "update" ]]; then +            keyword=${words[$((idx-1))]} +            ref=${words[$((idx))]} +        fi +    done +    [[ -z $ref ]] && return 0 + +    local type +    type=$(bpftool -jp map show $keyword $ref | \ +        command sed -n 's/.*"type": "\(.*\)",$/\1/p') +    printf $type +} + +_bpftool_map_update_get_id() +{ +    # Is it the map to update, or a map to insert into the map to update? +    # Search for "value" keyword. +    local idx value +    for (( idx=7; idx < ${#words[@]}-1; idx++ )); do +        if [[ ${words[idx]} == "value" ]]; then +            value=1 +            break +        fi +    done +    [[ $value -eq 0 ]] && _bpftool_get_map_ids && return 0 + +    # Id to complete is for a value. It can be either prog id or map id. This +    # depends on the type of the map to update. +    local type=$(_bpftool_map_update_map_type) +    case $type in +        array_of_maps|hash_of_maps) +            _bpftool_get_map_ids +            return 0 +            ;; +        prog_array) +            _bpftool_get_prog_ids +            return 0 +            ;; +        *) +            return 0 +            ;; +    esac +} + +_bpftool() +{ +    local cur prev words objword +    _init_completion || return + +    # Deal with simplest keywords +    case $prev in +        help|key|opcodes) +            return 0 +            ;; +        tag) +            _bpftool_get_prog_tags +            return 0 +            ;; +        file|pinned) +            _filedir +            return 0 +            ;; +        batch) +            COMPREPLY=( $( compgen -W 'file' -- "$cur" ) ) +            return 0 +            ;; +    esac + +    # Search for object and command +    local object command cmdword +    for (( cmdword=1; cmdword < ${#words[@]}-1; cmdword++ )); do +        [[ -n $object ]] && command=${words[cmdword]} && break +        [[ ${words[cmdword]} != -* ]] && object=${words[cmdword]} +    done + +    if [[ -z $object ]]; then +        case $cur in +            -*) +                local c='--version --json --pretty' +                COMPREPLY=( $( compgen -W "$c" -- "$cur" ) ) +                return 0 +                ;; +            *) +                COMPREPLY=( $( compgen -W "$( bpftool help 2>&1 | \ +                    command sed \ +                    -e '/OBJECT := /!d' \ +                    -e 's/.*{//' \ +                    -e 's/}.*//' \ +                    -e 's/|//g' )" -- "$cur" ) ) +                COMPREPLY+=( $( compgen -W 'batch help' -- "$cur" ) ) +                return 0 +                ;; +        esac +    fi + +    [[ $command == help ]] && return 0 + +    # Completion depends on object and command in use +    case $object in +        prog) +            case $prev in +                id) +                    _bpftool_get_prog_ids +                    return 0 +                    ;; +            esac + +            local PROG_TYPE='id pinned tag' +            case $command in +                show) +                    [[ $prev != "$command" ]] && return 0 +                    COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) +                    return 0 +                    ;; +                dump) +                    case $prev in +                        $command) +                            COMPREPLY+=( $( compgen -W "xlated jited" -- \ +                                "$cur" ) ) +                            return 0 +                            ;; +                        xlated|jited) +                            COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \ +                                "$cur" ) ) +                            return 0 +                            ;; +                    *) +                            _bpftool_once_attr 'file' +                            COMPREPLY+=( $( compgen -W 'opcodes' -- \ +                                "$cur" ) ) +                            return 0 +                            ;; +                    esac +                    ;; +                pin) +                    if [[ $prev == "$command" ]]; then +                        COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) +                    else +                        _filedir +                    fi +                    return 0 +                    ;; +                *) +                    [[ $prev == $object ]] && \ +                        COMPREPLY=( $( compgen -W 'dump help pin show' -- \ +                            "$cur" ) ) +                    ;; +            esac +            ;; +        map) +            local MAP_TYPE='id pinned' +            case $command in +                show|dump) +                    case $prev in +                        $command) +                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) +                            return 0 +                            ;; +                        id) +                            _bpftool_get_map_ids +                            return 0 +                            ;; +                        *) +                            return 0 +                            ;; +                    esac +                    ;; +                lookup|getnext|delete) +                    case $prev in +                        $command) +                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) +                            return 0 +                            ;; +                        id) +                            _bpftool_get_map_ids +                            return 0 +                            ;; +                        key) +                            return 0 +                            ;; +                        *) +                            _bpftool_once_attr 'key' +                            return 0 +                            ;; +                    esac +                    ;; +                update) +                    case $prev in +                        $command) +                            COMPREPLY=( $( compgen -W "$MAP_TYPE" -- "$cur" ) ) +                            return 0 +                            ;; +                        id) +                            _bpftool_map_update_get_id +                            return 0 +                            ;; +                        key) +                            return 0 +                            ;; +                        value) +                            # We can have bytes, or references to a prog or a +                            # map, depending on the type of the map to update. +                            case $(_bpftool_map_update_map_type) in +                                array_of_maps|hash_of_maps) +                                    local MAP_TYPE='id pinned' +                                    COMPREPLY+=( $( compgen -W "$MAP_TYPE" \ +                                        -- "$cur" ) ) +                                    return 0 +                                    ;; +                                prog_array) +                                    local PROG_TYPE='id pinned tag' +                                    COMPREPLY+=( $( compgen -W "$PROG_TYPE" \ +                                        -- "$cur" ) ) +                                    return 0 +                                    ;; +                                *) +                                    return 0 +                                    ;; +                            esac +                            return 0 +                            ;; +                        *) +                            _bpftool_once_attr 'key' +                            local UPDATE_FLAGS='any exist noexist' +                            for (( idx=3; idx < ${#words[@]}-1; idx++ )); do +                                if [[ ${words[idx]} == 'value' ]]; then +                                    # 'value' is present, but is not the last +                                    # word i.e. we can now have UPDATE_FLAGS. +                                    _bpftool_one_of_list "$UPDATE_FLAGS" +                                    return 0 +                                fi +                            done +                            for (( idx=3; idx < ${#words[@]}-1; idx++ )); do +                                if [[ ${words[idx]} == 'key' ]]; then +                                    # 'key' is present, but is not the last +                                    # word i.e. we can now have 'value'. +                                    _bpftool_once_attr 'value' +                                    return 0 +                                fi +                            done +                            return 0 +                            ;; +                    esac +                    ;; +                pin) +                    if [[ $prev == "$command" ]]; then +                        COMPREPLY=( $( compgen -W "$PROG_TYPE" -- "$cur" ) ) +                    else +                        _filedir +                    fi +                    return 0 +                    ;; +                *) +                    [[ $prev == $object ]] && \ +                        COMPREPLY=( $( compgen -W 'delete dump getnext help \ +                            lookup pin show update' -- "$cur" ) ) +                    ;; +            esac +            ;; +    esac +} && +complete -F _bpftool bpftool + +# ex: ts=4 sw=4 et filetype=sh diff --git a/tools/bpf/bpftool/common.c b/tools/bpf/bpftool/common.c new file mode 100644 index 000000000000..2bd3b280e6dd --- /dev/null +++ b/tools/bpf/bpftool/common.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * This software is dual licensed under the GNU General License Version 2, + * June 1991 as shown in the file COPYING in the top-level directory of this + * source tree or the BSD 2-Clause License provided below.  You have the + * option to license this software under the complete terms of either license. + * + * The BSD 2-Clause License: + * + *     Redistribution and use in source and binary forms, with or + *     without modification, are permitted provided that the following + *     conditions are met: + * + *      1. Redistributions of source code must retain the above + *         copyright notice, this list of conditions and the following + *         disclaimer. + * + *      2. Redistributions in binary form must reproduce the above + *         copyright notice, this list of conditions and the following + *         disclaimer in the documentation and/or other materials + *         provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Author: Jakub Kicinski <kubakici@wp.pl> */ + +#include <errno.h> +#include <fts.h> +#include <libgen.h> +#include <mntent.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/limits.h> +#include <linux/magic.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/vfs.h> + +#include <bpf.h> + +#include "main.h" + +void p_err(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	if (json_output) { +		jsonw_start_object(json_wtr); +		jsonw_name(json_wtr, "error"); +		jsonw_vprintf_enquote(json_wtr, fmt, ap); +		jsonw_end_object(json_wtr); +	} else { +		fprintf(stderr, "Error: "); +		vfprintf(stderr, fmt, ap); +		fprintf(stderr, "\n"); +	} +	va_end(ap); +} + +void p_info(const char *fmt, ...) +{ +	va_list ap; + +	if (json_output) +		return; + +	va_start(ap, fmt); +	vfprintf(stderr, fmt, ap); +	fprintf(stderr, "\n"); +	va_end(ap); +} + +static bool is_bpffs(char *path) +{ +	struct statfs st_fs; + +	if (statfs(path, &st_fs) < 0) +		return false; + +	return (unsigned long)st_fs.f_type == BPF_FS_MAGIC; +} + +static int mnt_bpffs(const char *target, char *buff, size_t bufflen) +{ +	bool bind_done = false; + +	while (mount("", target, "none", MS_PRIVATE | MS_REC, NULL)) { +		if (errno != EINVAL || bind_done) { +			snprintf(buff, bufflen, +				 "mount --make-private %s failed: %s", +				 target, strerror(errno)); +			return -1; +		} + +		if (mount(target, target, "none", MS_BIND, NULL)) { +			snprintf(buff, bufflen, +				 "mount --bind %s %s failed: %s", +				 target, target, strerror(errno)); +			return -1; +		} + +		bind_done = true; +	} + +	if (mount("bpf", target, "bpf", 0, "mode=0700")) { +		snprintf(buff, bufflen, "mount -t bpf bpf %s failed: %s", +			 target, strerror(errno)); +		return -1; +	} + +	return 0; +} + +int open_obj_pinned(char *path) +{ +	int fd; + +	fd = bpf_obj_get(path); +	if (fd < 0) { +		p_err("bpf obj get (%s): %s", path, +		      errno == EACCES && !is_bpffs(dirname(path)) ? +		    "directory not in bpf file system (bpffs)" : +		    strerror(errno)); +		return -1; +	} + +	return fd; +} + +int open_obj_pinned_any(char *path, enum bpf_obj_type exp_type) +{ +	enum bpf_obj_type type; +	int fd; + +	fd = open_obj_pinned(path); +	if (fd < 0) +		return -1; + +	type = get_fd_type(fd); +	if (type < 0) { +		close(fd); +		return type; +	} +	if (type != exp_type) { +		p_err("incorrect object type: %s", get_fd_type_name(type)); +		close(fd); +		return -1; +	} + +	return fd; +} + +int do_pin_any(int argc, char **argv, int (*get_fd_by_id)(__u32)) +{ +	char err_str[ERR_MAX_LEN]; +	unsigned int id; +	char *endptr; +	char *file; +	char *dir; +	int err; +	int fd; + +	if (!is_prefix(*argv, "id")) { +		p_err("expected 'id' got %s", *argv); +		return -1; +	} +	NEXT_ARG(); + +	id = strtoul(*argv, &endptr, 0); +	if (*endptr) { +		p_err("can't parse %s as ID", *argv); +		return -1; +	} +	NEXT_ARG(); + +	if (argc != 1) +		usage(); + +	fd = get_fd_by_id(id); +	if (fd < 0) { +		p_err("can't get prog by id (%u): %s", id, strerror(errno)); +		return -1; +	} + +	err = bpf_obj_pin(fd, *argv); +	if (!err) +		goto out_close; + +	file = malloc(strlen(*argv) + 1); +	strcpy(file, *argv); +	dir = dirname(file); + +	if (errno != EPERM || is_bpffs(dir)) { +		p_err("can't pin the object (%s): %s", *argv, strerror(errno)); +		goto out_free; +	} + +	/* Attempt to mount bpffs, then retry pinning. */ +	err = mnt_bpffs(dir, err_str, ERR_MAX_LEN); +	if (!err) { +		err = bpf_obj_pin(fd, *argv); +		if (err) +			p_err("can't pin the object (%s): %s", *argv, +			      strerror(errno)); +	} else { +		err_str[ERR_MAX_LEN - 1] = '\0'; +		p_err("can't mount BPF file system to pin the object (%s): %s", +		      *argv, err_str); +	} + +out_free: +	free(file); +out_close: +	close(fd); +	return err; +} + +const char *get_fd_type_name(enum bpf_obj_type type) +{ +	static const char * const names[] = { +		[BPF_OBJ_UNKNOWN]	= "unknown", +		[BPF_OBJ_PROG]		= "prog", +		[BPF_OBJ_MAP]		= "map", +	}; + +	if (type < 0 || type >= ARRAY_SIZE(names) || !names[type]) +		return names[BPF_OBJ_UNKNOWN]; + +	return names[type]; +} + +int get_fd_type(int fd) +{ +	char path[PATH_MAX]; +	char buf[512]; +	ssize_t n; + +	snprintf(path, sizeof(path), "/proc/%d/fd/%d", getpid(), fd); + +	n = readlink(path, buf, sizeof(buf)); +	if (n < 0) { +		p_err("can't read link type: %s", strerror(errno)); +		return -1; +	} +	if (n == sizeof(path)) { +		p_err("can't read link type: path too long!"); +		return -1; +	} + +	if (strstr(buf, "bpf-map")) +		return BPF_OBJ_MAP; +	else if (strstr(buf, "bpf-prog")) +		return BPF_OBJ_PROG; + +	return BPF_OBJ_UNKNOWN; +} + +char *get_fdinfo(int fd, const char *key) +{ +	char path[PATH_MAX]; +	char *line = NULL; +	size_t line_n = 0; +	ssize_t n; +	FILE *fdi; + +	snprintf(path, sizeof(path), "/proc/%d/fdinfo/%d", getpid(), fd); + +	fdi = fopen(path, "r"); +	if (!fdi) { +		p_err("can't open fdinfo: %s", strerror(errno)); +		return NULL; +	} + +	while ((n = getline(&line, &line_n, fdi))) { +		char *value; +		int len; + +		if (!strstr(line, key)) +			continue; + +		fclose(fdi); + +		value = strchr(line, '\t'); +		if (!value || !value[1]) { +			p_err("malformed fdinfo!?"); +			free(line); +			return NULL; +		} +		value++; + +		len = strlen(value); +		memmove(line, value, len); +		line[len - 1] = '\0'; + +		return line; +	} + +	p_err("key '%s' not found in fdinfo", key); +	free(line); +	fclose(fdi); +	return NULL; +} + +void print_hex_data_json(uint8_t *data, size_t len) +{ +	unsigned int i; + +	jsonw_start_array(json_wtr); +	for (i = 0; i < len; i++) +		jsonw_printf(json_wtr, "\"0x%02hhx\"", data[i]); +	jsonw_end_array(json_wtr); +} + +int build_pinned_obj_table(struct pinned_obj_table *tab, +			   enum bpf_obj_type type) +{ +	struct bpf_prog_info pinned_info = {}; +	struct pinned_obj *obj_node = NULL; +	__u32 len = sizeof(pinned_info); +	struct mntent *mntent = NULL; +	enum bpf_obj_type objtype; +	FILE *mntfile = NULL; +	FTSENT *ftse = NULL; +	FTS *fts = NULL; +	int fd, err; + +	mntfile = setmntent("/proc/mounts", "r"); +	if (!mntfile) +		return -1; + +	while ((mntent = getmntent(mntfile))) { +		char *path[] = { mntent->mnt_dir, NULL }; + +		if (strncmp(mntent->mnt_type, "bpf", 3) != 0) +			continue; + +		fts = fts_open(path, 0, NULL); +		if (!fts) +			continue; + +		while ((ftse = fts_read(fts))) { +			if (!(ftse->fts_info & FTS_F)) +				continue; +			fd = open_obj_pinned(ftse->fts_path); +			if (fd < 0) +				continue; + +			objtype = get_fd_type(fd); +			if (objtype != type) { +				close(fd); +				continue; +			} +			memset(&pinned_info, 0, sizeof(pinned_info)); +			err = bpf_obj_get_info_by_fd(fd, &pinned_info, &len); +			if (err) { +				close(fd); +				continue; +			} + +			obj_node = malloc(sizeof(*obj_node)); +			if (!obj_node) { +				close(fd); +				fts_close(fts); +				fclose(mntfile); +				return -1; +			} + +			memset(obj_node, 0, sizeof(*obj_node)); +			obj_node->id = pinned_info.id; +			obj_node->path = strdup(ftse->fts_path); +			hash_add(tab->table, &obj_node->hash, obj_node->id); + +			close(fd); +		} +		fts_close(fts); +	} +	fclose(mntfile); +	return 0; +} + +void delete_pinned_obj_table(struct pinned_obj_table *tab) +{ +	struct pinned_obj *obj; +	struct hlist_node *tmp; +	unsigned int bkt; + +	hash_for_each_safe(tab->table, bkt, tmp, obj, hash) { +		hash_del(&obj->hash); +		free(obj->path); +		free(obj); +	} +} diff --git a/tools/bpf/bpftool/jit_disasm.c b/tools/bpf/bpftool/jit_disasm.c new file mode 100644 index 000000000000..1551d3918d4c --- /dev/null +++ b/tools/bpf/bpftool/jit_disasm.c @@ -0,0 +1,162 @@ +/* + * Based on: + * + * Minimal BPF JIT image disassembler + * + * Disassembles BPF JIT compiler emitted opcodes back to asm insn's for + * debugging or verification purposes. + * + * Copyright 2013 Daniel Borkmann <daniel@iogearbox.net> + * Licensed under the GNU General Public License, version 2.0 (GPLv2) + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <bfd.h> +#include <dis-asm.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> + +#include "json_writer.h" +#include "main.h" + +static void get_exec_path(char *tpath, size_t size) +{ +	ssize_t len; +	char *path; + +	snprintf(tpath, size, "/proc/%d/exe", (int) getpid()); +	tpath[size - 1] = 0; + +	path = strdup(tpath); +	assert(path); + +	len = readlink(path, tpath, size - 1); +	assert(len > 0); +	tpath[len] = 0; + +	free(path); +} + +static int oper_count; +static int fprintf_json(void *out, const char *fmt, ...) +{ +	va_list ap; +	char *s; + +	va_start(ap, fmt); +	if (!oper_count) { +		int i; + +		s = va_arg(ap, char *); + +		/* Strip trailing spaces */ +		i = strlen(s) - 1; +		while (s[i] == ' ') +			s[i--] = '\0'; + +		jsonw_string_field(json_wtr, "operation", s); +		jsonw_name(json_wtr, "operands"); +		jsonw_start_array(json_wtr); +		oper_count++; +	} else if (!strcmp(fmt, ",")) { +		   /* Skip */ +	} else { +		s = va_arg(ap, char *); +		jsonw_string(json_wtr, s); +		oper_count++; +	} +	va_end(ap); +	return 0; +} + +void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes) +{ +	disassembler_ftype disassemble; +	struct disassemble_info info; +	int count, i, pc = 0; +	char tpath[PATH_MAX]; +	bfd *bfdf; + +	if (!len) +		return; + +	memset(tpath, 0, sizeof(tpath)); +	get_exec_path(tpath, sizeof(tpath)); + +	bfdf = bfd_openr(tpath, NULL); +	assert(bfdf); +	assert(bfd_check_format(bfdf, bfd_object)); + +	if (json_output) +		init_disassemble_info(&info, stdout, +				      (fprintf_ftype) fprintf_json); +	else +		init_disassemble_info(&info, stdout, +				      (fprintf_ftype) fprintf); +	info.arch = bfd_get_arch(bfdf); +	info.mach = bfd_get_mach(bfdf); +	info.buffer = image; +	info.buffer_length = len; + +	disassemble_init_for_target(&info); + +	disassemble = disassembler(bfdf); +	assert(disassemble); + +	if (json_output) +		jsonw_start_array(json_wtr); +	do { +		if (json_output) { +			jsonw_start_object(json_wtr); +			oper_count = 0; +			jsonw_name(json_wtr, "pc"); +			jsonw_printf(json_wtr, "\"0x%x\"", pc); +		} else { +			printf("%4x:\t", pc); +		} + +		count = disassemble(pc, &info); +		if (json_output) { +			/* Operand array, was started in fprintf_json. Before +			 * that, make sure we have a _null_ value if no operand +			 * other than operation code was present. +			 */ +			if (oper_count == 1) +				jsonw_null(json_wtr); +			jsonw_end_array(json_wtr); +		} + +		if (opcodes) { +			if (json_output) { +				jsonw_name(json_wtr, "opcodes"); +				jsonw_start_array(json_wtr); +				for (i = 0; i < count; ++i) +					jsonw_printf(json_wtr, "\"0x%02hhx\"", +						     (uint8_t)image[pc + i]); +				jsonw_end_array(json_wtr); +			} else { +				printf("\n\t"); +				for (i = 0; i < count; ++i) +					printf("%02x ", +					       (uint8_t)image[pc + i]); +			} +		} +		if (json_output) +			jsonw_end_object(json_wtr); +		else +			printf("\n"); + +		pc += count; +	} while (count > 0 && pc < len); +	if (json_output) +		jsonw_end_array(json_wtr); + +	bfd_close(bfdf); +} diff --git a/tools/bpf/bpftool/json_writer.c b/tools/bpf/bpftool/json_writer.c new file mode 100644 index 000000000000..c6eef76322ae --- /dev/null +++ b/tools/bpf/bpftool/json_writer.c @@ -0,0 +1,356 @@ +/* + * Simple streaming JSON writer + * + * This takes care of the annoying bits of JSON syntax like the commas + * after elements + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors:	Stephen Hemminger <stephen@networkplumber.org> + */ + +#include <stdio.h> +#include <stdbool.h> +#include <stdarg.h> +#include <assert.h> +#include <malloc.h> +#include <inttypes.h> +#include <stdint.h> + +#include "json_writer.h" + +struct json_writer { +	FILE		*out;	/* output file */ +	unsigned	depth;  /* nesting */ +	bool		pretty; /* optional whitepace */ +	char		sep;	/* either nul or comma */ +}; + +/* indentation for pretty print */ +static void jsonw_indent(json_writer_t *self) +{ +	unsigned i; +	for (i = 0; i < self->depth; ++i) +		fputs("    ", self->out); +} + +/* end current line and indent if pretty printing */ +static void jsonw_eol(json_writer_t *self) +{ +	if (!self->pretty) +		return; + +	putc('\n', self->out); +	jsonw_indent(self); +} + +/* If current object is not empty print a comma */ +static void jsonw_eor(json_writer_t *self) +{ +	if (self->sep != '\0') +		putc(self->sep, self->out); +	self->sep = ','; +} + + +/* Output JSON encoded string */ +/* Handles C escapes, does not do Unicode */ +static void jsonw_puts(json_writer_t *self, const char *str) +{ +	putc('"', self->out); +	for (; *str; ++str) +		switch (*str) { +		case '\t': +			fputs("\\t", self->out); +			break; +		case '\n': +			fputs("\\n", self->out); +			break; +		case '\r': +			fputs("\\r", self->out); +			break; +		case '\f': +			fputs("\\f", self->out); +			break; +		case '\b': +			fputs("\\b", self->out); +			break; +		case '\\': +			fputs("\\n", self->out); +			break; +		case '"': +			fputs("\\\"", self->out); +			break; +		case '\'': +			fputs("\\\'", self->out); +			break; +		default: +			putc(*str, self->out); +		} +	putc('"', self->out); +} + +/* Create a new JSON stream */ +json_writer_t *jsonw_new(FILE *f) +{ +	json_writer_t *self = malloc(sizeof(*self)); +	if (self) { +		self->out = f; +		self->depth = 0; +		self->pretty = false; +		self->sep = '\0'; +	} +	return self; +} + +/* End output to JSON stream */ +void jsonw_destroy(json_writer_t **self_p) +{ +	json_writer_t *self = *self_p; + +	assert(self->depth == 0); +	fputs("\n", self->out); +	fflush(self->out); +	free(self); +	*self_p = NULL; +} + +void jsonw_pretty(json_writer_t *self, bool on) +{ +	self->pretty = on; +} + +/* Basic blocks */ +static void jsonw_begin(json_writer_t *self, int c) +{ +	jsonw_eor(self); +	putc(c, self->out); +	++self->depth; +	self->sep = '\0'; +} + +static void jsonw_end(json_writer_t *self, int c) +{ +	assert(self->depth > 0); + +	--self->depth; +	if (self->sep != '\0') +		jsonw_eol(self); +	putc(c, self->out); +	self->sep = ','; +} + + +/* Add a JSON property name */ +void jsonw_name(json_writer_t *self, const char *name) +{ +	jsonw_eor(self); +	jsonw_eol(self); +	self->sep = '\0'; +	jsonw_puts(self, name); +	putc(':', self->out); +	if (self->pretty) +		putc(' ', self->out); +} + +void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap) +{ +	jsonw_eor(self); +	putc('"', self->out); +	vfprintf(self->out, fmt, ap); +	putc('"', self->out); +} + +void jsonw_printf(json_writer_t *self, const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	jsonw_eor(self); +	vfprintf(self->out, fmt, ap); +	va_end(ap); +} + +/* Collections */ +void jsonw_start_object(json_writer_t *self) +{ +	jsonw_begin(self, '{'); +} + +void jsonw_end_object(json_writer_t *self) +{ +	jsonw_end(self, '}'); +} + +void jsonw_start_array(json_writer_t *self) +{ +	jsonw_begin(self, '['); +} + +void jsonw_end_array(json_writer_t *self) +{ +	jsonw_end(self, ']'); +} + +/* JSON value types */ +void jsonw_string(json_writer_t *self, const char *value) +{ +	jsonw_eor(self); +	jsonw_puts(self, value); +} + +void jsonw_bool(json_writer_t *self, bool val) +{ +	jsonw_printf(self, "%s", val ? "true" : "false"); +} + +void jsonw_null(json_writer_t *self) +{ +	jsonw_printf(self, "null"); +} + +void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num) +{ +	jsonw_printf(self, fmt, num); +} + +#ifdef notused +void jsonw_float(json_writer_t *self, double num) +{ +	jsonw_printf(self, "%g", num); +} +#endif + +void jsonw_hu(json_writer_t *self, unsigned short num) +{ +	jsonw_printf(self, "%hu", num); +} + +void jsonw_uint(json_writer_t *self, uint64_t num) +{ +	jsonw_printf(self, "%"PRIu64, num); +} + +void jsonw_lluint(json_writer_t *self, unsigned long long int num) +{ +	jsonw_printf(self, "%llu", num); +} + +void jsonw_int(json_writer_t *self, int64_t num) +{ +	jsonw_printf(self, "%"PRId64, num); +} + +/* Basic name/value objects */ +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val) +{ +	jsonw_name(self, prop); +	jsonw_string(self, val); +} + +void jsonw_bool_field(json_writer_t *self, const char *prop, bool val) +{ +	jsonw_name(self, prop); +	jsonw_bool(self, val); +} + +#ifdef notused +void jsonw_float_field(json_writer_t *self, const char *prop, double val) +{ +	jsonw_name(self, prop); +	jsonw_float(self, val); +} +#endif + +void jsonw_float_field_fmt(json_writer_t *self, +			   const char *prop, +			   const char *fmt, +			   double val) +{ +	jsonw_name(self, prop); +	jsonw_float_fmt(self, fmt, val); +} + +void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num) +{ +	jsonw_name(self, prop); +	jsonw_uint(self, num); +} + +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num) +{ +	jsonw_name(self, prop); +	jsonw_hu(self, num); +} + +void jsonw_lluint_field(json_writer_t *self, +			const char *prop, +			unsigned long long int num) +{ +	jsonw_name(self, prop); +	jsonw_lluint(self, num); +} + +void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num) +{ +	jsonw_name(self, prop); +	jsonw_int(self, num); +} + +void jsonw_null_field(json_writer_t *self, const char *prop) +{ +	jsonw_name(self, prop); +	jsonw_null(self); +} + +#ifdef TEST +int main(int argc, char **argv) +{ +	json_writer_t *wr = jsonw_new(stdout); + +	jsonw_start_object(wr); +	jsonw_pretty(wr, true); +	jsonw_name(wr, "Vyatta"); +	jsonw_start_object(wr); +	jsonw_string_field(wr, "url", "http://vyatta.com"); +	jsonw_uint_field(wr, "downloads", 2000000ul); +	jsonw_float_field(wr, "stock", 8.16); + +	jsonw_name(wr, "ARGV"); +	jsonw_start_array(wr); +	while (--argc) +		jsonw_string(wr, *++argv); +	jsonw_end_array(wr); + +	jsonw_name(wr, "empty"); +	jsonw_start_array(wr); +	jsonw_end_array(wr); + +	jsonw_name(wr, "NIL"); +	jsonw_start_object(wr); +	jsonw_end_object(wr); + +	jsonw_null_field(wr, "my_null"); + +	jsonw_name(wr, "special chars"); +	jsonw_start_array(wr); +	jsonw_string_field(wr, "slash", "/"); +	jsonw_string_field(wr, "newline", "\n"); +	jsonw_string_field(wr, "tab", "\t"); +	jsonw_string_field(wr, "ff", "\f"); +	jsonw_string_field(wr, "quote", "\""); +	jsonw_string_field(wr, "tick", "\'"); +	jsonw_string_field(wr, "backslash", "\\"); +	jsonw_end_array(wr); + +	jsonw_end_object(wr); + +	jsonw_end_object(wr); +	jsonw_destroy(&wr); +	return 0; +} + +#endif diff --git a/tools/bpf/bpftool/json_writer.h b/tools/bpf/bpftool/json_writer.h new file mode 100644 index 000000000000..0fa2fb1b6351 --- /dev/null +++ b/tools/bpf/bpftool/json_writer.h @@ -0,0 +1,72 @@ +/* + * Simple streaming JSON writer + * + * This takes care of the annoying bits of JSON syntax like the commas + * after elements + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors:	Stephen Hemminger <stephen@networkplumber.org> + */ + +#ifndef _JSON_WRITER_H_ +#define _JSON_WRITER_H_ + +#include <stdbool.h> +#include <stdint.h> +#include <stdarg.h> + +/* Opaque class structure */ +typedef struct json_writer json_writer_t; + +/* Create a new JSON stream */ +json_writer_t *jsonw_new(FILE *f); +/* End output to JSON stream */ +void jsonw_destroy(json_writer_t **self_p); + +/* Cause output to have pretty whitespace */ +void jsonw_pretty(json_writer_t *self, bool on); + +/* Add property name */ +void jsonw_name(json_writer_t *self, const char *name); + +/* Add value  */ +void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap); +void jsonw_printf(json_writer_t *self, const char *fmt, ...); +void jsonw_string(json_writer_t *self, const char *value); +void jsonw_bool(json_writer_t *self, bool value); +void jsonw_float(json_writer_t *self, double number); +void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num); +void jsonw_uint(json_writer_t *self, uint64_t number); +void jsonw_hu(json_writer_t *self, unsigned short number); +void jsonw_int(json_writer_t *self, int64_t number); +void jsonw_null(json_writer_t *self); +void jsonw_lluint(json_writer_t *self, unsigned long long int num); + +/* Useful Combinations of name and value */ +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val); +void jsonw_bool_field(json_writer_t *self, const char *prop, bool value); +void jsonw_float_field(json_writer_t *self, const char *prop, double num); +void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num); +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num); +void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num); +void jsonw_null_field(json_writer_t *self, const char *prop); +void jsonw_lluint_field(json_writer_t *self, const char *prop, +			unsigned long long int num); +void jsonw_float_field_fmt(json_writer_t *self, const char *prop, +			   const char *fmt, double val); + +/* Collections */ +void jsonw_start_object(json_writer_t *self); +void jsonw_end_object(json_writer_t *self); + +void jsonw_start_array(json_writer_t *self); +void jsonw_end_array(json_writer_t *self); + +/* Override default exception handling */ +typedef void (jsonw_err_handler_fn)(const char *); + +#endif /* _JSON_WRITER_H_ */ diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c new file mode 100644 index 000000000000..d294bc8168be --- /dev/null +++ b/tools/bpf/bpftool/main.c @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * This software is dual licensed under the GNU General License Version 2, + * June 1991 as shown in the file COPYING in the top-level directory of this + * source tree or the BSD 2-Clause License provided below.  You have the + * option to license this software under the complete terms of either license. + * + * The BSD 2-Clause License: + * + *     Redistribution and use in source and binary forms, with or + *     without modification, are permitted provided that the following + *     conditions are met: + * + *      1. Redistributions of source code must retain the above + *         copyright notice, this list of conditions and the following + *         disclaimer. + * + *      2. Redistributions in binary form must reproduce the above + *         copyright notice, this list of conditions and the following + *         disclaimer in the documentation and/or other materials + *         provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Author: Jakub Kicinski <kubakici@wp.pl> */ + +#include <bfd.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#include <linux/bpf.h> +#include <linux/version.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <bpf.h> + +#include "main.h" + +const char *bin_name; +static int last_argc; +static char **last_argv; +static int (*last_do_help)(int argc, char **argv); +json_writer_t *json_wtr; +bool pretty_output; +bool json_output; +bool show_pinned; +struct pinned_obj_table prog_table; +struct pinned_obj_table map_table; + +static void __noreturn clean_and_exit(int i) +{ +	if (json_output) +		jsonw_destroy(&json_wtr); + +	exit(i); +} + +void usage(void) +{ +	last_do_help(last_argc - 1, last_argv + 1); + +	clean_and_exit(-1); +} + +static int do_help(int argc, char **argv) +{ +	if (json_output) { +		jsonw_null(json_wtr); +		return 0; +	} + +	fprintf(stderr, +		"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n" +		"       %s batch file FILE\n" +		"       %s version\n" +		"\n" +		"       OBJECT := { prog | map }\n" +		"       " HELP_SPEC_OPTIONS "\n" +		"", +		bin_name, bin_name, bin_name); + +	return 0; +} + +static int do_version(int argc, char **argv) +{ +	unsigned int version[3]; + +	version[0] = LINUX_VERSION_CODE >> 16; +	version[1] = LINUX_VERSION_CODE >> 8 & 0xf; +	version[2] = LINUX_VERSION_CODE & 0xf; + +	if (json_output) { +		jsonw_start_object(json_wtr); +		jsonw_name(json_wtr, "version"); +		jsonw_printf(json_wtr, "\"%u.%u.%u\"", +			     version[0], version[1], version[2]); +		jsonw_end_object(json_wtr); +	} else { +		printf("%s v%u.%u.%u\n", bin_name, +		       version[0], version[1], version[2]); +	} +	return 0; +} + +int cmd_select(const struct cmd *cmds, int argc, char **argv, +	       int (*help)(int argc, char **argv)) +{ +	unsigned int i; + +	last_argc = argc; +	last_argv = argv; +	last_do_help = help; + +	if (argc < 1 && cmds[0].func) +		return cmds[0].func(argc, argv); + +	for (i = 0; cmds[i].func; i++) +		if (is_prefix(*argv, cmds[i].cmd)) +			return cmds[i].func(argc - 1, argv + 1); + +	help(argc - 1, argv + 1); + +	return -1; +} + +bool is_prefix(const char *pfx, const char *str) +{ +	if (!pfx) +		return false; +	if (strlen(str) < strlen(pfx)) +		return false; + +	return !memcmp(str, pfx, strlen(pfx)); +} + +void fprint_hex(FILE *f, void *arg, unsigned int n, const char *sep) +{ +	unsigned char *data = arg; +	unsigned int i; + +	for (i = 0; i < n; i++) { +		const char *pfx = ""; + +		if (!i) +			/* nothing */; +		else if (!(i % 16)) +			fprintf(f, "\n"); +		else if (!(i % 8)) +			fprintf(f, "  "); +		else +			pfx = sep; + +		fprintf(f, "%s%02hhx", i ? pfx : "", data[i]); +	} +} + +static int do_batch(int argc, char **argv); + +static const struct cmd cmds[] = { +	{ "help",	do_help }, +	{ "batch",	do_batch }, +	{ "prog",	do_prog }, +	{ "map",	do_map }, +	{ "version",	do_version }, +	{ 0 } +}; + +static int do_batch(int argc, char **argv) +{ +	unsigned int lines = 0; +	char *n_argv[4096]; +	char buf[65536]; +	int n_argc; +	FILE *fp; +	int err; +	int i; + +	if (argc < 2) { +		p_err("too few parameters for batch"); +		return -1; +	} else if (!is_prefix(*argv, "file")) { +		p_err("expected 'file', got: %s", *argv); +		return -1; +	} else if (argc > 2) { +		p_err("too many parameters for batch"); +		return -1; +	} +	NEXT_ARG(); + +	fp = fopen(*argv, "r"); +	if (!fp) { +		p_err("Can't open file (%s): %s", *argv, strerror(errno)); +		return -1; +	} + +	if (json_output) +		jsonw_start_array(json_wtr); +	while (fgets(buf, sizeof(buf), fp)) { +		if (strlen(buf) == sizeof(buf) - 1) { +			errno = E2BIG; +			break; +		} + +		n_argc = 0; +		n_argv[n_argc] = strtok(buf, " \t\n"); + +		while (n_argv[n_argc]) { +			n_argc++; +			if (n_argc == ARRAY_SIZE(n_argv)) { +				p_err("line %d has too many arguments, skip", +				      lines); +				n_argc = 0; +				break; +			} +			n_argv[n_argc] = strtok(NULL, " \t\n"); +		} + +		if (!n_argc) +			continue; + +		if (json_output) { +			jsonw_start_object(json_wtr); +			jsonw_name(json_wtr, "command"); +			jsonw_start_array(json_wtr); +			for (i = 0; i < n_argc; i++) +				jsonw_string(json_wtr, n_argv[i]); +			jsonw_end_array(json_wtr); +			jsonw_name(json_wtr, "output"); +		} + +		err = cmd_select(cmds, n_argc, n_argv, do_help); + +		if (json_output) +			jsonw_end_object(json_wtr); + +		if (err) +			goto err_close; + +		lines++; +	} + +	if (errno && errno != ENOENT) { +		perror("reading batch file failed"); +		err = -1; +	} else { +		p_info("processed %d lines", lines); +		err = 0; +	} +err_close: +	fclose(fp); + +	if (json_output) +		jsonw_end_array(json_wtr); + +	return err; +} + +int main(int argc, char **argv) +{ +	static const struct option options[] = { +		{ "json",	no_argument,	NULL,	'j' }, +		{ "help",	no_argument,	NULL,	'h' }, +		{ "pretty",	no_argument,	NULL,	'p' }, +		{ "version",	no_argument,	NULL,	'V' }, +		{ "bpffs",	no_argument,	NULL,	'f' }, +		{ 0 } +	}; +	int opt, ret; + +	last_do_help = do_help; +	pretty_output = false; +	json_output = false; +	show_pinned = false; +	bin_name = argv[0]; + +	hash_init(prog_table.table); +	hash_init(map_table.table); + +	opterr = 0; +	while ((opt = getopt_long(argc, argv, "Vhpjf", +				  options, NULL)) >= 0) { +		switch (opt) { +		case 'V': +			return do_version(argc, argv); +		case 'h': +			return do_help(argc, argv); +		case 'p': +			pretty_output = true; +			/* fall through */ +		case 'j': +			if (!json_output) { +				json_wtr = jsonw_new(stdout); +				if (!json_wtr) { +					p_err("failed to create JSON writer"); +					return -1; +				} +				json_output = true; +			} +			jsonw_pretty(json_wtr, pretty_output); +			break; +		case 'f': +			show_pinned = true; +			break; +		default: +			p_err("unrecognized option '%s'", argv[optind - 1]); +			if (json_output) +				clean_and_exit(-1); +			else +				usage(); +		} +	} + +	argc -= optind; +	argv += optind; +	if (argc < 0) +		usage(); + +	bfd_init(); + +	ret = cmd_select(cmds, argc, argv, do_help); + +	if (json_output) +		jsonw_destroy(&json_wtr); + +	if (show_pinned) { +		delete_pinned_obj_table(&prog_table); +		delete_pinned_obj_table(&map_table); +	} + +	return ret; +} diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h new file mode 100644 index 000000000000..bff330b49791 --- /dev/null +++ b/tools/bpf/bpftool/main.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * This software is dual licensed under the GNU General License Version 2, + * June 1991 as shown in the file COPYING in the top-level directory of this + * source tree or the BSD 2-Clause License provided below.  You have the + * option to license this software under the complete terms of either license. + * + * The BSD 2-Clause License: + * + *     Redistribution and use in source and binary forms, with or + *     without modification, are permitted provided that the following + *     conditions are met: + * + *      1. Redistributions of source code must retain the above + *         copyright notice, this list of conditions and the following + *         disclaimer. + * + *      2. Redistributions in binary form must reproduce the above + *         copyright notice, this list of conditions and the following + *         disclaimer in the documentation and/or other materials + *         provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Author: Jakub Kicinski <kubakici@wp.pl> */ + +#ifndef __BPF_TOOL_H +#define __BPF_TOOL_H + +/* BFD and kernel.h both define GCC_VERSION, differently */ +#undef GCC_VERSION +#include <stdbool.h> +#include <stdio.h> +#include <linux/bpf.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/hashtable.h> + +#include "json_writer.h" + +#define ptr_to_u64(ptr)	((__u64)(unsigned long)(ptr)) + +#define NEXT_ARG()	({ argc--; argv++; if (argc < 0) usage(); }) +#define NEXT_ARGP()	({ (*argc)--; (*argv)++; if (*argc < 0) usage(); }) +#define BAD_ARG()	({ p_err("what is '%s'?", *argv); -1; }) + +#define ERR_MAX_LEN	1024 + +#define BPF_TAG_FMT	"%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" + +#define HELP_SPEC_PROGRAM						\ +	"PROG := { id PROG_ID | pinned FILE | tag PROG_TAG }" +#define HELP_SPEC_OPTIONS						\ +	"OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} }" + +enum bpf_obj_type { +	BPF_OBJ_UNKNOWN, +	BPF_OBJ_PROG, +	BPF_OBJ_MAP, +}; + +extern const char *bin_name; + +extern json_writer_t *json_wtr; +extern bool json_output; +extern bool show_pinned; +extern struct pinned_obj_table prog_table; +extern struct pinned_obj_table map_table; + +void p_err(const char *fmt, ...); +void p_info(const char *fmt, ...); + +bool is_prefix(const char *pfx, const char *str); +void fprint_hex(FILE *f, void *arg, unsigned int n, const char *sep); +void usage(void) __noreturn; + +struct pinned_obj_table { +	DECLARE_HASHTABLE(table, 16); +}; + +struct pinned_obj { +	__u32 id; +	char *path; +	struct hlist_node hash; +}; + +int build_pinned_obj_table(struct pinned_obj_table *table, +			   enum bpf_obj_type type); +void delete_pinned_obj_table(struct pinned_obj_table *tab); + +struct cmd { +	const char *cmd; +	int (*func)(int argc, char **argv); +}; + +int cmd_select(const struct cmd *cmds, int argc, char **argv, +	       int (*help)(int argc, char **argv)); + +int get_fd_type(int fd); +const char *get_fd_type_name(enum bpf_obj_type type); +char *get_fdinfo(int fd, const char *key); +int open_obj_pinned(char *path); +int open_obj_pinned_any(char *path, enum bpf_obj_type exp_type); +int do_pin_any(int argc, char **argv, int (*get_fd_by_id)(__u32)); + +int do_prog(int argc, char **arg); +int do_map(int argc, char **arg); + +int prog_parse_fd(int *argc, char ***argv); + +void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes); +void print_hex_data_json(uint8_t *data, size_t len); + +#endif diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c new file mode 100644 index 000000000000..e2450c8e88e6 --- /dev/null +++ b/tools/bpf/bpftool/map.c @@ -0,0 +1,899 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * This software is dual licensed under the GNU General License Version 2, + * June 1991 as shown in the file COPYING in the top-level directory of this + * source tree or the BSD 2-Clause License provided below.  You have the + * option to license this software under the complete terms of either license. + * + * The BSD 2-Clause License: + * + *     Redistribution and use in source and binary forms, with or + *     without modification, are permitted provided that the following + *     conditions are met: + * + *      1. Redistributions of source code must retain the above + *         copyright notice, this list of conditions and the following + *         disclaimer. + * + *      2. Redistributions in binary form must reproduce the above + *         copyright notice, this list of conditions and the following + *         disclaimer in the documentation and/or other materials + *         provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Author: Jakub Kicinski <kubakici@wp.pl> */ + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <bpf.h> + +#include "main.h" + +static const char * const map_type_name[] = { +	[BPF_MAP_TYPE_UNSPEC]		= "unspec", +	[BPF_MAP_TYPE_HASH]		= "hash", +	[BPF_MAP_TYPE_ARRAY]		= "array", +	[BPF_MAP_TYPE_PROG_ARRAY]	= "prog_array", +	[BPF_MAP_TYPE_PERF_EVENT_ARRAY]	= "perf_event_array", +	[BPF_MAP_TYPE_PERCPU_HASH]	= "percpu_hash", +	[BPF_MAP_TYPE_PERCPU_ARRAY]	= "percpu_array", +	[BPF_MAP_TYPE_STACK_TRACE]	= "stack_trace", +	[BPF_MAP_TYPE_CGROUP_ARRAY]	= "cgroup_array", +	[BPF_MAP_TYPE_LRU_HASH]		= "lru_hash", +	[BPF_MAP_TYPE_LRU_PERCPU_HASH]	= "lru_percpu_hash", +	[BPF_MAP_TYPE_LPM_TRIE]		= "lpm_trie", +	[BPF_MAP_TYPE_ARRAY_OF_MAPS]	= "array_of_maps", +	[BPF_MAP_TYPE_HASH_OF_MAPS]	= "hash_of_maps", +	[BPF_MAP_TYPE_DEVMAP]		= "devmap", +	[BPF_MAP_TYPE_SOCKMAP]		= "sockmap", +}; + +static unsigned int get_possible_cpus(void) +{ +	static unsigned int result; +	char buf[128]; +	long int n; +	char *ptr; +	int fd; + +	if (result) +		return result; + +	fd = open("/sys/devices/system/cpu/possible", O_RDONLY); +	if (fd < 0) { +		p_err("can't open sysfs possible cpus"); +		exit(-1); +	} + +	n = read(fd, buf, sizeof(buf)); +	if (n < 2) { +		p_err("can't read sysfs possible cpus"); +		exit(-1); +	} +	close(fd); + +	if (n == sizeof(buf)) { +		p_err("read sysfs possible cpus overflow"); +		exit(-1); +	} + +	ptr = buf; +	n = 0; +	while (*ptr && *ptr != '\n') { +		unsigned int a, b; + +		if (sscanf(ptr, "%u-%u", &a, &b) == 2) { +			n += b - a + 1; + +			ptr = strchr(ptr, '-') + 1; +		} else if (sscanf(ptr, "%u", &a) == 1) { +			n++; +		} else { +			assert(0); +		} + +		while (isdigit(*ptr)) +			ptr++; +		if (*ptr == ',') +			ptr++; +	} + +	result = n; + +	return result; +} + +static bool map_is_per_cpu(__u32 type) +{ +	return type == BPF_MAP_TYPE_PERCPU_HASH || +	       type == BPF_MAP_TYPE_PERCPU_ARRAY || +	       type == BPF_MAP_TYPE_LRU_PERCPU_HASH; +} + +static bool map_is_map_of_maps(__u32 type) +{ +	return type == BPF_MAP_TYPE_ARRAY_OF_MAPS || +	       type == BPF_MAP_TYPE_HASH_OF_MAPS; +} + +static bool map_is_map_of_progs(__u32 type) +{ +	return type == BPF_MAP_TYPE_PROG_ARRAY; +} + +static void *alloc_value(struct bpf_map_info *info) +{ +	if (map_is_per_cpu(info->type)) +		return malloc(info->value_size * get_possible_cpus()); +	else +		return malloc(info->value_size); +} + +static int map_parse_fd(int *argc, char ***argv) +{ +	int fd; + +	if (is_prefix(**argv, "id")) { +		unsigned int id; +		char *endptr; + +		NEXT_ARGP(); + +		id = strtoul(**argv, &endptr, 0); +		if (*endptr) { +			p_err("can't parse %s as ID", **argv); +			return -1; +		} +		NEXT_ARGP(); + +		fd = bpf_map_get_fd_by_id(id); +		if (fd < 0) +			p_err("get map by id (%u): %s", id, strerror(errno)); +		return fd; +	} else if (is_prefix(**argv, "pinned")) { +		char *path; + +		NEXT_ARGP(); + +		path = **argv; +		NEXT_ARGP(); + +		return open_obj_pinned_any(path, BPF_OBJ_MAP); +	} + +	p_err("expected 'id' or 'pinned', got: '%s'?", **argv); +	return -1; +} + +static int +map_parse_fd_and_info(int *argc, char ***argv, void *info, __u32 *info_len) +{ +	int err; +	int fd; + +	fd = map_parse_fd(argc, argv); +	if (fd < 0) +		return -1; + +	err = bpf_obj_get_info_by_fd(fd, info, info_len); +	if (err) { +		p_err("can't get map info: %s", strerror(errno)); +		close(fd); +		return err; +	} + +	return fd; +} + +static void print_entry_json(struct bpf_map_info *info, unsigned char *key, +			     unsigned char *value) +{ +	jsonw_start_object(json_wtr); + +	if (!map_is_per_cpu(info->type)) { +		jsonw_name(json_wtr, "key"); +		print_hex_data_json(key, info->key_size); +		jsonw_name(json_wtr, "value"); +		print_hex_data_json(value, info->value_size); +	} else { +		unsigned int i, n; + +		n = get_possible_cpus(); + +		jsonw_name(json_wtr, "key"); +		print_hex_data_json(key, info->key_size); + +		jsonw_name(json_wtr, "values"); +		jsonw_start_array(json_wtr); +		for (i = 0; i < n; i++) { +			jsonw_start_object(json_wtr); + +			jsonw_int_field(json_wtr, "cpu", i); + +			jsonw_name(json_wtr, "value"); +			print_hex_data_json(value + i * info->value_size, +					    info->value_size); + +			jsonw_end_object(json_wtr); +		} +		jsonw_end_array(json_wtr); +	} + +	jsonw_end_object(json_wtr); +} + +static void print_entry_plain(struct bpf_map_info *info, unsigned char *key, +			      unsigned char *value) +{ +	if (!map_is_per_cpu(info->type)) { +		bool single_line, break_names; + +		break_names = info->key_size > 16 || info->value_size > 16; +		single_line = info->key_size + info->value_size <= 24 && +			!break_names; + +		printf("key:%c", break_names ? '\n' : ' '); +		fprint_hex(stdout, key, info->key_size, " "); + +		printf(single_line ? "  " : "\n"); + +		printf("value:%c", break_names ? '\n' : ' '); +		fprint_hex(stdout, value, info->value_size, " "); + +		printf("\n"); +	} else { +		unsigned int i, n; + +		n = get_possible_cpus(); + +		printf("key:\n"); +		fprint_hex(stdout, key, info->key_size, " "); +		printf("\n"); +		for (i = 0; i < n; i++) { +			printf("value (CPU %02d):%c", +			       i, info->value_size > 16 ? '\n' : ' '); +			fprint_hex(stdout, value + i * info->value_size, +				   info->value_size, " "); +			printf("\n"); +		} +	} +} + +static char **parse_bytes(char **argv, const char *name, unsigned char *val, +			  unsigned int n) +{ +	unsigned int i = 0; +	char *endptr; + +	while (i < n && argv[i]) { +		val[i] = strtoul(argv[i], &endptr, 0); +		if (*endptr) { +			p_err("error parsing byte: %s", argv[i]); +			return NULL; +		} +		i++; +	} + +	if (i != n) { +		p_err("%s expected %d bytes got %d", name, n, i); +		return NULL; +	} + +	return argv + i; +} + +static int parse_elem(char **argv, struct bpf_map_info *info, +		      void *key, void *value, __u32 key_size, __u32 value_size, +		      __u32 *flags, __u32 **value_fd) +{ +	if (!*argv) { +		if (!key && !value) +			return 0; +		p_err("did not find %s", key ? "key" : "value"); +		return -1; +	} + +	if (is_prefix(*argv, "key")) { +		if (!key) { +			if (key_size) +				p_err("duplicate key"); +			else +				p_err("unnecessary key"); +			return -1; +		} + +		argv = parse_bytes(argv + 1, "key", key, key_size); +		if (!argv) +			return -1; + +		return parse_elem(argv, info, NULL, value, key_size, value_size, +				  flags, value_fd); +	} else if (is_prefix(*argv, "value")) { +		int fd; + +		if (!value) { +			if (value_size) +				p_err("duplicate value"); +			else +				p_err("unnecessary value"); +			return -1; +		} + +		argv++; + +		if (map_is_map_of_maps(info->type)) { +			int argc = 2; + +			if (value_size != 4) { +				p_err("value smaller than 4B for map in map?"); +				return -1; +			} +			if (!argv[0] || !argv[1]) { +				p_err("not enough value arguments for map in map"); +				return -1; +			} + +			fd = map_parse_fd(&argc, &argv); +			if (fd < 0) +				return -1; + +			*value_fd = value; +			**value_fd = fd; +		} else if (map_is_map_of_progs(info->type)) { +			int argc = 2; + +			if (value_size != 4) { +				p_err("value smaller than 4B for map of progs?"); +				return -1; +			} +			if (!argv[0] || !argv[1]) { +				p_err("not enough value arguments for map of progs"); +				return -1; +			} + +			fd = prog_parse_fd(&argc, &argv); +			if (fd < 0) +				return -1; + +			*value_fd = value; +			**value_fd = fd; +		} else { +			argv = parse_bytes(argv, "value", value, value_size); +			if (!argv) +				return -1; +		} + +		return parse_elem(argv, info, key, NULL, key_size, value_size, +				  flags, NULL); +	} else if (is_prefix(*argv, "any") || is_prefix(*argv, "noexist") || +		   is_prefix(*argv, "exist")) { +		if (!flags) { +			p_err("flags specified multiple times: %s", *argv); +			return -1; +		} + +		if (is_prefix(*argv, "any")) +			*flags = BPF_ANY; +		else if (is_prefix(*argv, "noexist")) +			*flags = BPF_NOEXIST; +		else if (is_prefix(*argv, "exist")) +			*flags = BPF_EXIST; + +		return parse_elem(argv + 1, info, key, value, key_size, +				  value_size, NULL, value_fd); +	} + +	p_err("expected key or value, got: %s", *argv); +	return -1; +} + +static int show_map_close_json(int fd, struct bpf_map_info *info) +{ +	char *memlock; + +	memlock = get_fdinfo(fd, "memlock"); +	close(fd); + +	jsonw_start_object(json_wtr); + +	jsonw_uint_field(json_wtr, "id", info->id); +	if (info->type < ARRAY_SIZE(map_type_name)) +		jsonw_string_field(json_wtr, "type", +				   map_type_name[info->type]); +	else +		jsonw_uint_field(json_wtr, "type", info->type); + +	if (*info->name) +		jsonw_string_field(json_wtr, "name", info->name); + +	jsonw_name(json_wtr, "flags"); +	jsonw_printf(json_wtr, "%#x", info->map_flags); +	jsonw_uint_field(json_wtr, "bytes_key", info->key_size); +	jsonw_uint_field(json_wtr, "bytes_value", info->value_size); +	jsonw_uint_field(json_wtr, "max_entries", info->max_entries); + +	if (memlock) +		jsonw_int_field(json_wtr, "bytes_memlock", atoi(memlock)); +	free(memlock); + +	if (!hash_empty(map_table.table)) { +		struct pinned_obj *obj; + +		jsonw_name(json_wtr, "pinned"); +		jsonw_start_array(json_wtr); +		hash_for_each_possible(map_table.table, obj, hash, info->id) { +			if (obj->id == info->id) +				jsonw_string(json_wtr, obj->path); +		} +		jsonw_end_array(json_wtr); +	} + +	jsonw_end_object(json_wtr); + +	return 0; +} + +static int show_map_close_plain(int fd, struct bpf_map_info *info) +{ +	char *memlock; + +	memlock = get_fdinfo(fd, "memlock"); +	close(fd); + +	printf("%u: ", info->id); +	if (info->type < ARRAY_SIZE(map_type_name)) +		printf("%s  ", map_type_name[info->type]); +	else +		printf("type %u  ", info->type); + +	if (*info->name) +		printf("name %s  ", info->name); + +	printf("flags 0x%x\n", info->map_flags); +	printf("\tkey %uB  value %uB  max_entries %u", +	       info->key_size, info->value_size, info->max_entries); + +	if (memlock) +		printf("  memlock %sB", memlock); +	free(memlock); + +	printf("\n"); +	if (!hash_empty(map_table.table)) { +		struct pinned_obj *obj; + +		hash_for_each_possible(map_table.table, obj, hash, info->id) { +			if (obj->id == info->id) +				printf("\tpinned %s\n", obj->path); +		} +	} +	return 0; +} + +static int do_show(int argc, char **argv) +{ +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	__u32 id = 0; +	int err; +	int fd; + +	if (show_pinned) +		build_pinned_obj_table(&map_table, BPF_OBJ_MAP); + +	if (argc == 2) { +		fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +		if (fd < 0) +			return -1; + +		if (json_output) +			return show_map_close_json(fd, &info); +		else +			return show_map_close_plain(fd, &info); +	} + +	if (argc) +		return BAD_ARG(); + +	if (json_output) +		jsonw_start_array(json_wtr); +	while (true) { +		err = bpf_map_get_next_id(id, &id); +		if (err) { +			if (errno == ENOENT) +				break; +			p_err("can't get next map: %s%s", strerror(errno), +			      errno == EINVAL ? " -- kernel too old?" : ""); +			return -1; +		} + +		fd = bpf_map_get_fd_by_id(id); +		if (fd < 0) { +			p_err("can't get map by id (%u): %s", +			      id, strerror(errno)); +			return -1; +		} + +		err = bpf_obj_get_info_by_fd(fd, &info, &len); +		if (err) { +			p_err("can't get map info: %s", strerror(errno)); +			close(fd); +			return -1; +		} + +		if (json_output) +			show_map_close_json(fd, &info); +		else +			show_map_close_plain(fd, &info); +	} +	if (json_output) +		jsonw_end_array(json_wtr); + +	return errno == ENOENT ? 0 : -1; +} + +static int do_dump(int argc, char **argv) +{ +	void *key, *value, *prev_key; +	unsigned int num_elems = 0; +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	int err; +	int fd; + +	if (argc != 2) +		usage(); + +	fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +	if (fd < 0) +		return -1; + +	if (map_is_map_of_maps(info.type) || map_is_map_of_progs(info.type)) { +		p_err("Dumping maps of maps and program maps not supported"); +		close(fd); +		return -1; +	} + +	key = malloc(info.key_size); +	value = alloc_value(&info); +	if (!key || !value) { +		p_err("mem alloc failed"); +		err = -1; +		goto exit_free; +	} + +	prev_key = NULL; +	if (json_output) +		jsonw_start_array(json_wtr); +	while (true) { +		err = bpf_map_get_next_key(fd, prev_key, key); +		if (err) { +			if (errno == ENOENT) +				err = 0; +			break; +		} + +		if (!bpf_map_lookup_elem(fd, key, value)) { +			if (json_output) +				print_entry_json(&info, key, value); +			else +				print_entry_plain(&info, key, value); +		} else { +			if (json_output) { +				jsonw_name(json_wtr, "key"); +				print_hex_data_json(key, info.key_size); +				jsonw_name(json_wtr, "value"); +				jsonw_start_object(json_wtr); +				jsonw_string_field(json_wtr, "error", +						   "can't lookup element"); +				jsonw_end_object(json_wtr); +			} else { +				p_info("can't lookup element with key: "); +				fprint_hex(stderr, key, info.key_size, " "); +				fprintf(stderr, "\n"); +			} +		} + +		prev_key = key; +		num_elems++; +	} + +	if (json_output) +		jsonw_end_array(json_wtr); +	else +		printf("Found %u element%s\n", num_elems, +		       num_elems != 1 ? "s" : ""); + +exit_free: +	free(key); +	free(value); +	close(fd); + +	return err; +} + +static int do_update(int argc, char **argv) +{ +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	__u32 *value_fd = NULL; +	__u32 flags = BPF_ANY; +	void *key, *value; +	int fd, err; + +	if (argc < 2) +		usage(); + +	fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +	if (fd < 0) +		return -1; + +	key = malloc(info.key_size); +	value = alloc_value(&info); +	if (!key || !value) { +		p_err("mem alloc failed"); +		err = -1; +		goto exit_free; +	} + +	err = parse_elem(argv, &info, key, value, info.key_size, +			 info.value_size, &flags, &value_fd); +	if (err) +		goto exit_free; + +	err = bpf_map_update_elem(fd, key, value, flags); +	if (err) { +		p_err("update failed: %s", strerror(errno)); +		goto exit_free; +	} + +exit_free: +	if (value_fd) +		close(*value_fd); +	free(key); +	free(value); +	close(fd); + +	if (!err && json_output) +		jsonw_null(json_wtr); +	return err; +} + +static int do_lookup(int argc, char **argv) +{ +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	void *key, *value; +	int err; +	int fd; + +	if (argc < 2) +		usage(); + +	fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +	if (fd < 0) +		return -1; + +	key = malloc(info.key_size); +	value = alloc_value(&info); +	if (!key || !value) { +		p_err("mem alloc failed"); +		err = -1; +		goto exit_free; +	} + +	err = parse_elem(argv, &info, key, NULL, info.key_size, 0, NULL, NULL); +	if (err) +		goto exit_free; + +	err = bpf_map_lookup_elem(fd, key, value); +	if (!err) { +		if (json_output) +			print_entry_json(&info, key, value); +		else +			print_entry_plain(&info, key, value); +	} else if (errno == ENOENT) { +		if (json_output) { +			jsonw_null(json_wtr); +		} else { +			printf("key:\n"); +			fprint_hex(stdout, key, info.key_size, " "); +			printf("\n\nNot found\n"); +		} +	} else { +		p_err("lookup failed: %s", strerror(errno)); +	} + +exit_free: +	free(key); +	free(value); +	close(fd); + +	return err; +} + +static int do_getnext(int argc, char **argv) +{ +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	void *key, *nextkey; +	int err; +	int fd; + +	if (argc < 2) +		usage(); + +	fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +	if (fd < 0) +		return -1; + +	key = malloc(info.key_size); +	nextkey = malloc(info.key_size); +	if (!key || !nextkey) { +		p_err("mem alloc failed"); +		err = -1; +		goto exit_free; +	} + +	if (argc) { +		err = parse_elem(argv, &info, key, NULL, info.key_size, 0, +				 NULL, NULL); +		if (err) +			goto exit_free; +	} else { +		free(key); +		key = NULL; +	} + +	err = bpf_map_get_next_key(fd, key, nextkey); +	if (err) { +		p_err("can't get next key: %s", strerror(errno)); +		goto exit_free; +	} + +	if (json_output) { +		jsonw_start_object(json_wtr); +		if (key) { +			jsonw_name(json_wtr, "key"); +			print_hex_data_json(key, info.key_size); +		} else { +			jsonw_null_field(json_wtr, "key"); +		} +		jsonw_name(json_wtr, "next_key"); +		print_hex_data_json(nextkey, info.key_size); +		jsonw_end_object(json_wtr); +	} else { +		if (key) { +			printf("key:\n"); +			fprint_hex(stdout, key, info.key_size, " "); +			printf("\n"); +		} else { +			printf("key: None\n"); +		} +		printf("next key:\n"); +		fprint_hex(stdout, nextkey, info.key_size, " "); +		printf("\n"); +	} + +exit_free: +	free(nextkey); +	free(key); +	close(fd); + +	return err; +} + +static int do_delete(int argc, char **argv) +{ +	struct bpf_map_info info = {}; +	__u32 len = sizeof(info); +	void *key; +	int err; +	int fd; + +	if (argc < 2) +		usage(); + +	fd = map_parse_fd_and_info(&argc, &argv, &info, &len); +	if (fd < 0) +		return -1; + +	key = malloc(info.key_size); +	if (!key) { +		p_err("mem alloc failed"); +		err = -1; +		goto exit_free; +	} + +	err = parse_elem(argv, &info, key, NULL, info.key_size, 0, NULL, NULL); +	if (err) +		goto exit_free; + +	err = bpf_map_delete_elem(fd, key); +	if (err) +		p_err("delete failed: %s", strerror(errno)); + +exit_free: +	free(key); +	close(fd); + +	if (!err && json_output) +		jsonw_null(json_wtr); +	return err; +} + +static int do_pin(int argc, char **argv) +{ +	int err; + +	err = do_pin_any(argc, argv, bpf_map_get_fd_by_id); +	if (!err && json_output) +		jsonw_null(json_wtr); +	return err; +} + +static int do_help(int argc, char **argv) +{ +	if (json_output) { +		jsonw_null(json_wtr); +		return 0; +	} + +	fprintf(stderr, +		"Usage: %s %s show   [MAP]\n" +		"       %s %s dump    MAP\n" +		"       %s %s update  MAP  key BYTES value VALUE [UPDATE_FLAGS]\n" +		"       %s %s lookup  MAP  key BYTES\n" +		"       %s %s getnext MAP [key BYTES]\n" +		"       %s %s delete  MAP  key BYTES\n" +		"       %s %s pin     MAP  FILE\n" +		"       %s %s help\n" +		"\n" +		"       MAP := { id MAP_ID | pinned FILE }\n" +		"       " HELP_SPEC_PROGRAM "\n" +		"       VALUE := { BYTES | MAP | PROG }\n" +		"       UPDATE_FLAGS := { any | exist | noexist }\n" +		"       " HELP_SPEC_OPTIONS "\n" +		"", +		bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], +		bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], +		bin_name, argv[-2], bin_name, argv[-2]); + +	return 0; +} + +static const struct cmd cmds[] = { +	{ "show",	do_show }, +	{ "help",	do_help }, +	{ "dump",	do_dump }, +	{ "update",	do_update }, +	{ "lookup",	do_lookup }, +	{ "getnext",	do_getnext }, +	{ "delete",	do_delete }, +	{ "pin",	do_pin }, +	{ 0 } +}; + +int do_map(int argc, char **argv) +{ +	return cmd_select(cmds, argc, argv, do_help); +} diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c new file mode 100644 index 000000000000..ad619b96c276 --- /dev/null +++ b/tools/bpf/bpftool/prog.c @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2017 Netronome Systems, Inc. + * + * This software is dual licensed under the GNU General License Version 2, + * June 1991 as shown in the file COPYING in the top-level directory of this + * source tree or the BSD 2-Clause License provided below.  You have the + * option to license this software under the complete terms of either license. + * + * The BSD 2-Clause License: + * + *     Redistribution and use in source and binary forms, with or + *     without modification, are permitted provided that the following + *     conditions are met: + * + *      1. Redistributions of source code must retain the above + *         copyright notice, this list of conditions and the following + *         disclaimer. + * + *      2. Redistributions in binary form must reproduce the above + *         copyright notice, this list of conditions and the following + *         disclaimer in the documentation and/or other materials + *         provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Author: Jakub Kicinski <kubakici@wp.pl> */ + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <bpf.h> + +#include "main.h" +#include "disasm.h" + +static const char * const prog_type_name[] = { +	[BPF_PROG_TYPE_UNSPEC]		= "unspec", +	[BPF_PROG_TYPE_SOCKET_FILTER]	= "socket_filter", +	[BPF_PROG_TYPE_KPROBE]		= "kprobe", +	[BPF_PROG_TYPE_SCHED_CLS]	= "sched_cls", +	[BPF_PROG_TYPE_SCHED_ACT]	= "sched_act", +	[BPF_PROG_TYPE_TRACEPOINT]	= "tracepoint", +	[BPF_PROG_TYPE_XDP]		= "xdp", +	[BPF_PROG_TYPE_PERF_EVENT]	= "perf_event", +	[BPF_PROG_TYPE_CGROUP_SKB]	= "cgroup_skb", +	[BPF_PROG_TYPE_CGROUP_SOCK]	= "cgroup_sock", +	[BPF_PROG_TYPE_LWT_IN]		= "lwt_in", +	[BPF_PROG_TYPE_LWT_OUT]		= "lwt_out", +	[BPF_PROG_TYPE_LWT_XMIT]	= "lwt_xmit", +	[BPF_PROG_TYPE_SOCK_OPS]	= "sock_ops", +	[BPF_PROG_TYPE_SK_SKB]		= "sk_skb", +}; + +static void print_boot_time(__u64 nsecs, char *buf, unsigned int size) +{ +	struct timespec real_time_ts, boot_time_ts; +	time_t wallclock_secs; +	struct tm load_tm; + +	buf[--size] = '\0'; + +	if (clock_gettime(CLOCK_REALTIME, &real_time_ts) || +	    clock_gettime(CLOCK_BOOTTIME, &boot_time_ts)) { +		perror("Can't read clocks"); +		snprintf(buf, size, "%llu", nsecs / 1000000000); +		return; +	} + +	wallclock_secs = (real_time_ts.tv_sec - boot_time_ts.tv_sec) + +		nsecs / 1000000000; + +	if (!localtime_r(&wallclock_secs, &load_tm)) { +		snprintf(buf, size, "%llu", nsecs / 1000000000); +		return; +	} + +	strftime(buf, size, "%b %d/%H:%M", &load_tm); +} + +static int prog_fd_by_tag(unsigned char *tag) +{ +	struct bpf_prog_info info = {}; +	__u32 len = sizeof(info); +	unsigned int id = 0; +	int err; +	int fd; + +	while (true) { +		err = bpf_prog_get_next_id(id, &id); +		if (err) { +			p_err("%s", strerror(errno)); +			return -1; +		} + +		fd = bpf_prog_get_fd_by_id(id); +		if (fd < 0) { +			p_err("can't get prog by id (%u): %s", +			      id, strerror(errno)); +			return -1; +		} + +		err = bpf_obj_get_info_by_fd(fd, &info, &len); +		if (err) { +			p_err("can't get prog info (%u): %s", +			      id, strerror(errno)); +			close(fd); +			return -1; +		} + +		if (!memcmp(tag, info.tag, BPF_TAG_SIZE)) +			return fd; + +		close(fd); +	} +} + +int prog_parse_fd(int *argc, char ***argv) +{ +	int fd; + +	if (is_prefix(**argv, "id")) { +		unsigned int id; +		char *endptr; + +		NEXT_ARGP(); + +		id = strtoul(**argv, &endptr, 0); +		if (*endptr) { +			p_err("can't parse %s as ID", **argv); +			return -1; +		} +		NEXT_ARGP(); + +		fd = bpf_prog_get_fd_by_id(id); +		if (fd < 0) +			p_err("get by id (%u): %s", id, strerror(errno)); +		return fd; +	} else if (is_prefix(**argv, "tag")) { +		unsigned char tag[BPF_TAG_SIZE]; + +		NEXT_ARGP(); + +		if (sscanf(**argv, BPF_TAG_FMT, tag, tag + 1, tag + 2, +			   tag + 3, tag + 4, tag + 5, tag + 6, tag + 7) +		    != BPF_TAG_SIZE) { +			p_err("can't parse tag"); +			return -1; +		} +		NEXT_ARGP(); + +		return prog_fd_by_tag(tag); +	} else if (is_prefix(**argv, "pinned")) { +		char *path; + +		NEXT_ARGP(); + +		path = **argv; +		NEXT_ARGP(); + +		return open_obj_pinned_any(path, BPF_OBJ_PROG); +	} + +	p_err("expected 'id', 'tag' or 'pinned', got: '%s'?", **argv); +	return -1; +} + +static void show_prog_maps(int fd, u32 num_maps) +{ +	struct bpf_prog_info info = {}; +	__u32 len = sizeof(info); +	__u32 map_ids[num_maps]; +	unsigned int i; +	int err; + +	info.nr_map_ids = num_maps; +	info.map_ids = ptr_to_u64(map_ids); + +	err = bpf_obj_get_info_by_fd(fd, &info, &len); +	if (err || !info.nr_map_ids) +		return; + +	if (json_output) { +		jsonw_name(json_wtr, "map_ids"); +		jsonw_start_array(json_wtr); +		for (i = 0; i < info.nr_map_ids; i++) +			jsonw_uint(json_wtr, map_ids[i]); +		jsonw_end_array(json_wtr); +	} else { +		printf("  map_ids "); +		for (i = 0; i < info.nr_map_ids; i++) +			printf("%u%s", map_ids[i], +			       i == info.nr_map_ids - 1 ? "" : ","); +	} +} + +static void print_prog_json(struct bpf_prog_info *info, int fd) +{ +	char *memlock; + +	jsonw_start_object(json_wtr); +	jsonw_uint_field(json_wtr, "id", info->id); +	if (info->type < ARRAY_SIZE(prog_type_name)) +		jsonw_string_field(json_wtr, "type", +				   prog_type_name[info->type]); +	else +		jsonw_uint_field(json_wtr, "type", info->type); + +	if (*info->name) +		jsonw_string_field(json_wtr, "name", info->name); + +	jsonw_name(json_wtr, "tag"); +	jsonw_printf(json_wtr, "\"" BPF_TAG_FMT "\"", +		     info->tag[0], info->tag[1], info->tag[2], info->tag[3], +		     info->tag[4], info->tag[5], info->tag[6], info->tag[7]); + +	if (info->load_time) { +		char buf[32]; + +		print_boot_time(info->load_time, buf, sizeof(buf)); + +		/* Piggy back on load_time, since 0 uid is a valid one */ +		jsonw_string_field(json_wtr, "loaded_at", buf); +		jsonw_uint_field(json_wtr, "uid", info->created_by_uid); +	} + +	jsonw_uint_field(json_wtr, "bytes_xlated", info->xlated_prog_len); + +	if (info->jited_prog_len) { +		jsonw_bool_field(json_wtr, "jited", true); +		jsonw_uint_field(json_wtr, "bytes_jited", info->jited_prog_len); +	} else { +		jsonw_bool_field(json_wtr, "jited", false); +	} + +	memlock = get_fdinfo(fd, "memlock"); +	if (memlock) +		jsonw_int_field(json_wtr, "bytes_memlock", atoi(memlock)); +	free(memlock); + +	if (info->nr_map_ids) +		show_prog_maps(fd, info->nr_map_ids); + +	if (!hash_empty(prog_table.table)) { +		struct pinned_obj *obj; + +		jsonw_name(json_wtr, "pinned"); +		jsonw_start_array(json_wtr); +		hash_for_each_possible(prog_table.table, obj, hash, info->id) { +			if (obj->id == info->id) +				jsonw_string(json_wtr, obj->path); +		} +		jsonw_end_array(json_wtr); +	} + +	jsonw_end_object(json_wtr); +} + +static void print_prog_plain(struct bpf_prog_info *info, int fd) +{ +	char *memlock; + +	printf("%u: ", info->id); +	if (info->type < ARRAY_SIZE(prog_type_name)) +		printf("%s  ", prog_type_name[info->type]); +	else +		printf("type %u  ", info->type); + +	if (*info->name) +		printf("name %s  ", info->name); + +	printf("tag "); +	fprint_hex(stdout, info->tag, BPF_TAG_SIZE, ""); +	printf("\n"); + +	if (info->load_time) { +		char buf[32]; + +		print_boot_time(info->load_time, buf, sizeof(buf)); + +		/* Piggy back on load_time, since 0 uid is a valid one */ +		printf("\tloaded_at %s  uid %u\n", buf, info->created_by_uid); +	} + +	printf("\txlated %uB", info->xlated_prog_len); + +	if (info->jited_prog_len) +		printf("  jited %uB", info->jited_prog_len); +	else +		printf("  not jited"); + +	memlock = get_fdinfo(fd, "memlock"); +	if (memlock) +		printf("  memlock %sB", memlock); +	free(memlock); + +	if (info->nr_map_ids) +		show_prog_maps(fd, info->nr_map_ids); + +	if (!hash_empty(prog_table.table)) { +		struct pinned_obj *obj; + +		printf("\n"); +		hash_for_each_possible(prog_table.table, obj, hash, info->id) { +			if (obj->id == info->id) +				printf("\tpinned %s\n", obj->path); +		} +	} + +	printf("\n"); +} + +static int show_prog(int fd) +{ +	struct bpf_prog_info info = {}; +	__u32 len = sizeof(info); +	int err; + +	err = bpf_obj_get_info_by_fd(fd, &info, &len); +	if (err) { +		p_err("can't get prog info: %s", strerror(errno)); +		return -1; +	} + +	if (json_output) +		print_prog_json(&info, fd); +	else +		print_prog_plain(&info, fd); + +	return 0; +} + +static int do_show(int argc, char **argv) +{ +	__u32 id = 0; +	int err; +	int fd; + +	if (show_pinned) +		build_pinned_obj_table(&prog_table, BPF_OBJ_PROG); + +	if (argc == 2) { +		fd = prog_parse_fd(&argc, &argv); +		if (fd < 0) +			return -1; + +		return show_prog(fd); +	} + +	if (argc) +		return BAD_ARG(); + +	if (json_output) +		jsonw_start_array(json_wtr); +	while (true) { +		err = bpf_prog_get_next_id(id, &id); +		if (err) { +			if (errno == ENOENT) { +				err = 0; +				break; +			} +			p_err("can't get next program: %s%s", strerror(errno), +			      errno == EINVAL ? " -- kernel too old?" : ""); +			err = -1; +			break; +		} + +		fd = bpf_prog_get_fd_by_id(id); +		if (fd < 0) { +			p_err("can't get prog by id (%u): %s", +			      id, strerror(errno)); +			err = -1; +			break; +		} + +		err = show_prog(fd); +		close(fd); +		if (err) +			break; +	} + +	if (json_output) +		jsonw_end_array(json_wtr); + +	return err; +} + +static void print_insn(struct bpf_verifier_env *env, const char *fmt, ...) +{ +	va_list args; + +	va_start(args, fmt); +	vprintf(fmt, args); +	va_end(args); +} + +static void dump_xlated_plain(void *buf, unsigned int len, bool opcodes) +{ +	struct bpf_insn *insn = buf; +	bool double_insn = false; +	unsigned int i; + +	for (i = 0; i < len / sizeof(*insn); i++) { +		if (double_insn) { +			double_insn = false; +			continue; +		} + +		double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); + +		printf("% 4d: ", i); +		print_bpf_insn(print_insn, NULL, insn + i, true); + +		if (opcodes) { +			printf("       "); +			fprint_hex(stdout, insn + i, 8, " "); +			if (double_insn && i < len - 1) { +				printf(" "); +				fprint_hex(stdout, insn + i + 1, 8, " "); +			} +			printf("\n"); +		} +	} +} + +static void print_insn_json(struct bpf_verifier_env *env, const char *fmt, ...) +{ +	unsigned int l = strlen(fmt); +	char chomped_fmt[l]; +	va_list args; + +	va_start(args, fmt); +	if (l > 0) { +		strncpy(chomped_fmt, fmt, l - 1); +		chomped_fmt[l - 1] = '\0'; +	} +	jsonw_vprintf_enquote(json_wtr, chomped_fmt, args); +	va_end(args); +} + +static void dump_xlated_json(void *buf, unsigned int len, bool opcodes) +{ +	struct bpf_insn *insn = buf; +	bool double_insn = false; +	unsigned int i; + +	jsonw_start_array(json_wtr); +	for (i = 0; i < len / sizeof(*insn); i++) { +		if (double_insn) { +			double_insn = false; +			continue; +		} +		double_insn = insn[i].code == (BPF_LD | BPF_IMM | BPF_DW); + +		jsonw_start_object(json_wtr); +		jsonw_name(json_wtr, "disasm"); +		print_bpf_insn(print_insn_json, NULL, insn + i, true); + +		if (opcodes) { +			jsonw_name(json_wtr, "opcodes"); +			jsonw_start_object(json_wtr); + +			jsonw_name(json_wtr, "code"); +			jsonw_printf(json_wtr, "\"0x%02hhx\"", insn[i].code); + +			jsonw_name(json_wtr, "src_reg"); +			jsonw_printf(json_wtr, "\"0x%hhx\"", insn[i].src_reg); + +			jsonw_name(json_wtr, "dst_reg"); +			jsonw_printf(json_wtr, "\"0x%hhx\"", insn[i].dst_reg); + +			jsonw_name(json_wtr, "off"); +			print_hex_data_json((uint8_t *)(&insn[i].off), 2); + +			jsonw_name(json_wtr, "imm"); +			if (double_insn && i < len - 1) +				print_hex_data_json((uint8_t *)(&insn[i].imm), +						    12); +			else +				print_hex_data_json((uint8_t *)(&insn[i].imm), +						    4); +			jsonw_end_object(json_wtr); +		} +		jsonw_end_object(json_wtr); +	} +	jsonw_end_array(json_wtr); +} + +static int do_dump(int argc, char **argv) +{ +	struct bpf_prog_info info = {}; +	__u32 len = sizeof(info); +	unsigned int buf_size; +	char *filepath = NULL; +	bool opcodes = false; +	unsigned char *buf; +	__u32 *member_len; +	__u64 *member_ptr; +	ssize_t n; +	int err; +	int fd; + +	if (is_prefix(*argv, "jited")) { +		member_len = &info.jited_prog_len; +		member_ptr = &info.jited_prog_insns; +	} else if (is_prefix(*argv, "xlated")) { +		member_len = &info.xlated_prog_len; +		member_ptr = &info.xlated_prog_insns; +	} else { +		p_err("expected 'xlated' or 'jited', got: %s", *argv); +		return -1; +	} +	NEXT_ARG(); + +	if (argc < 2) +		usage(); + +	fd = prog_parse_fd(&argc, &argv); +	if (fd < 0) +		return -1; + +	if (is_prefix(*argv, "file")) { +		NEXT_ARG(); +		if (!argc) { +			p_err("expected file path"); +			return -1; +		} + +		filepath = *argv; +		NEXT_ARG(); +	} else if (is_prefix(*argv, "opcodes")) { +		opcodes = true; +		NEXT_ARG(); +	} + +	if (argc) { +		usage(); +		return -1; +	} + +	err = bpf_obj_get_info_by_fd(fd, &info, &len); +	if (err) { +		p_err("can't get prog info: %s", strerror(errno)); +		return -1; +	} + +	if (!*member_len) { +		p_info("no instructions returned"); +		close(fd); +		return 0; +	} + +	buf_size = *member_len; + +	buf = malloc(buf_size); +	if (!buf) { +		p_err("mem alloc failed"); +		close(fd); +		return -1; +	} + +	memset(&info, 0, sizeof(info)); + +	*member_ptr = ptr_to_u64(buf); +	*member_len = buf_size; + +	err = bpf_obj_get_info_by_fd(fd, &info, &len); +	close(fd); +	if (err) { +		p_err("can't get prog info: %s", strerror(errno)); +		goto err_free; +	} + +	if (*member_len > buf_size) { +		p_err("too many instructions returned"); +		goto err_free; +	} + +	if (filepath) { +		fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0600); +		if (fd < 0) { +			p_err("can't open file %s: %s", filepath, +			      strerror(errno)); +			goto err_free; +		} + +		n = write(fd, buf, *member_len); +		close(fd); +		if (n != *member_len) { +			p_err("error writing output file: %s", +			      n < 0 ? strerror(errno) : "short write"); +			goto err_free; +		} +	} else { +		if (member_len == &info.jited_prog_len) +			disasm_print_insn(buf, *member_len, opcodes); +		else +			if (json_output) +				dump_xlated_json(buf, *member_len, opcodes); +			else +				dump_xlated_plain(buf, *member_len, opcodes); +	} + +	free(buf); + +	return 0; + +err_free: +	free(buf); +	return -1; +} + +static int do_pin(int argc, char **argv) +{ +	int err; + +	err = do_pin_any(argc, argv, bpf_prog_get_fd_by_id); +	if (!err && json_output) +		jsonw_null(json_wtr); +	return err; +} + +static int do_help(int argc, char **argv) +{ +	if (json_output) { +		jsonw_null(json_wtr); +		return 0; +	} + +	fprintf(stderr, +		"Usage: %s %s show [PROG]\n" +		"       %s %s dump xlated PROG [{ file FILE | opcodes }]\n" +		"       %s %s dump jited  PROG [{ file FILE | opcodes }]\n" +		"       %s %s pin   PROG FILE\n" +		"       %s %s help\n" +		"\n" +		"       " HELP_SPEC_PROGRAM "\n" +		"       " HELP_SPEC_OPTIONS "\n" +		"", +		bin_name, argv[-2], bin_name, argv[-2], bin_name, argv[-2], +		bin_name, argv[-2], bin_name, argv[-2]); + +	return 0; +} + +static const struct cmd cmds[] = { +	{ "show",	do_show }, +	{ "help",	do_help }, +	{ "dump",	do_dump }, +	{ "pin",	do_pin }, +	{ 0 } +}; + +int do_prog(int argc, char **argv) +{ +	return cmd_select(cmds, argc, argv, do_help); +}  | 
