├── .gitignore ├── COPYING ├── Dockerfile ├── Makefile.in ├── README.md ├── autogen.sh ├── configure.ac ├── container_info.cpp ├── container_info.h ├── ebpf_flow.cpp ├── ebpf_flow.h ├── ebpf_types.h ├── ebpflow_code.ebpf ├── ebpflow_header.ebpf ├── ebpflowexport.cpp ├── ebpflowexport.go ├── examples └── c++ │ ├── README.md │ └── usage_libebpfflow.cpp ├── go └── ebpf_flow.go ├── pid2veth ├── Makefile ├── README.md └── pid2veth.c ├── utils ├── README.md ├── docker_show_veth.sh └── kubectl_show_veth.sh └── wireshark ├── Makefile ├── README.md ├── ebpf.lua ├── ebpfdump.c ├── pcapio.c └── pcapio.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | ebpflow.ebpf.enc 4 | ebpftest 5 | toolebpflow 6 | autom4te.cache/ -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN echo "deb [trusted=yes] http://repo.iovisor.org/apt/bionic bionic-nightly main" | \ 4 | tee /etc/apt/sources.list.d/iovisor.list && \ 5 | apt-get update -y && \ 6 | DEBIAN_FRONTEND=noninteractive apt-get install -y bcc-tools 7 | 8 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libcurl4-openssl-dev libjson-c-dev libzmq3-dev libbpfcc-dev 9 | 10 | COPY ebpflowexport /usr/share/ 11 | 12 | ENTRYPOINT ["/usr/share/ebpflowexport"] 13 | 14 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # 2 | # (C) 2018-22 - ntop.org 3 | # 4 | 5 | ############################################################### 6 | 7 | HAS_JSON=$(shell pkg-config --exists json-c; echo $$?) 8 | ifeq ($(HAS_JSON), 0) 9 | JSON_INC = $(shell pkg-config --cflags json-c) -DHAVE_JSONC 10 | JSON_LIB = $(shell pkg-config --libs json-c) 11 | endif 12 | 13 | ############################################################### 14 | 15 | HAS_LIBCURL=$(shell pkg-config --exists libcurl; echo $$?) 16 | ifeq ($(HAS_LIBCURL), 0) 17 | LIBCURL_INC = $(shell pkg-config --cflags libcurl) -DHAVE_LIBCURL 18 | LIBCURL_LIB = $(shell pkg-config --libs libcurl) 19 | endif 20 | 21 | ############################################################### 22 | 23 | CFLAGS=-std=c++11 -g -Wall $(JSON_INC) $(LIBCURL_INC) -fPIC 24 | LIBS=-lbcc -lzmq $(JSON_LIB) $(LIBCURL_LIB) 25 | GOROOT= 26 | 27 | all: ebpflowexport 28 | 29 | container_info.o: container_info.cpp container_info.h 30 | g++ -c $(CFLAGS) container_info.cpp -o container_info.o 31 | 32 | libebpfflow.a: ebpf_flow.cpp ebpf_flow.h container_info.o ebpflow.ebpf.enc 33 | g++ -c $(CFLAGS) ebpf_flow.cpp -o ebpf_flow.o 34 | ar rvs $@ ebpf_flow.o container_info.o 35 | 36 | ebpflowexport: ebpflowexport.cpp libebpfflow.a Makefile 37 | g++ $(CFLAGS) ebpflowexport.cpp -o $@ libebpfflow.a $(LIBS) 38 | 39 | ebpflow.ebpf.enc: ebpflow_header.ebpf ebpf_types.h ebpflow_code.ebpf Makefile 40 | echo -n "const char * ebpf_code = R\"(" > ebpflow.ebpf.enc 41 | cat ebpflow_header.ebpf ebpf_types.h ebpflow_code.ebpf | base64 -w 0 >> ebpflow.ebpf.enc 42 | echo ")\";" >> ebpflow.ebpf.enc 43 | 44 | go_ebpflowexport: ebpflowexport.go Makefile libebpfflow.a 45 | go build -o go_ebpflowexport ebpflowexport.go 46 | 47 | clean: 48 | /bin/rm -f *~ container_info.a libebpfflow.a *.o ebpflow.ebpf.enc 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libebpfflow 2 | Traffic visibility library based on eBPF 3 | 4 | ### Introduction 5 | libebpfflow is a traffic visibility library based on eBPF able to compute network flows. It can be used to: 6 | * enable network visibility 7 | * create a packet-less network probe 8 | * inspect host and container communications for different container runtimes 9 | 10 | ### Main features 11 | * Ability to inspect TCP and UDP traffic 12 | * Container visibility 13 | * TCP latency computation 14 | * Process and user visibility 15 | 16 | ### Supported Languages 17 | * Golang 18 | * C/C++ 19 | 20 | ### Requirements 21 | You need a modern eBPF-enabled Linux distribution. 22 | 23 | On Ubuntu 16.04/18.04/20.04 Server LTS you can install the prerequisites (we assume that the compiler is already installed) as follows: 24 | ```sh 25 | $ sudo apt-get install build-essential autoconf automake autogen libjson-c-dev pkg-config libzmq3-dev libcurl4-openssl-dev libbpfcc-dev 26 | ``` 27 | 28 | ### Build 29 | Generate makefile 30 | ```sh 31 | $ ./autogen.sh 32 | ``` 33 | 34 | Now build averything 35 | ```sh 36 | $ make 37 | ``` 38 | Go testing tool 39 | ```sh 40 | make go_ebpflowexport 41 | ``` 42 | 43 | ### Testing 44 | The library comes with two different tools: *ebpflowexport* and *go\_ebpflowexport*. In the _Build_ section is reported how to build the tools. Although both tools were developed to show potential library usage and to provide guidance on how to use the library, *ebpflowexport* displays all the information provided by *libebpfflow* and provides some options for filtering flow events while *go\_ebpflowexport* displays only basic information concerning events. 45 | ```sh 46 | $ sudo ./ebpflowexport -h 47 | ebpflowexport: Traffic visibility tool based on libebpfflow. By default all events will be shown 48 | Usage: ebpflow [ OPTIONS ] 49 | -h, --help display this message 50 | -t, --tcp TCP events 51 | -u, --udp UDP events 52 | -i, --in incoming events (i.e. TCP accept and UDP receive) 53 | -o, --on outgoing events (i.e. TCP connect and UDP send) 54 | -r, --retr retransmissions events 55 | -c, --tcpclose TCP close events 56 | -d, --docker gather additional information concerning containers (default: enabled) 57 | -v, --verbose vebose formatting (default: every event is shown) 58 | Note: please run as root 59 | ``` 60 | What follows is a demostration of the execution of *ebpflowexport* in a system where both minikube with containerd as runtime and docker containers are running at the same time. 61 | ```sh 62 | $ sudo ./ebpflowexport -tio 63 | Welcome to ebpflowexport v.1.0.190407 64 | (C) 2018-19 ntop.org 65 | Initializing eBPF [Legacy API]... 66 | eBPF initializated successfully 67 | 1554803923.684786 [lo][Sent][IPv4/TCP][pid/tid: 1446/496 [/usr/bin/kubelet], uid/gid: 0/0][father pid/tid: 1/0 [/lib/systemd/systemd], uid/gid: 0/0][addr: 127.0.0.1:53790 <-> 127.0.0.1:10252][latency: 0.10 msec] 68 | 1554803923.685139 [lo][Rcvd][IPv4/TCP][pid/tid: 2554/2329 [/usr/local/bin/kube-controller-manager], uid/gid: 0/0][father pid/tid: 2295/0 [/usr/local/bin/containerd-shim], uid/gid: 0/0][addr: 127.0.0.1:53790 <-> 127.0.0.1:10252][containerID: 275d71585e03][runtime: containerd][kube_pod: kube-controller-manager-minikube][kube_ns: kube-system][latency: 0.00 msec] 69 | 1554803924.781354 [eth0][Sent][IPv4/TCP][pid/tid: 30197/30197 [/usr/bin/curl], uid/gid: 0/0][father pid/tid: 26219/0 [/bin/bash], uid/gid: 0/0][addr: 172.17.0.2:54348 <-> 216.58.205.46:80][containerID: cbd2540ec5be][runtime: docker][docker_name: sleepy_haibt][latency: 0.22 msec] 70 | 1554803929.257494 [enp0s3][Sent][IPv4/TCP][pid/tid: 30221/30221 [/usr/lib/apt/methods/http], uid/gid: 104/65534][father pid/tid: 30216/0 [/usr/bin/apt], uid/gid: 0/0][addr: 10.0.2.15:37140 <-> 91.189.88.162:80][latency: 0.17 msec] 71 | ``` 72 | A basic example of usage in c++ can be found in the directory */examples* whereas for the Go language the example provided is the one in */go/ebpf_flow.go*. More details on how to use the library you can be found in the [ntopng](https://github.com/ntop/ntopng) code or by inspecting the code of the tool ebpflowexport application. 73 | 74 | ### Export eBPF Information to ntopng 75 | Supposing to start both ebpflowexport and ntopng on the same host do 76 | 77 | - ntopng -i tcp://127.0.0.1:1234 78 | - ebpflowexport -z tcp://127.0.0.1:1234 79 | 80 | 81 | ### Start as a Docker container 82 | To use ebpflowexport as a Docker container first you have to build the tool. Once the tool has been built, build the docker image from the project root: 83 | ```sh 84 | $ docker build -t ebpflowexport . 85 | ``` 86 | The container can then be run 87 | ```sh 88 | $ docker run -it --rm --privileged \ 89 | -v /lib/modules:/lib/modules:ro \ 90 | -v /usr/src:/usr/src:ro \ 91 | -v /etc/localtime:/etc/localtime:ro \ 92 | -v /sys/kernel/debug:/sys/kernel/debug \ 93 | -v /var/run/docker.sock:/var/run/docker.sock \ 94 | -v /snap/bin/microk8s.ctr:/snap/bin/microk8s.ctr \ 95 | ebpflowexport 96 | ``` 97 | 98 | ### Open Issues 99 | While the library is already usable in production, we plan to add some additional features including: 100 | * Implement periodic flow stats exports including bytes/packets/retransmissions 101 | 102 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -f config.h config.h.in *~ #* 4 | 5 | echo "Wait please..." 6 | autoreconf -if 7 | echo "" 8 | echo "Now running ./configure" 9 | ./configure 10 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | odnl> Do not add anything above 2 | AC_INIT([libebpfflow],[1.0.1]) 3 | dnl> Do not add anything above 4 | 5 | AC_PROG_CPP 6 | AC_PROG_CXX 7 | 8 | # Use C++ language for tests 9 | AC_LANG(C++) 10 | 11 | # libbpfcc-dev 12 | 13 | # AC_CHECK_LIB([bcc], [printf]) 14 | #if test "x$ac_cv_lib_bcc_printf" != x""yes; then 15 | # echo "Please install the bcc-dev(el) package and try again" 16 | # exit 17 | #fi 18 | 19 | # Libs required for compilation test below 20 | #LIBS="${LIBS} -lbcc" 21 | 22 | AC_MSG_CHECKING([eBPF library new API version]) 23 | AC_TRY_COMPILE([ 24 | #include 25 | ], [ ebpf::BPF *bpf; 26 | bpf->attach_kprobe((const char*)"", NULL, 0, BPF_PROBE_ENTRY); 27 | ], [ AC_MSG_RESULT(yes) AC_DEFINE_UNQUOTED(HAVE_NEW_EBPF, 1, [new BPF API]) ], [AC_MSG_RESULT(no)]) 28 | 29 | DATE=`date +"%y%m%d"` 30 | VERSION="1.0.${DATE}" 31 | 32 | AC_DEFINE_UNQUOTED(EBPF_FLOW_VERSION, "${VERSION}", "Library version") 33 | 34 | AC_CHECK_LIB([json-c], [json_object_new_object]) 35 | if test "x$ac_cv_lib_json_c_json_object_new_object" != "xyes"; then 36 | echo "Please install the json-c package and try again" 37 | exit 38 | fi 39 | 40 | AC_CHECK_LIB([curl], [curl_easy_init]) 41 | if test "x$ac_cv_lib_curl_curl_easy_init" != "xyes"; then 42 | echo "Please install the libcurl (libcurl4-openssl-dev) package and try again" 43 | exit 44 | fi 45 | 46 | AC_CHECK_LIB([zmq], [zmq_socket_monitor]) 47 | if test "x$ac_cv_lib_zmq_zmq_socket_monitor" != "xyes"; then : 48 | echo "Please install the ZMQ package and try again" 49 | exit 50 | fi 51 | 52 | if ! which pkg-config >/dev/null ; then 53 | AC_MSG_ERROR([Missing pkg-config: please check README.md]) 54 | fi 55 | 56 | 57 | AC_CHECK_LIB([json-c], json_object_new_double_s, AC_DEFINE_UNQUOTED(HAVE_DOUBLES, 1, [json-c has json_object_new_double_s])) 58 | 59 | AC_SUBST(EBPF_LIBRARY_VERSION) 60 | AC_CONFIG_HEADERS(config.h) 61 | AC_CONFIG_FILES(Makefile) 62 | 63 | AC_OUTPUT 64 | -------------------------------------------------------------------------------- /container_info.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #include "config.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #include "container_info.h" 38 | 39 | #define DOCKERD_SOCK_PATH "/var/run/docker.sock" 40 | #define MICROK8S_CTR_PATH "/snap/bin/microk8s.ctr" 41 | 42 | 43 | // Used to store libcurl partial results 44 | struct response_buffer { 45 | char *memory; 46 | size_t size; 47 | }; 48 | 49 | // #define DEBUG 50 | 51 | /* ************************************************* */ 52 | // ===== ===== INITIALIZER AND DESTROYER ===== ===== // 53 | /* ************************************************* */ 54 | ContainerInfo::ContainerInfo() { 55 | struct stat s; 56 | 57 | gQueryCache.clear(); 58 | 59 | if(stat(MICROK8S_CTR_PATH, &s) == 0) 60 | strcpy(ctr_path, MICROK8S_CTR_PATH); 61 | else 62 | strcpy(ctr_path, "ctr"); 63 | 64 | update_namespaces(); 65 | } 66 | 67 | ContainerInfo::~ContainerInfo() { 68 | gQueryCache.clear(); 69 | namespaces.clear(); 70 | } 71 | 72 | /* ********************************************** */ 73 | // ===== ===== QUERY TO DOCKER DAEMON ===== ===== // 74 | /* ********************************************** */ 75 | static size_t WriteMemoryCallback (void *contents, size_t size, size_t nmemb, void *userp) { 76 | size_t realsize = size * nmemb; 77 | struct response_buffer *mem = (struct response_buffer *) userp; 78 | 79 | char *ptr = (char*) realloc(mem->memory, mem->size + realsize + 1); 80 | if(ptr == NULL) { 81 | /* out of memory! */ 82 | #ifdef DEBUG 83 | printf("not enough memory (realloc returned NULL)\n"); 84 | #endif 85 | return(0); 86 | } 87 | 88 | mem->memory = ptr; 89 | memcpy(&(mem->memory[mem->size]), contents, realsize); 90 | mem->size += realsize; 91 | mem->memory[mem->size] = 0; 92 | 93 | return realsize; 94 | } 95 | 96 | /* **************************************************** */ 97 | 98 | /* parse_response - fill a container_info data structure with the information returned by a query to 99 | * the docker daemon 100 | * return(0) if no error occurred -1 otherwise 101 | */ 102 | int ContainerInfo::parse_response(const char* buff, ssize_t buffsize, struct container_info *entry) { 103 | int res_found = 0; // 1 if some info has been found 104 | struct json_object *jobj=NULL, *jlabel=NULL; 105 | struct json_object *jdockername, *jconfig, *jpodname, *jcname, *jkubens; 106 | struct json_tokener *jtok; 107 | 108 | #ifdef DEBUG 109 | printf("[%s:%u] %s(%s)\n", __FILE__, __LINE__, __FUNCTION__, buff); 110 | #endif 111 | 112 | //memset(entry, 0, sizeof(struct container_info)); 113 | 114 | // Parsing req to json 115 | jtok = json_tokener_new(); 116 | jobj = json_tokener_parse_ex(jtok, buff, buffsize); 117 | // We're not using it anymore, let's cleanup 118 | json_tokener_free(jtok); 119 | 120 | if(jobj == NULL) 121 | goto fail; 122 | 123 | // Docker name 124 | if(json_object_object_get_ex(jobj, "Name", &jdockername)) { 125 | res_found = 1; 126 | entry->docker.name = std::string(json_object_get_string(jdockername)+1); 127 | } 128 | 129 | /* 130 | "Labels": { 131 | "io.cri-containerd.kind": "container", 132 | "io.kubernetes.container.name": "dnsmasq", 133 | "io.kubernetes.pod.name": "kube-dns-6bfbdd666c-5jbmx", 134 | "io.kubernetes.pod.namespace": "kube-system", 135 | "io.kubernetes.pod.uid": "5528e13d-5df8-11e9-a377-001c427c953a" 136 | }, 137 | */ 138 | 139 | // Container labels 140 | if(json_object_object_get_ex(jobj, "Config", &jconfig)) /* json from docker api */ 141 | json_object_object_get_ex(jconfig, "Labels", &jlabel); 142 | else /* json from containerd api */ 143 | json_object_object_get_ex(jobj, "Labels", &jlabel); 144 | 145 | // Kubernetes info (when available) 146 | if(jlabel != NULL) { 147 | // Extracting kube info 148 | if(json_object_object_get_ex(jlabel, "io.kubernetes.pod.name", &jpodname)) { 149 | res_found = 1; 150 | entry->kube.pod = std::string(json_object_get_string(jpodname)); 151 | } 152 | 153 | if(json_object_object_get_ex(jlabel, "io.kubernetes.container.name", &jcname)) { 154 | res_found = 1; 155 | entry->kube.name = std::string(json_object_get_string(jcname)); 156 | } 157 | 158 | if(json_object_object_get_ex(jlabel, "io.kubernetes.pod.namespace", &jkubens)) { 159 | res_found = 1; 160 | entry->kube.ns = std::string(json_object_get_string(jkubens)); 161 | } 162 | } 163 | 164 | if(!res_found) 165 | goto fail; 166 | 167 | json_object_put(jobj); 168 | return(0); 169 | 170 | fail: 171 | if(jobj) 172 | json_object_put(jobj); 173 | 174 | // memset(entry, 0, sizeof(struct container_info)); 175 | return(-1); 176 | } 177 | 178 | /* **************************************************** */ 179 | 180 | int ContainerInfo::dockerd_update_query_cache(char* t_containerid, 181 | struct container_info **t_dqr) { 182 | int rc = 0; 183 | CURL *curl_handle; 184 | CURLcode res; 185 | char url[101]; 186 | struct response_buffer chunk; 187 | std::string cgroupid(t_containerid); 188 | struct stat s; 189 | struct cache_entry ce; 190 | 191 | #ifdef DEBUG 192 | printf("[%s:%u] %s()\n", __FILE__, __LINE__, __FUNCTION__); 193 | #endif 194 | 195 | (*t_dqr) = NULL; 196 | 197 | if(stat(DOCKERD_SOCK_PATH, &s) == -1) 198 | return(-1); /* Docker not found */ 199 | 200 | // Crafting query 201 | snprintf(url, sizeof(url), "http://localhost/containers/%s/json", t_containerid); 202 | 203 | // Performing query ----- // 204 | // Initializing memory buffer 205 | chunk.memory = (char*) malloc(1); 206 | chunk.size = 0; 207 | // Preparing libcurl 208 | curl_handle = curl_easy_init(); 209 | // URL 210 | curl_easy_setopt(curl_handle, CURLOPT_URL, url); 211 | // Callback && Callback arguments 212 | curl_easy_setopt(curl_handle, CURLOPT_UNIX_SOCKET_PATH, DOCKERD_SOCK_PATH); 213 | curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); 214 | curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); 215 | 216 | res = curl_easy_perform(curl_handle); 217 | 218 | // Checking for errors 219 | if(res == CURLE_OK) { 220 | rc = parse_response(chunk.memory, chunk.size, &ce.content); 221 | } else { 222 | #ifdef DEBUG 223 | printf("curl_easy_perform(%s) failed: %s\n", url, curl_easy_strerror(res)); 224 | #endif 225 | } 226 | 227 | // Adding entry to table and pointing argument to entry 228 | ce.num_uses = 0; 229 | gQueryCache[t_containerid] = ce; 230 | 231 | *t_dqr = &(gQueryCache[t_containerid].content); 232 | 233 | // Cleaning up 234 | curl_easy_cleanup(curl_handle); 235 | free(chunk.memory); 236 | curl_global_cleanup(); 237 | 238 | return(rc); 239 | } 240 | 241 | /* **************************************************** */ 242 | 243 | int ContainerInfo::containerd_update_query_cache (char* t_containerid, 244 | struct container_info **t_dqr) { 245 | FILE *fp; 246 | char *ns; 247 | char comm[256]; 248 | char buff[LINE_MAX]; 249 | std::string cgroupid(t_containerid); 250 | std::set::iterator s; 251 | std::string result; 252 | struct cache_entry ce; 253 | 254 | #ifdef DEBUG 255 | printf("[%s:%u] %s()\n", __FILE__, __LINE__, __FUNCTION__); 256 | #endif 257 | 258 | (*t_dqr) = NULL; 259 | 260 | try { 261 | regex_match(cgroupid, std::regex("^([0-9a-zA-Z\\.\\_\\-])*$")); 262 | } catch (std::regex_error& e) { 263 | #ifdef DEBUG 264 | printf("[%s:%u] %s()\n", __FILE__, __LINE__, __FUNCTION__); 265 | #endif 266 | return(-1); 267 | } 268 | 269 | for(s = namespaces.begin(); s != namespaces.end(); ++s) { 270 | ns = (char*) (*s).c_str(); 271 | 272 | /* ***** ***** SANITIZE THE INPUT ***** ***** */ 273 | // The container id and namespace MUST be sanitized 274 | // otherwise there's a risk of command injection 275 | // cgroupid has been already sanitized 276 | /* ***** ***** ****************** ***** ***** */ 277 | try { 278 | regex_match(*s, std::regex("^([0-9a-zA-Z\\.\\_\\-])*$")); 279 | } catch (std::regex_error& e) { 280 | #ifdef DEBUG 281 | printf("[%s:%u] %s()\n", __FILE__, __LINE__, __FUNCTION__); 282 | #endif 283 | return(-1); 284 | } 285 | 286 | snprintf(comm, sizeof(comm), "%s --namespace=%s c info %s 2>/dev/null", 287 | ctr_path, ns, t_containerid); 288 | 289 | #ifdef DEBUG 290 | printf("[%s:%u] %s\n", __FILE__, __LINE__, comm); 291 | #endif 292 | 293 | // piping to the command 294 | fp = popen(comm, "r"); 295 | if(fp == NULL) { 296 | #ifdef DEBUG 297 | printf("containerd interaction failed \n"); 298 | #endif 299 | return -1; 300 | } 301 | 302 | while(fgets(buff, sizeof(buff), fp)) { 303 | result += buff; 304 | } 305 | 306 | pclose(fp); 307 | 308 | // handling json 309 | if(parse_response(result.c_str(), result.size(), &ce.content) == 0) 310 | break; 311 | } 312 | 313 | ce.num_uses = 0; 314 | gQueryCache[t_containerid] = ce; 315 | *t_dqr = &(gQueryCache[t_containerid].content); 316 | 317 | return(0); 318 | } 319 | 320 | /* **************************************************** */ 321 | 322 | int ContainerInfo::update_namespaces() { 323 | FILE *fp; 324 | int i = 0; 325 | char ns[LINE_MAX]; 326 | char buf[90]; 327 | 328 | #ifdef DEBUG 329 | printf("[%s:%u] %s()\n", __FILE__, __LINE__, __FUNCTION__); 330 | #endif 331 | 332 | namespaces.clear(); 333 | 334 | snprintf(buf, sizeof(buf), "%s namespace ls 2>/dev/null", ctr_path); 335 | 336 | #ifdef DEBUG 337 | printf("[%s:%u] %s\n", __FILE__, __LINE__, buf); 338 | #endif 339 | 340 | if((fp = popen(buf, "r")) == NULL) 341 | return(-1); 342 | 343 | while(fgets(ns, sizeof(ns), fp) != NULL) { 344 | char *space; 345 | 346 | if(i == 0) /* Fs line is the title */ { 347 | i++; 348 | continue; 349 | } 350 | 351 | if((space = strchr(ns, ' ')) != NULL) 352 | space[0] = '\0'; 353 | 354 | #ifdef DEBUG 355 | printf("[%s:%u] Found namespace %s\n", __FILE__, __LINE__, space); 356 | #endif 357 | 358 | namespaces.insert(ns); 359 | } 360 | 361 | pclose(fp); 362 | return(0); 363 | } 364 | 365 | /* **************************************************** */ 366 | 367 | /* *********************************** */ 368 | // ===== ===== CACHE CHECK ===== ===== // 369 | /* *********************************** */ 370 | /* 371 | * container_id_find_in_cache - check if containers info have been cached 372 | * returns -1 if the query has not been cached 0 if some info are available 373 | * 1 for dummy keys 374 | */ 375 | int ContainerInfo::container_id_find_in_cache(char* t_containerid, 376 | struct container_info **t_dqs) { 377 | std::string cgroupid(t_containerid); 378 | std::unordered_map::iterator res = gQueryCache.find(cgroupid); 379 | 380 | if(res != gQueryCache.end()) { 381 | *t_dqs = &(res->second.content); 382 | res->second.num_uses++; 383 | 384 | return(0); 385 | } else 386 | return(-1); 387 | } 388 | 389 | /* **************************************************** */ 390 | 391 | void ContainerInfo::clean_cache() { 392 | std::unordered_map::iterator it; 393 | 394 | for(it = gQueryCache.begin(); it != gQueryCache.end();) { 395 | struct cache_entry ce = it->second; 396 | 397 | if(ce.num_uses == 0) 398 | it = gQueryCache.erase(it); 399 | else { 400 | ce.num_uses = 0; 401 | it++; 402 | } 403 | } 404 | } 405 | 406 | /* **************************************************** */ 407 | 408 | int ContainerInfo::get_container_info(char* t_containerId, struct container_info **t_dqr) { 409 | int res; 410 | static time_t last = time(NULL); 411 | time_t now; 412 | 413 | #ifdef DEBUG 414 | printf("[%s:%u] %s(%s)\n", __FILE__, __LINE__, __FUNCTION__, t_containerId); 415 | #endif 416 | 417 | if((t_containerId[0] == '\0') || (strcmp(t_containerId, "/") == 0)) 418 | return(-1); 419 | 420 | now = time(NULL); 421 | 422 | if(difftime(now, last) > REFRESH_TIME /* Seconds */ ) { 423 | int rc; 424 | clean_cache(); 425 | namespaces.clear(); 426 | rc = update_namespaces(); 427 | last = now; 428 | 429 | if(rc == -1) return(rc); 430 | } 431 | 432 | res = container_id_find_in_cache(t_containerId, t_dqr); 433 | if(res != -1) /* Item is cached */ 434 | return(res); 435 | 436 | // Dockerd 437 | res = dockerd_update_query_cache(t_containerId, t_dqr); 438 | 439 | // Containerd interaction 440 | if(res != 0) 441 | res = containerd_update_query_cache(t_containerId, t_dqr); 442 | 443 | return(res); 444 | } 445 | -------------------------------------------------------------------------------- /container_info.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #ifndef __CONTAINER_INFO_HPP__ 22 | #define __CONTAINER_INFO_HPP__ 1 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | struct container_info { 32 | struct { 33 | std::string name; 34 | } docker; 35 | 36 | struct { 37 | std::string name, pod, ns; 38 | } kube; 39 | }; 40 | 41 | // Cache cleaning and namespace update interval in seconds 42 | #define REFRESH_TIME 30 43 | 44 | struct cache_entry { 45 | int num_uses; 46 | struct container_info content; 47 | }; 48 | 49 | class ContainerInfo { 50 | private: 51 | // Cache where to store the queries results 52 | std::unordered_map gQueryCache; 53 | 54 | // Namespace cache 55 | std::set namespaces; 56 | 57 | char ctr_path[64]; 58 | 59 | /* 60 | * Gather namespaces from ctr or docker-containerd-ctr 61 | */ 62 | int update_namespaces(); 63 | 64 | /* 65 | * Removes from the cache all those entries that have 66 | * been accessed less than MIN_VISITS times 67 | */ 68 | void clean_cache(); 69 | 70 | /* 71 | * Create the entry if it does not exist, otherwise updates the content 72 | */ 73 | void update_cache_entry(char* t_containerid, struct cache_entry *t_dqr); 74 | 75 | /* parse_response - fill a container_info data structure with the information returned by a query to 76 | * the docker daemon 77 | * buff: if NULL a dummy entry will be created (not added to the cache) 78 | * return 0 if no error occurred -1 otherwise 79 | */ 80 | int parse_response(const char* buff, ssize_t buffsize, struct container_info *entry); 81 | 82 | /* 83 | * update_query_cache - query to docker api from docker socket (/var/run/docker.sock) and caches the result. 84 | * @t_containerid: full length cgroup id 85 | * @t_dqr: filled with the information gathered if no error occurred 86 | * returns 0 if no error occurres and container info has been found, otherwise -1 87 | * note: the same operation can be done using 88 | * `$ curl --unix-socket /var/run/docker.sock http://localhost/containers//json` 89 | */ 90 | int dockerd_update_query_cache(char* t_containerid, struct container_info **t_dqr); 91 | 92 | /* 93 | * update_query_cache - exec ctr (i.e. containerd cli) to retrieve information 94 | * concerning the container. If ctr is not available it will be tried: docker-containerd-ctr 95 | * @t_containerid: full length cgroup id 96 | * @t_ns: target namespace 97 | * @t_dqr: filled with the information gathered if no error occurred 98 | * returns 0 if no error occurres and container info has been found, otherwise -1 99 | * note: the same operation can be done using 100 | * `$ sudo ctr --namespace= containers info ` 101 | */ 102 | int containerd_update_query_cache (char* t_containerid, struct container_info **t_dqr); 103 | 104 | /* *********************************** */ 105 | // ===== ===== CACHE CHECK ===== ===== // 106 | /* *********************************** */ 107 | /* 108 | * container_id_cached - check if containers info have been cached 109 | * and if some info are available stores them in *t_dqs 110 | * @t_containerid: docker container ID 111 | * @t_dqs: will point to the cache entry if no error occurs (returns != -1) 112 | * returns 0 if the cache contains information concerning the container 113 | * -1 if there no entry corresponding to the ID provided. 1 if 114 | * there is an entry associated with the ID but there isn't 115 | * information available 116 | */ 117 | int container_id_find_in_cache(char *t_containerid, struct container_info **t_dqs); 118 | 119 | public: 120 | ContainerInfo(); 121 | ~ContainerInfo(); 122 | 123 | /* 124 | * Retrieves container information 125 | * @t_containerId: container ID 126 | * @t_dqs: will point to the container informations if no error occurs (returns != -1) 127 | * returns 0 if some info has been found, -1 otherwise 128 | */ 129 | int get_container_info(char* t_containerId, container_info **t_dqr); 130 | }; 131 | 132 | #endif /* __CONTAINER_INFO_HPP__ */ 133 | -------------------------------------------------------------------------------- /ebpf_flow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #include "config.h" 22 | 23 | #include "ebpf_flow.h" 24 | #include "container_info.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "ebpflow.ebpf.enc" 38 | 39 | static ContainerInfo cinfo; 40 | 41 | /* ******************************************* */ 42 | 43 | std::string b64decode(const void* data, const size_t len) { 44 | unsigned char* p = (unsigned char*)data; 45 | int pad = len > 0 && (len % 4 || p[len - 1] == '='); 46 | const size_t L = ((len + 3) / 4 - pad) * 4; 47 | const size_t strsize = L / 4 * 3 + pad; 48 | if(strsize <= 0) 49 | return std::string(); 50 | 51 | std::string str(strsize, '\0'); 52 | const int B64index[256] = { 53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56 | 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 57 | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 58 | 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 59 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; 60 | 61 | for(size_t i = 0, j = 0; i < L; i += 4) { 62 | int n = B64index[p[i]] << 18 | 63 | B64index[p[i + 1]] << 12 | 64 | B64index[p[i + 2]] << 6 | 65 | B64index[p[i + 3]]; 66 | str[j++] = n >> 16; 67 | str[j++] = n >> 8 & 0xFF; 68 | str[j++] = n & 0xFF; 69 | } 70 | 71 | if(pad) { 72 | int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; 73 | str[str.size() - 1] = n >> 16; 74 | 75 | if(len > L + 2 && p[L + 2] != '=') { 76 | n |= B64index[p[L + 2]] << 6; 77 | str.push_back(n >> 8 & 0xFF); 78 | } 79 | } 80 | 81 | return str; 82 | } 83 | 84 | /* ******************************************* */ 85 | /* ******************************************* */ 86 | 87 | static int attachEBPFTracepoint(ebpf::BPF *bpf, 88 | const char *tracepoint, const char *probe_func) { 89 | ebpf::StatusTuple rc = bpf->attach_tracepoint(tracepoint, probe_func); 90 | 91 | #ifdef DEBUG 92 | if(rc.code() != 0) 93 | printf("ERROR: %s/%s: %d/%s\n", tracepoint, probe_func, rc.code(), rc.msg().c_str()); 94 | #endif 95 | 96 | return(rc.code()); 97 | } 98 | 99 | /* ******************************************* */ 100 | 101 | static int attachEBPFKernelProbe(ebpf::BPF *bpf, const char *queue_name, 102 | const char *entry_point, 103 | bpf_probe_attach_type attach_type) { 104 | int rc = bpf->attach_kprobe(queue_name, entry_point, 105 | #if defined HAVE_NEW_EBPF || LINUX_VERSION_CODE <= KERNEL_VERSION(3,15,0) 106 | 0, 107 | #endif 108 | attach_type).code(); 109 | 110 | #ifdef DEBUG 111 | if(rc != 0) 112 | printf("ERROR: %s/%s: %d\n", queue_name, entry_point, rc); 113 | #endif 114 | 115 | return(rc); 116 | } 117 | 118 | /* ******************************************* */ 119 | 120 | extern "C" { 121 | void* init_ebpf_flow(void *priv_ptr, eBPFHandler ebpfHandler, 122 | ebpfRetCode *rc, u_int16_t flags) { 123 | ebpf::BPF *bpf = NULL; 124 | std::string code = b64decode(ebpf_code, strlen(ebpf_code)); 125 | ebpf::StatusTuple open_res(0); 126 | 127 | // Default value is 0 128 | flags = (flags == 0) ? 0xFFFF : flags; 129 | 130 | if(code == "") { 131 | *rc = ebpf_unable_to_load_kernel_probe; 132 | goto init_failed; 133 | } 134 | 135 | try { 136 | bpf = new ebpf::BPF; 137 | } catch(std::bad_alloc& ba) { 138 | *rc = ebpf_out_of_memory; 139 | goto init_failed; 140 | } 141 | 142 | if(bpf->init(code).code() != 0) { 143 | *rc = ebpf_initialization_failed; 144 | goto init_failed; 145 | } 146 | 147 | // attaching probes ----- // 148 | if((flags & LIBEBPF_TCP) && (flags & LIBEBPF_OUTCOMING)) { 149 | if(attachEBPFKernelProbe(bpf,"tcp_v4_connect", 150 | "trace_connect_entry", BPF_PROBE_ENTRY) 151 | || attachEBPFKernelProbe(bpf, "tcp_v4_connect", 152 | "trace_connect_v4_return", BPF_PROBE_RETURN) 153 | || attachEBPFKernelProbe(bpf, "tcp_v6_connect", 154 | "trace_connect_entry", BPF_PROBE_ENTRY) 155 | || attachEBPFKernelProbe(bpf, "tcp_v6_connect", 156 | "trace_connect_v6_return", BPF_PROBE_RETURN) 157 | ) { 158 | *rc = ebpf_kprobe_attach_error; 159 | goto init_failed; 160 | } 161 | } 162 | 163 | if((flags & LIBEBPF_TCP) && (flags & LIBEBPF_INCOMING)) { 164 | if(attachEBPFKernelProbe(bpf, "inet_csk_accept", 165 | "trace_tcp_accept", BPF_PROBE_RETURN)) { 166 | *rc = ebpf_kprobe_attach_error; 167 | goto init_failed; 168 | } 169 | } 170 | 171 | if((flags & LIBEBPF_UDP) && (flags & LIBEBPF_OUTCOMING)) { 172 | if(attachEBPFTracepoint(bpf, "net:net_dev_queue", 173 | "trace_netif_tx_entry")) { 174 | *rc = ebpf_kprobe_attach_error; 175 | goto init_failed; 176 | } 177 | } 178 | 179 | if((flags & LIBEBPF_UDP) && (flags & LIBEBPF_INCOMING)) { 180 | if(attachEBPFTracepoint(bpf, "net:netif_receive_skb", 181 | "trace_netif_rx_entry")) { 182 | *rc = ebpf_kprobe_attach_error; 183 | goto init_failed; 184 | } 185 | } 186 | 187 | if(flags & LIBEBPF_TCP_CLOSE) { 188 | if(attachEBPFKernelProbe(bpf, "tcp_set_state", 189 | "trace_tcp_set_state", BPF_PROBE_ENTRY)) { 190 | *rc = ebpf_kprobe_attach_error; 191 | goto init_failed; 192 | } 193 | } 194 | 195 | if(flags & LIBEBPF_TCP_RETR) { 196 | if(attachEBPFKernelProbe(bpf, "tcp_retransmit_skb", 197 | "trace_tcp_retransmit_skb", BPF_PROBE_ENTRY)) { 198 | *rc = ebpf_kprobe_attach_error; 199 | goto init_failed; 200 | } 201 | } 202 | 203 | // opening output buffer ----- // 204 | open_res = bpf->open_perf_buffer("ebpf_events", ebpfHandler, NULL, (void*)priv_ptr); 205 | if(open_res.code() != 0) { *rc = ebpf_events_open_error; goto init_failed; } 206 | 207 | *rc = ebpf_no_error; 208 | return((void*)bpf); 209 | 210 | init_failed: 211 | if(bpf) delete bpf; 212 | return(NULL); 213 | }; 214 | 215 | /* ******************************************* */ 216 | 217 | static int is_kernel_thread(__u32 pid) { 218 | char pathname[64]; 219 | struct stat statbuf; 220 | 221 | snprintf(pathname, sizeof(pathname), "/proc/%u", pid); 222 | 223 | /* The process exists... */ 224 | if(stat(pathname, &statbuf) == 0) { 225 | snprintf(pathname, sizeof(pathname), "/proc/%u/exe", pid); 226 | 227 | if(stat(pathname, &statbuf) == -1) 228 | return(1); /* It looks like a kernel thread */ 229 | } 230 | 231 | return(0); 232 | } 233 | 234 | /* ******************************************* */ 235 | 236 | static void check_pid(struct taskInfo *task) { 237 | if((task->pid != 0) && is_kernel_thread(task->pid)) 238 | memset(task, 0, sizeof(struct taskInfo)); 239 | } 240 | 241 | /* ******************************************* */ 242 | 243 | /* Fill cmdline arguments */ 244 | static void fill_exe_cmdline(struct taskInfo *task) { 245 | if(task->pid != 0) { 246 | FILE *f; 247 | char what[256]; 248 | 249 | sprintf(what, "/proc/%u/cmdline", task->pid); 250 | 251 | if((f = fopen(what, "r")) != NULL) { 252 | char *line, cmdbuf[512] = { '\0' }; 253 | 254 | if((line = fgets(cmdbuf, sizeof(cmdbuf), f)) != NULL) { 255 | char *delimiter = strchr(line, '\0'); 256 | 257 | if(delimiter[1] != '\0') 258 | task->cmdline = strdup(&delimiter[1]); 259 | } 260 | 261 | fclose(f); 262 | } 263 | } 264 | } 265 | 266 | /* ******************************************* */ 267 | 268 | void fill_exe_takinfo(struct taskInfo *task) { 269 | task->cmdline = NULL, task->full_task_path = NULL; 270 | 271 | if(task->pid != 0) { 272 | char what[256], sym[256]; 273 | int l; 274 | 275 | /* Fill full path */ 276 | snprintf(what, sizeof(what), "/proc/%u/exe", task->pid); 277 | if((l = readlink(what, sym, sizeof(sym))) != -1) { 278 | char *space; 279 | 280 | sym[l] = '\0'; 281 | 282 | if((space = strchr(sym, ' ')) != NULL) { 283 | if(space[1] == '(') /* (deleted) */ 284 | space[0] = '\0'; 285 | } 286 | 287 | task->full_task_path = strdup(sym); 288 | 289 | fill_exe_cmdline(task); 290 | } 291 | } 292 | } 293 | 294 | /* ******************************************* */ 295 | 296 | void ebpf_preprocess_event(eBPFevent *event) { 297 | struct container_info *container_info; 298 | 299 | gettimeofday(&event->event_time, NULL); 300 | check_pid(&event->proc), check_pid(&event->father); 301 | 302 | fill_exe_takinfo(&event->proc); 303 | fill_exe_takinfo(&event->father); 304 | 305 | if((event->container_id[0] == '\0') 306 | || ((event->container_id[0] == '/') && (event->container_id[1] == '\0'))) 307 | event->container_id[0] = '\0'; 308 | else { 309 | event->docker.name = event->kube.name = event->kube.pod = event->kube.ns = NULL; 310 | 311 | // Docker names cgroup as follows: "docker-.scope" 312 | if(strstr(event->container_id, "docker-") != NULL) { 313 | event->container_id[71] = '\0'; 314 | memmove(event->container_id, event->container_id+7, strlen(event->container_id)-6); 315 | } 316 | 317 | if(cinfo.get_container_info(event->container_id, &container_info) == 0) { 318 | if(container_info->docker.name[0] != '\0') /* Docker info available */ 319 | event->docker.name = strdup(container_info->docker.name.c_str()); 320 | 321 | if(container_info->kube.name[0] != '\0') /* Kubernetes info available */ { 322 | event->kube.name = strdup(container_info->kube.name.c_str()); 323 | event->kube.pod = strdup(container_info->kube.pod.c_str()); 324 | event->kube.ns = strdup(container_info->kube.ns.c_str()); 325 | } 326 | } 327 | } 328 | } 329 | 330 | /* ******************************************* */ 331 | 332 | void ebpf_free_event(eBPFevent *event) { 333 | if(event->proc.full_task_path != NULL) free(event->proc.full_task_path); 334 | if(event->father.full_task_path != NULL) free(event->father.full_task_path); 335 | if(event->proc.cmdline != NULL) free(event->proc.cmdline); 336 | if(event->father.cmdline != NULL) free(event->father.cmdline); 337 | 338 | if(event->docker.name != NULL) free(event->docker.name); 339 | if(event->kube.name != NULL) free(event->kube.name); 340 | if(event->kube.pod != NULL) free(event->kube.pod); 341 | if(event->kube.ns != NULL) free(event->kube.ns); 342 | } 343 | 344 | /* ******************************************* */ 345 | 346 | void term_ebpf_flow(void *ebpfHook) { 347 | ebpf::BPF *bpf = (ebpf::BPF*)ebpfHook; 348 | 349 | if(bpf != NULL) 350 | delete bpf; 351 | } 352 | 353 | /* ******************************************* */ 354 | 355 | int ebpf_poll_event(void *ebpfHook, u_int ms_timeout) { 356 | ebpf::BPF *bpf = (ebpf::BPF*)ebpfHook; 357 | 358 | /* NOTE: some library versions do not return a value [TODO] */ 359 | /* int rc = */ bpf->poll_perf_buffer("ebpf_events", ms_timeout); 360 | 361 | return(1); 362 | } 363 | 364 | /* ******************************************* */ 365 | 366 | const char* ebpf_print_error(ebpfRetCode rc) { 367 | switch(rc) { 368 | case ebpf_no_error: 369 | return("ebpf_no_error"); 370 | 371 | case ebpf_initialization_failed: 372 | return("ebpf_initialization_failed"); 373 | 374 | case ebpf_unable_to_load_kernel_probe: 375 | return("ebpf_unable_to_load_kernel_probe"); 376 | 377 | case ebpf_out_of_memory: 378 | return("ebpf_out_of_memory"); 379 | 380 | case ebpf_kprobe_attach_error: 381 | return("ebpf_kprobe_attach_error"); 382 | 383 | case ebpf_events_open_error: 384 | return("ebpf_events_open_error"); 385 | } 386 | 387 | return("Unknown error"); 388 | } 389 | 390 | /* ******************************************* */ 391 | 392 | void* ebpf_get_cinfo_handler() { return((void*)&cinfo); } 393 | 394 | /* ******************************************* */ 395 | 396 | const char* ebpf_flow_version() { 397 | return(EBPF_FLOW_VERSION); 398 | } 399 | 400 | }; 401 | -------------------------------------------------------------------------------- /ebpf_flow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #ifndef __EBPF_FLOW_H__ 22 | #define __EBPF_FLOW_H__ 1 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #ifdef __linux__ 29 | #include 30 | #include 31 | #endif 32 | #include 33 | 34 | #define ktime_t u_int64_t 35 | #define u8 u_int8_t 36 | #define u16 u_int16_t 37 | #define u32 u_int32_t 38 | #define u64 u_int64_t 39 | 40 | #include "ebpf_types.h" 41 | 42 | /* ******************************************* */ 43 | 44 | typedef enum { 45 | ebpf_no_error = 0, 46 | ebpf_initialization_failed, 47 | ebpf_unable_to_load_kernel_probe, 48 | ebpf_out_of_memory, 49 | ebpf_kprobe_attach_error, 50 | ebpf_events_open_error, 51 | } ebpfRetCode; 52 | 53 | /* 54 | * Supported flags to filter events when initializating libebpfflow 55 | * Combinations of this flags allow selecte events to be to captured 56 | */ 57 | typedef enum { 58 | LIBEBPF_TCP = 1 << 0, 59 | LIBEBPF_UDP = 1 << 1, 60 | LIBEBPF_INCOMING = 1 << 2, 61 | LIBEBPF_OUTCOMING = 1 << 3, 62 | LIBEBPF_TCP_CLOSE = 1 << 4, 63 | LIBEBPF_TCP_RETR = 1 << 5, 64 | } libebpflow_flag; 65 | 66 | /* ******************************************* */ 67 | 68 | #ifdef __cplusplus 69 | extern "C" { 70 | #endif // __cplusplus 71 | 72 | typedef void (*eBPFHandler)(void* t_bpfctx, void* t_data, int t_datasize); 73 | 74 | /* 75 | * init_ebpf_flow - Initializes the library with a target event handler 76 | * @ebpfHandler: the function used to handle events 77 | * @rc: pointer to the variable in which to store the return code 78 | * @flags: restrict the number of events to generate by 79 | * not tracing certain functions. Use default (i.e. 0 or 0xFFFF) to capture 80 | * all events. Supported events are combinations of libebpflow_flag enum type 81 | * returns a pointer to an ebpf::BPF object upon success NULL otherwise 82 | */ 83 | void* init_ebpf_flow(void *priv_ptr, eBPFHandler ebpfHandler, 84 | ebpfRetCode *rc, 85 | u_int16_t flags /* default 0 to capture all events */); 86 | 87 | /* 88 | * term_ebpf_flow - Cleans the resources used by the library 89 | * @ebpfHook: a pointer to an ebpf::BPF, that is the one returned by init_ebpf_flow 90 | */ 91 | void term_ebpf_flow(void *ebpfHook); 92 | 93 | /* 94 | * ebpf_poll_event: Pools an event from an ebpf::BPF object 95 | * @ms_timeout: maximum time to wait for an event 96 | * @ebpfHook: reference to the result of init_ebpf_flow invocation 97 | * return 1 if an event has been processed, 0 in case of timeout. 98 | */ 99 | int ebpf_poll_event(void *ebpfHook, u_int ms_timeout); 100 | 101 | /* 102 | * Collect further information wrt the one contained in an eBPF event 103 | */ 104 | void ebpf_preprocess_event(eBPFevent *event); 105 | 106 | const char* ebpf_print_error(ebpfRetCode rc); 107 | 108 | /* 109 | * Cleans the resources used by an eBPFevent data structure 110 | */ 111 | void ebpf_free_event(eBPFevent *event); 112 | 113 | 114 | /* ******************************************* */ 115 | /* 116 | * Returns the handler used by this library to retrieve container information 117 | * to be casted to ContainerInfo* to avoid mixinc C with C++ 118 | */ 119 | void* ebpf_get_cinfo_handler(); 120 | 121 | const char* ebpf_flow_version(); 122 | 123 | /* ******************************************* */ 124 | /* Helper */ 125 | void fill_exe_takinfo(struct taskInfo *task); 126 | 127 | #ifdef __cplusplus 128 | }; 129 | #endif // __cplusplus 130 | 131 | #endif /* __EBPF_FLOW_H__ */ 132 | -------------------------------------------------------------------------------- /ebpf_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #define CONTAINER_ID_LEN 64 /* 128 */ // max is in dcache.h > DNAME_INLINE_LEN 22 | 23 | #define COMMAND_LEN 16 // defined in sched.h 24 | 25 | /* 26 | * Events types are forged as follows: 27 | * I_digit (=1): init events (e.g. connection creation) 28 | * (=2): update events on existing connection 29 | * (=3): connection closing 30 | * (=5): operation failed 31 | * II_digit (=0): tcp events 32 | * (=1): udp events 33 | * III_digit: discriminate the single event 34 | */ 35 | typedef enum { 36 | eTCP_ACPT = 100, 37 | eTCP_CONN = 101, 38 | eTCP_RETR = 200, 39 | eUDP_RECV = 210, 40 | eUDP_SEND = 211, 41 | eTCP_CLOSE = 300, 42 | eTCP_CONN_FAIL = 500, 43 | } event_type; 44 | 45 | struct taskInfo { 46 | u32 pid; /* Process Id */ 47 | u32 tid; /* Thread Id */ 48 | u32 uid; /* User Id */ 49 | u32 gid; /* Group Id */ 50 | char task[COMMAND_LEN]; 51 | char *full_task_path, *cmdline; 52 | }; 53 | 54 | // separate data structs for ipv4 and ipv6 55 | struct ipv4_addr_t { 56 | u64 saddr; 57 | u64 daddr; 58 | }; 59 | 60 | struct ipv6_addr_t { 61 | unsigned __int128 saddr; 62 | unsigned __int128 daddr; 63 | }; 64 | 65 | #ifndef IFNAMSIZ 66 | #define IFNAMSIZ 16 67 | #endif 68 | 69 | typedef struct { 70 | ktime_t ktime; 71 | char ifname[IFNAMSIZ]; 72 | struct timeval event_time; 73 | u_int8_t ip_version, sent_packet; 74 | u16 etype; 75 | 76 | union { 77 | struct ipv4_addr_t v4; 78 | struct ipv6_addr_t v6; 79 | } addr; 80 | 81 | u8 proto; 82 | u16 sport, dport; 83 | u32 latency_usec; 84 | u16 retransmissions; 85 | 86 | struct taskInfo proc, father; 87 | char container_id[CONTAINER_ID_LEN]; 88 | 89 | struct { 90 | char *name; 91 | } docker; 92 | 93 | struct { 94 | char *name; 95 | char *pod; 96 | char *ns; 97 | } kube; 98 | } eBPFevent; 99 | 100 | -------------------------------------------------------------------------------- /ebpflow_code.ebpf: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | 22 | struct udp_info { 23 | struct taskInfo proc, father; 24 | char container_id[CONTAINER_ID_LEN]; 25 | }; 26 | BPF_HASH(udpinfo, u16, struct udp_info); 27 | 28 | static void fill_ifname(eBPFevent *ev, struct sock *sk); 29 | 30 | /* ******************************************* */ 31 | 32 | static void update_socket_hash(struct pt_regs *ctx, struct sock *sk) { 33 | u32 tid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF; 34 | struct sock_stats s = { .sk = sk, .ts = bpf_ktime_get_ns() }; 35 | 36 | // stash the sock ptr for lookup on returns 37 | currsock.update(&tid, &s); 38 | }; 39 | 40 | /* ******************************************* */ 41 | 42 | int trace_connect_entry(struct pt_regs *ctx, struct sock *sk) { 43 | update_socket_hash(ctx, sk); 44 | 45 | // bpf_override_return(ctx, -ENOMEM); 46 | return(0); 47 | }; 48 | 49 | /* ******************************************* */ 50 | 51 | static void fill_father_task_info(struct taskInfo *task) { 52 | // Parent basic info ----- // 53 | struct task_struct *t = (struct task_struct *)bpf_get_current_task(); 54 | struct task_struct *parent; 55 | struct cred *fcredential; 56 | 57 | // Grabbing father pointer 58 | // bpf_probe_read(&parent, sizeof(struct task_struct *), &t->real_parent); 59 | parent = t->real_parent; 60 | 61 | // Reading father credential 62 | // bpf_probe_read(&fcredential, sizeof(struct cred *), &parent->real_cred); 63 | fcredential = (struct cred *)(parent->real_cred); 64 | 65 | task->pid = (u32)parent->pid; 66 | task->uid = (u32)fcredential->uid.val; 67 | task->gid = (u32)fcredential->gid.val; 68 | 69 | if(task->pid == 0) 70 | task->task[0] = '\0'; 71 | else 72 | bpf_probe_read(&task->task, sizeof(task->task), parent->comm); 73 | } 74 | 75 | /* ******************************************* */ 76 | 77 | static void fill_container_id(char *container_id) { 78 | struct task_struct *curr_task; 79 | struct css_set *css; 80 | struct cgroup_subsys_state *sbs; 81 | struct cgroup *cg; 82 | struct kernfs_node *knode, *pknode; 83 | char *name; 84 | int name_shift = 0; 85 | 86 | // Initializing to root cgroup 87 | memcpy(container_id, "/\0", 2); 88 | 89 | curr_task = (struct task_struct *) bpf_get_current_task(); 90 | css = curr_task->cgroups; 91 | bpf_probe_read(&sbs, sizeof(void *), &css->subsys[0]); 92 | bpf_probe_read(&cg, sizeof(void *), &sbs->cgroup); 93 | 94 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(3,15,0) 95 | bpf_probe_read(&name, sizeof(void *), &cg->name); 96 | // Docker name cgroup as follows: "docker-" 97 | bpf_probe_read(container_id, CONTAINER_ID_LEN, name+sizeof(struct cgroup_name)); 98 | container_id[CONTAINER_ID_LEN-1] = '\0'; 99 | #else 100 | // Reading fspath 101 | bpf_probe_read(&knode, sizeof(void *), &cg->kn); 102 | bpf_probe_read(&pknode, sizeof(void *), &knode->parent); 103 | 104 | if(pknode != NULL) { 105 | char *aus; 106 | 107 | bpf_probe_read(&aus, sizeof(void *), &knode->name); 108 | bpf_probe_read_str(container_id, CONTAINER_ID_LEN, aus); 109 | } 110 | #endif 111 | } 112 | 113 | /* ******************************************* */ 114 | 115 | static void fill_task_info(char *container_id, struct taskInfo *task, struct taskInfo *father) { 116 | struct task_struct *curr_task = (struct task_struct *)bpf_get_current_task(); 117 | u64 tgid = bpf_get_current_pid_tgid(); 118 | u64 ugid = bpf_get_current_uid_gid(); 119 | u32 pid = tgid & 0xFFFFFFFF, tid = (tgid >> 32) & 0xFFFFFFFF; 120 | u32 uid = ugid & 0xFFFFFFFF, gid = (ugid >> 32) & 0xFFFFFFFF; 121 | 122 | task->pid = pid; 123 | task->tid = tid; 124 | task->uid = uid; 125 | task->gid = gid; 126 | 127 | if(pid == 0) 128 | task->task[0] = '\0'; 129 | else { 130 | bpf_get_current_comm(&task->task, sizeof(task->task)); 131 | fill_father_task_info(father); 132 | } 133 | 134 | container_id[0] = '\0'; 135 | fill_container_id(container_id); 136 | } 137 | 138 | /* ******************************************* */ 139 | 140 | static void swap_event_peers(eBPFevent *ev) { 141 | if(ev->ip_version == 4) { 142 | u32 tmp; 143 | u16 tmp16; 144 | 145 | tmp16 = ev->sport; 146 | ev->sport = ev->dport; 147 | ev->dport = tmp16; 148 | 149 | tmp = ev->addr.v4.daddr; 150 | ev->addr.v4.daddr = ev->addr.v4.saddr; 151 | ev->addr.v4.saddr = tmp; 152 | } else { 153 | u16 tmp16; 154 | unsigned __int128 tmp; 155 | 156 | tmp16 = ev->sport; 157 | ev->sport = ev->dport; 158 | ev->dport = tmp16; 159 | 160 | memcpy(&tmp, &ev->addr.v6.saddr, sizeof(tmp)); 161 | memcpy(&ev->addr.v6.saddr, &ev->addr.v6.daddr, sizeof(ev->addr.v6.saddr)); 162 | memcpy(&ev->addr.v6.daddr, &tmp, sizeof(ev->addr.v6.daddr)); 163 | } 164 | } 165 | 166 | /* ******************************************* */ 167 | 168 | static int fill_event(struct pt_regs *ctx, eBPFevent *ev, 169 | struct sock *sk, 170 | void *msg, 171 | u64 begin_ts, 172 | u8 proto, u8 swap_peers) { 173 | u16 sport = 0, dport = 0; 174 | u16 family; 175 | u64 delta; 176 | u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; 177 | u32 saddr = 0, daddr = 0; 178 | ktime_t kt = { bpf_ktime_get_ns() }; 179 | 180 | ev->sent_packet = (swap_peers == 0) ? 1 : 0; 181 | 182 | bpf_probe_read(&family, sizeof(family), &sk->__sk_common.skc_family); 183 | if((family != AF_INET) && (family != AF_INET6)) return(-1); 184 | 185 | bpf_probe_read(&sport, sizeof(u16), &sk->__sk_common.skc_num); 186 | bpf_probe_read(&dport, sizeof(u16), &sk->__sk_common.skc_dport); 187 | 188 | if(msg) { 189 | struct sockaddr_in usin; 190 | 191 | bpf_probe_read(&usin, sizeof(usin), msg); 192 | family = usin.sin_family; 193 | 194 | if(usin.sin_family == AF_INET) { 195 | daddr = usin.sin_addr.s_addr; 196 | dport = usin.sin_port; 197 | } 198 | } 199 | 200 | if(begin_ts > 0) { 201 | delta = bpf_ktime_get_ns() - begin_ts; 202 | delta /= 1000; 203 | } else 204 | delta = 0; 205 | 206 | dport = ntohs(dport); /* This has to be done all the time */ 207 | 208 | if((sport == 0) && (dport == 0)) 209 | return(-1); 210 | 211 | ev->proc.pid = pid; 212 | 213 | if(family == AF_INET) { 214 | ev->ip_version = 4; 215 | 216 | if(saddr == 0) 217 | bpf_probe_read(&ev->addr.v4.saddr, sizeof(u32), &sk->__sk_common.skc_rcv_saddr); 218 | else 219 | ev->addr.v4.saddr = saddr; 220 | 221 | if(daddr == 0) 222 | bpf_probe_read(&ev->addr.v4.daddr, sizeof(u32), &sk->__sk_common.skc_daddr); 223 | else 224 | ev->addr.v4.daddr = daddr; 225 | } else /* (family == AF_INET6) */ { 226 | ev->ip_version = 6; 227 | 228 | bpf_probe_read(&ev->addr.v6.saddr, sizeof(ev->addr.v6.saddr), 229 | sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32); 230 | bpf_probe_read(&ev->addr.v6.daddr, sizeof(ev->addr.v6.daddr), sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32); 231 | 232 | if(/* Implement in a better way */ 233 | (((ev->addr.v6.saddr) & 0xFFFFFFFF) == 0) 234 | && (((ev->addr.v6.saddr >> 32) & 0xFFFFFFFF) == 0) 235 | ) { 236 | ev->ip_version = 4; 237 | ev->proc.pid = pid; 238 | ev->addr.v4.saddr = ev->addr.v6.saddr >> 96; 239 | ev->sport = sport; 240 | ev->addr.v4.daddr = ev->addr.v6.daddr >> 96; 241 | } 242 | } 243 | 244 | ev->dport = dport; 245 | ev->sport = sport; 246 | ev->latency_usec = delta; 247 | ev->proto = proto; 248 | bpf_get_current_comm(&ev->proc.task, sizeof(ev->proc.task)); 249 | ev->proc.pid = pid; 250 | 251 | fill_task_info((char*)ev->container_id, &ev->proc, &ev->father); 252 | 253 | if(swap_peers) swap_event_peers(ev); 254 | 255 | fill_ifname(ev, sk); 256 | 257 | ev->ktime = kt; 258 | return(0); 259 | } 260 | 261 | /* ******************************************* */ 262 | 263 | static int trace_connect_return(struct pt_regs *ctx) { 264 | int ret = PT_REGS_RC(ctx); // return value 265 | struct sock_stats *s; 266 | u32 tid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF; 267 | eBPFevent event = { .etype = eTCP_CONN, .ip_version = 4 }; 268 | 269 | s = currsock.lookup(&tid); 270 | if(s == NULL) 271 | return(0); // missed entry 272 | 273 | 274 | fill_event(ctx, &event, s->sk, NULL, s->ts, IPPROTO_TCP, 0 /* don't swap */); 275 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 276 | 277 | currsock.delete(&tid); 278 | 279 | return(0); 280 | } 281 | 282 | /* ******************************************* */ 283 | 284 | int trace_connect_v4_return(struct pt_regs *ctx) { 285 | return trace_connect_return(ctx); 286 | } 287 | 288 | /* ******************************************* */ 289 | 290 | int trace_connect_v6_return(struct pt_regs *ctx) { 291 | return trace_connect_return(ctx); 292 | } 293 | 294 | /* ******************************************* */ 295 | 296 | int trace_tcp_accept(struct pt_regs *ctx) { 297 | struct sock *newsk = (struct sock *)PT_REGS_RC(ctx); 298 | 299 | if(newsk != NULL) { 300 | eBPFevent event = { .etype = eTCP_ACPT, .ip_version = 4 }; 301 | 302 | fill_event(ctx, &event, newsk, NULL, 0, IPPROTO_TCP, 1 /* swap */); 303 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 304 | } 305 | 306 | return(0); 307 | } 308 | 309 | /* ******************************************* */ 310 | 311 | // Fired when the state changes and check if the state is CLOSE 312 | int trace_tcp_set_state(struct pt_regs *ctx, struct sock *sk, int state) { 313 | unsigned char old_state; 314 | eBPFevent event = {}; 315 | 316 | if((state != TCP_CLOSE) && (state != EINPROGRESS)) 317 | return 0; 318 | else { 319 | // Reading old state 320 | // bpf_probe_read(&old_state, sizeof(unsigned char), (unsigned char*) &sk->__sk_common.skc_state); 321 | old_state = sk->__sk_common.skc_state; 322 | } 323 | 324 | fill_event(ctx, &event, sk, NULL, 0, IPPROTO_TCP, 0); 325 | 326 | // Connection refused if we move from SYN_SENT to TCP_CLOSE 327 | if (((int)old_state == TCP_SYN_SENT) && (state == TCP_CLOSE)) 328 | event.etype = eTCP_CONN_FAIL; 329 | else 330 | event.etype = eTCP_CLOSE; 331 | 332 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 333 | return 0; 334 | } 335 | 336 | /* ******************************************* */ 337 | 338 | int trace_tcp_retransmit_skb(struct pt_regs *ctx, struct sock *sk) { 339 | u32 tid = (bpf_get_current_pid_tgid() >> 32) & 0xFFFFFFFF; 340 | eBPFevent event = { .etype = eTCP_RETR, .retransmissions = 1 }; 341 | 342 | fill_event(ctx, &event, sk, NULL, 0, IPPROTO_TCP, 0); 343 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 344 | 345 | return 0; 346 | } 347 | 348 | /* *********************** UDP *************************** */ 349 | /* *********************** UDP *************************** */ 350 | /* *********************** UDP *************************** */ 351 | 352 | /* key is IPs+sport+dport, value = bpf_ktime_get_ns() */ 353 | #define BPF_LRU_HASH3(_name, _key_type, _leaf_type) BPF_TABLE("lru_hash", _key_type, _leaf_type, _name, 10240) 354 | BPF_LRU_HASH3(udpmsglru, u64, u64); 355 | 356 | /* ******************************************* */ 357 | 358 | static u8 is_cached_entry(eBPFevent *ev) { 359 | u64 hash_idx; 360 | u64 *when, now; 361 | 362 | /* NOTE: implemented asymmetric hash to make sure we see both flow directions */ 363 | if(ev->ip_version == 4) 364 | hash_idx = ev->addr.v4.saddr + ev->addr.v4.daddr + ev->sport + ev->dport + ev->proc.pid; 365 | else 366 | hash_idx = ev->addr.v6.saddr + ev->addr.v6.daddr + ev->sport + ev->dport + ev->proc.pid; 367 | 368 | when = udpmsglru.lookup(&hash_idx); 369 | now = bpf_ktime_get_ns(); 370 | 371 | if(when == NULL) { 372 | /* not found so not cached */ 373 | 374 | udpmsglru.update(&hash_idx, &now); 375 | return(0); 376 | } else { 377 | u64 diff = now - *when; 378 | 379 | if(diff > 1000000000 /* 1 sec */) { 380 | /* or it was cached more than one second ago */ 381 | udpmsglru.update(&hash_idx, &now); 382 | return(0); 383 | } 384 | 385 | return(1); 386 | } 387 | 388 | return(0); 389 | } 390 | 391 | /* ******************************************* */ 392 | /* ******************************************* */ 393 | 394 | /* https://blog.yadutaf.fr/2017/07/28/tracing-a-packet-journey-using-linux-tracepoints-perf-ebpf/ */ 395 | 396 | #define ETHERTYPE_IP 0x0800 /* IP */ 397 | #define ETHERTYPE_IPV6 0x86DD /* IP protocol version 6 */ 398 | #define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */ 399 | 400 | #define MAC_HEADER_SIZE 14; 401 | #define member_address(source_struct, source_member) \ 402 | ({ \ 403 | void* __ret; \ 404 | __ret = (void*) (((char*)source_struct) + offsetof(typeof(*source_struct), source_member)); \ 405 | __ret; \ 406 | }) 407 | #define member_read(destination, source_struct, source_member) \ 408 | do{ \ 409 | bpf_probe_read( \ 410 | destination, \ 411 | sizeof(source_struct->source_member), \ 412 | member_address(source_struct, source_member) \ 413 | ); \ 414 | } while(0) 415 | 416 | static inline int udp_packet_trace(void *ctx, struct sk_buff* skb, u_int8_t sent_packet) { 417 | // Compute MAC header address 418 | char* head; 419 | u16 mac_header; 420 | eBPFevent event = { .etype = eUDP_SEND, .sent_packet = sent_packet }; 421 | u8 offset, l4proto, ip_version; 422 | char* ip_header_address; 423 | struct udphdr *udphdr; 424 | u16 eth_proto; 425 | struct net_device *dev; 426 | 427 | member_read(&head, skb, head); 428 | member_read(&mac_header, skb, mac_header); 429 | 430 | head = head + mac_header; 431 | 432 | bpf_probe_read(ð_proto, sizeof(u16), &head[12]); 433 | 434 | // Compute IP Header address 435 | ip_header_address = head + MAC_HEADER_SIZE; 436 | 437 | // Load IP protocol version 438 | bpf_probe_read(&ip_version, sizeof(u8), ip_header_address); 439 | event.ip_version = ip_version >> 4 & 0xf; 440 | 441 | /* TODO; ADD VLAN support */ 442 | 443 | if(eth_proto == htons(ETHERTYPE_IP)) { 444 | struct iphdr iphdr; 445 | 446 | event.ip_version = 4; 447 | bpf_probe_read(&iphdr, sizeof(iphdr), ip_header_address); 448 | 449 | // Load protocol and address 450 | offset = iphdr.ihl * 4; 451 | 452 | l4proto = iphdr.protocol; 453 | 454 | // Discard non UDP traffic 455 | if(l4proto != IPPROTO_UDP) return 0; 456 | 457 | event.addr.v4.saddr = iphdr.saddr; 458 | event.addr.v4.daddr = iphdr.daddr; 459 | udphdr = (struct udphdr*)(&ip_header_address[offset]); 460 | bpf_probe_read(&event.sport, sizeof(u16), &udphdr->source); 461 | bpf_probe_read(&event.dport, sizeof(u16), &udphdr->dest); 462 | event.sport = htons(event.sport); 463 | event.dport = htons(event.dport); 464 | } else if(eth_proto == htons(ETHERTYPE_IPV6)) { 465 | // Assume no option header --> fixed size header 466 | struct ipv6hdr* ipv6hdr = (struct ipv6hdr*)ip_header_address; 467 | 468 | event.ip_version = 6; 469 | bpf_probe_read(&l4proto, sizeof(ipv6hdr->nexthdr), 470 | (char*)ipv6hdr + offsetof(struct ipv6hdr, nexthdr)); 471 | 472 | // Discard non UDP traffic 473 | if(l4proto != IPPROTO_UDP) return 0; 474 | 475 | bpf_probe_read(&event.addr.v6.saddr, sizeof(ipv6hdr->saddr), 476 | (char*)ipv6hdr + offsetof(struct ipv6hdr, saddr)); 477 | bpf_probe_read(&event.addr.v6.daddr, sizeof(ipv6hdr->daddr), 478 | (char*)ipv6hdr + offsetof(struct ipv6hdr, daddr)); 479 | offset = sizeof(*ipv6hdr); 480 | udphdr = (struct udphdr*)(&ip_header_address[offset]); 481 | bpf_probe_read(&event.sport, sizeof(u16), &udphdr->source); 482 | bpf_probe_read(&event.dport, sizeof(u16), &udphdr->dest); 483 | event.sport = htons(event.sport); 484 | event.dport = htons(event.dport); 485 | } else { 486 | #if 0 487 | event.ip_version = 6; 488 | event.sport = ntohs(eth_proto); 489 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 490 | #endif 491 | return(0); 492 | } 493 | 494 | event.proto = IPPROTO_UDP; 495 | event.latency_usec = 0; 496 | 497 | if(sent_packet) 498 | fill_task_info((char*)event.container_id, &event.proc, &event.father); 499 | else { 500 | event.container_id[0] = '\0'; 501 | memset(&event.proc, 0, sizeof(event.proc)); 502 | memset(&event.father, 0, sizeof(event.father)); 503 | } 504 | 505 | member_read(&dev, skb, dev); 506 | bpf_probe_read(&event.ifname, IFNAMSIZ, dev->name); 507 | 508 | if(!is_cached_entry(&event)) 509 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 510 | 511 | return 0; 512 | } 513 | 514 | /* ******************************************* */ 515 | 516 | static void fill_ifname(eBPFevent *ev, struct sock *sk) { 517 | struct net_device *dev; 518 | struct dst_entry *dst; 519 | 520 | member_read(&dst, sk, sk_dst_cache); 521 | member_read(&dev, dst, dev); 522 | bpf_probe_read(&ev->ifname, IFNAMSIZ, dev->name); 523 | } 524 | 525 | /* ******************************************* */ 526 | 527 | /** 528 | * Attach to Kernel Tracepoints 529 | */ 530 | /* 531 | cat /sys/kernel/debug/tracing/events/net/netif_rx/format 532 | 533 | field:unsigned short common_type;offset:0;size:2;signed:0; 534 | field:unsigned char common_flags;offset:2;size:1;signed:0; 535 | field:unsigned char common_preempt_count;offset:3;size:1;signed:0; 536 | field:int common_pid;offset:4;size:4;signed:1; 537 | 538 | field:void * skbaddr;offset:8;size:8;signed:0; 539 | field:unsigned int len;offset:16;size:4;signed:0; 540 | field:__data_loc char[] name;offset:20;size:4;signed:1; 541 | 542 | */ 543 | struct netif_rx_read_args { 544 | u64 __unused__; 545 | void * skbaddr; 546 | u_int16_t len; 547 | char name[]; 548 | }; 549 | 550 | /* 551 | cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_bind/format 552 | 553 | field:int __syscall_nr;offset:8;size:4;signed:1; 554 | field:int fd;offset:16;size:8;signed:0; 555 | field:struct sockaddr * umyaddr;offset:24;size:8;signed:0; 556 | field:int addrlen;offset:32;size:8;signed:0; 557 | */ 558 | struct sys_bind_args { 559 | u64 __unused__; 560 | int __syscall_nr; 561 | int fd; 562 | struct sockaddr *umyaddr; 563 | int addrlen; 564 | }; 565 | 566 | /* 567 | * When a packet is received the skb has not yet hit the system and thus 568 | * we don't know (yet) the process that will handle it 569 | */ 570 | int trace_netif_rx_entry(struct netif_rx_read_args *args) { 571 | return udp_packet_trace(args, (struct sk_buff*)(args->skbaddr), 0); 572 | } 573 | 574 | int trace_netif_tx_entry(struct netif_rx_read_args *args) { 575 | return udp_packet_trace(args, (struct sk_buff*)(args->skbaddr), 1); 576 | } 577 | 578 | int trace_receive_v4(struct pt_regs *ctx, struct sock *sk) { 579 | eBPFevent event = { .etype = eUDP_RECV, .ip_version = 4 }; 580 | 581 | if(fill_event(ctx, &event, sk, NULL, bpf_ktime_get_ns(), IPPROTO_UDP, 0 /* don't swap */) == 0) 582 | ebpf_events.perf_submit(ctx, &event, sizeof(eBPFevent)); 583 | 584 | return(0); 585 | } 586 | -------------------------------------------------------------------------------- /ebpflow_header.ebpf: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #pragma clang diagnostic push 30 | #pragma clang diagnostic ignored "-Wtautological-compare" 31 | #include 32 | #pragma clang diagnostic pop 33 | 34 | struct sock_stats { 35 | struct sock *sk; 36 | u64 ts; 37 | }; 38 | 39 | // hash map currsock 40 | BPF_HASH(currsock, u32, struct sock_stats); 41 | 42 | BPF_PERF_OUTPUT(ebpf_events); 43 | 44 | -------------------------------------------------------------------------------- /ebpflowexport.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-22 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include "ebpf_flow.h" 47 | 48 | 49 | void help(); 50 | static char* intoaV4(unsigned int addr, char* buf, u_short bufLen); 51 | char* intoaV6(unsigned __int128 addr, char* buf, u_short bufLen); 52 | static void handleTermination(int t_s=0); 53 | static void ebpfHandler(void* t_bpfctx, void* t_data, int t_datasize); 54 | static void zmqHandler(void* t_bpfctx, void* t_data, int t_datasize); 55 | static u_int8_t verbose = 0; 56 | static int gRUNNING = 1; 57 | void *gZMQsocket = NULL; 58 | 59 | /* **************************** */ 60 | // ===== ===== MAIN ===== ===== // 61 | /* **************************** */ 62 | 63 | struct zmq_msg_hdr { 64 | char url[32]; 65 | u_int32_t version; 66 | u_int32_t size; 67 | }; 68 | 69 | static const struct option long_opts[] = { 70 | { "retr", 0, NULL, 'r' }, 71 | { "tcpclose", 0, NULL, 'c' }, 72 | { "udp", 0, NULL, 'u' }, 73 | { "tcp", 0, NULL, 't' }, 74 | { "in", 0, NULL, 'i' }, 75 | { "out", 0, NULL, 'o' }, 76 | { "help", 0, NULL, 'h' }, 77 | { "zmq", 0, NULL, 'z' }, 78 | { "verbose", 0, NULL, 'v' }, 79 | { NULL, 0, NULL, 0 } 80 | }; 81 | 82 | int main(int argc, char **argv) { 83 | int ch; 84 | char* zmq_endpoint = NULL; 85 | void *context = NULL, *ebpf = NULL; 86 | short flags = 0; 87 | ebpfRetCode rc = ebpf_no_error; 88 | eBPFHandler handler = ebpfHandler; 89 | 90 | signal(SIGINT, handleTermination); 91 | 92 | // Argument Parsing ----- // 93 | while ((ch = getopt_long(argc, argv, "z:rcutiohv", long_opts, NULL)) != EOF) { 94 | switch (ch) { 95 | case 'u': 96 | flags |= LIBEBPF_UDP; 97 | break; 98 | case 't': 99 | flags |= LIBEBPF_TCP; 100 | break; 101 | case 'i': 102 | flags |= LIBEBPF_INCOMING; 103 | break; 104 | case 'o': 105 | flags |= LIBEBPF_OUTCOMING; 106 | break; 107 | case 'c': 108 | flags |= LIBEBPF_TCP_CLOSE; 109 | break; 110 | case 'r': 111 | flags |= LIBEBPF_TCP_RETR; 112 | break; 113 | case 'z': 114 | zmq_endpoint = strdup(optarg); 115 | handler = zmqHandler; 116 | break; 117 | case 'v': 118 | verbose = 1; 119 | break; 120 | default: 121 | help(); 122 | return 0; 123 | } 124 | } 125 | // Setting defaults 126 | if(flags == 0) 127 | flags = 0xffff; 128 | 129 | if(!(flags & LIBEBPF_INCOMING) && !(flags & LIBEBPF_OUTCOMING)) 130 | flags += LIBEBPF_INCOMING | LIBEBPF_OUTCOMING; 131 | 132 | if(!(flags & LIBEBPF_TCP) && !(flags & LIBEBPF_UDP) 133 | && !(flags & LIBEBPF_TCP_CLOSE) && !(flags & eTCP_RETR)) 134 | flags += (LIBEBPF_UDP | LIBEBPF_TCP) | LIBEBPF_TCP_CLOSE; 135 | 136 | // Checking root ----- // 137 | if(getuid() != 0) { 138 | //printf("Please run as root user \n"); 139 | help(); 140 | return 0; 141 | } 142 | 143 | printf("Welcome to ebpflowexport v.%s\n(C) 2018-22 ntop.org\n", 144 | ebpf_flow_version()); 145 | 146 | if(zmq_endpoint) { 147 | context = zmq_ctx_new(); 148 | if(context == NULL) { 149 | printf("Unable to initialize ZMQ context"); 150 | goto close; 151 | } 152 | 153 | gZMQsocket = zmq_socket(context, ZMQ_PUB); 154 | if(gZMQsocket == NULL) { 155 | printf("Unable to create ZMQ socket"); 156 | goto close; 157 | } 158 | 159 | if(zmq_endpoint[strlen(zmq_endpoint) - 1] == 'c') { 160 | /* Collector mode */ 161 | if(zmq_connect(gZMQsocket, zmq_endpoint) != 0) 162 | printf("Unable to connect to ZMQ socket %s: %s\n", zmq_endpoint, strerror(errno)); 163 | } else { 164 | /* Probe mode */ 165 | if(zmq_bind(gZMQsocket, zmq_endpoint) != 0) { 166 | printf("Unable to bind to ZMQ socket %s: %s\n", zmq_endpoint, strerror(errno)); 167 | goto close; 168 | } 169 | } 170 | } 171 | 172 | // Activating libebpflow ----- // 173 | printf("Initializing eBPF [%s]...\n", 174 | #ifdef NEW_EBF 175 | "New API" 176 | #else 177 | "Legacy API" 178 | #endif 179 | ); 180 | ebpf = init_ebpf_flow(NULL, handler, &rc, flags); 181 | 182 | if(!ebpf) { 183 | printf("Unable to initialize libebpfflow: %s\n", ebpf_print_error(rc)); 184 | goto close; 185 | } 186 | 187 | printf("eBPF initializated successfully\n"); 188 | 189 | // Polling event ----- // 190 | while(gRUNNING) { 191 | ebpf_poll_event(ebpf, 10); 192 | } 193 | 194 | // Cleaning and terminating ----- // 195 | close: 196 | if(gZMQsocket != NULL) 197 | zmq_close(gZMQsocket); 198 | if(context != NULL) 199 | zmq_ctx_destroy(context); 200 | if(zmq_endpoint) 201 | free(zmq_endpoint); 202 | 203 | term_ebpf_flow(ebpf); 204 | printf("eBPF terminated\n"); 205 | 206 | return(rc); 207 | } 208 | 209 | void help() { 210 | printf( 211 | "(C) 2018-22 - ntop.org\n" 212 | "ebpflowexport: Traffic visibility tool based on libebpfflow. By default all events will be shown\n" 213 | "Termination: CTRL-C\n" 214 | "Usage: ebpflowexport [ OPTIONS ]\n" 215 | " -h, --help display this message\n" 216 | " -v Verbose\n" 217 | " -t, --tcp TCP events\n" 218 | " -u, --udp UDP events\n" 219 | " -i, --in Incoming events (i.e. TCP accept and UDP receive)\n" 220 | " -o, --on Outgoing events (i.e. TCP connect and UDP send)\n" 221 | " -r, --retr Retransmissions events\n" 222 | " -c, --tcpclose TCP connection refused and socket close \n" 223 | " -z, --zmq Publish JSON events as a ZeroMQ publisher with envelope 'ebpfflow'\n" 224 | " Example:\n" 225 | " - ebpflowexport -z tcp://127.0.0.1:1234\n" 226 | " - ebpflowexport -z tcp://127.0.0.1:6789c [for Wireshark]\n\n" 227 | "IMPORTANT: please run this tool as root\n" 228 | ); 229 | } 230 | 231 | 232 | /* ******************************************** */ 233 | // ===== ===== IP ADDRESS TO STRING ===== ===== // 234 | /* ******************************************** */ 235 | static char* intoaV4(unsigned int addr, char* buf, u_short bufLen) { 236 | char *cp, *retStr; 237 | int n; 238 | 239 | cp = &buf[bufLen]; 240 | *--cp = '\0'; 241 | 242 | n = 4; 243 | do { 244 | u_int byte = addr & 0xff; 245 | 246 | *--cp = byte % 10 + '0'; 247 | byte /= 10; 248 | if(byte > 0) { 249 | *--cp = byte % 10 + '0'; 250 | byte /= 10; 251 | if(byte > 0) 252 | *--cp = byte + '0'; 253 | } 254 | *--cp = '.'; 255 | addr >>= 8; 256 | } while (--n > 0); 257 | 258 | /* Convert the string to lowercase */ 259 | retStr = (char*)(cp+1); 260 | 261 | return(retStr); 262 | } 263 | 264 | static char* intoaV6(void *addr, char* buf, u_short bufLen) { 265 | char *ret = (char*)inet_ntop(AF_INET6, addr, buf, bufLen); 266 | 267 | if(ret == NULL) 268 | buf[0] = '\0'; 269 | 270 | return(buf); 271 | } 272 | 273 | 274 | /* ***************************************** */ 275 | // ===== ===== CALLBACK HANDLERS ===== ===== // 276 | /* ***************************************** */ 277 | 278 | const char* event_summary(eBPFevent* e) { 279 | switch(e->etype) { 280 | case eTCP_ACPT: 281 | return("ACCEPT"); 282 | break; 283 | case eTCP_CONN: 284 | return("CONNECT"); 285 | break; 286 | case eTCP_CONN_FAIL: 287 | return("CONNECT_FAILED"); 288 | break; 289 | case eTCP_CLOSE: 290 | return("CLOSE"); 291 | break; 292 | case eTCP_RETR: 293 | return("RETRANSMIT"); 294 | break; 295 | case eUDP_SEND: 296 | return("SEND"); 297 | break; 298 | case eUDP_RECV: 299 | return("RECV"); 300 | break; 301 | } 302 | 303 | return("???"); 304 | } 305 | 306 | /* ***************************************************** */ 307 | /* ***************************************************** */ 308 | 309 | static void IPV4Handler(void* t_bpfctx, eBPFevent *e, struct ipv4_addr_t *event) { 310 | char buf1[32], buf2[32]; 311 | 312 | printf("[addr: %s:%u <-> %s:%u]", 313 | intoaV4(htonl(event->saddr), buf1, sizeof(buf1)), e->sport, 314 | intoaV4(htonl(event->daddr), buf2, sizeof(buf2)), e->dport); 315 | } 316 | 317 | static void IPV6Handler(void* t_bpfctx, eBPFevent *e, struct ipv6_addr_t *event) { 318 | char buf1[128], buf2[128]; 319 | 320 | printf("[addr: %s:%u <-> %s:%u]", 321 | intoaV6(&event->saddr, buf1, sizeof(buf1)), e->sport, 322 | intoaV6(&event->daddr, buf2, sizeof(buf2)), e->dport); 323 | } 324 | 325 | /* ***************************************************************** */ 326 | 327 | static void ebpfHandler(void* t_bpfctx, void* t_data, int t_datasize) { 328 | eBPFevent *e = (eBPFevent*)t_data; 329 | eBPFevent event; 330 | struct timespec tp; 331 | 332 | memcpy(&event, e, sizeof(eBPFevent)); /* Copy needed as ebpf_preprocess_event will modify the memory */ 333 | 334 | ebpf_preprocess_event(&event); 335 | 336 | clock_gettime(CLOCK_MONOTONIC, &tp); 337 | 338 | #if 0 339 | printf("[latency %.1f usec] ", 340 | (float)(tp.tv_nsec-(event.ktime % 1000000000))/(float)1000); 341 | #endif 342 | 343 | printf("%u.%06u ", (unsigned int)event.event_time.tv_sec, (unsigned int)event.event_time.tv_usec); 344 | 345 | printf("[%s][%s][IPv4/%s][pid/tid: %u/%u [%s%s%s], uid/gid: %u/%u][father pid/tid: %u/%u [%s%s%s], uid/gid: %u/%u]", 346 | event.ifname, event.sent_packet ? "Sent" : "Rcvd", 347 | (event.proto == IPPROTO_TCP) ? "TCP" : "UDP", 348 | event.proc.pid, event.proc.tid, 349 | (event.proc.full_task_path == NULL) ? event.proc.task : event.proc.full_task_path, 350 | (event.proc.cmdline == NULL) ? "" : " ", 351 | (event.proc.cmdline == NULL) ? "" : event.proc.cmdline, 352 | event.proc.uid, event.proc.gid, 353 | event.father.pid, event.father.tid, 354 | (event.father.full_task_path == NULL) ? event.father.task : event.father.full_task_path, 355 | (event.father.cmdline == NULL) ? "" : "", 356 | (event.father.cmdline == NULL) ? "": event.father.cmdline, 357 | event.father.uid, event.father.gid); 358 | 359 | if(event.ip_version == 4) 360 | IPV4Handler(t_bpfctx, &event, &event.addr.v4); 361 | else 362 | IPV6Handler(t_bpfctx, &event, &event.addr.v6); 363 | 364 | if(event.proto == IPPROTO_TCP) { 365 | printf("[%s]", event_summary(&event)); 366 | 367 | if(event.etype == eTCP_CONN) 368 | printf("[latency: %.2f msec]", ((float)event.latency_usec)/(float)1000); 369 | } 370 | 371 | // Container ----- /'/ 372 | if(event.container_id[0] != '\0') { 373 | printf("[containerID: %s]", event.container_id); 374 | 375 | if(event.docker.name != NULL) 376 | printf("[docker_name: %s]", event.docker.name); 377 | 378 | if(event.kube.ns) printf("[kube_name: %s]", event.kube.name); 379 | if(event.kube.pod) printf("[kube_pod: %s]", event.kube.pod); 380 | if(event.kube.ns) printf("[kube_ns: %s]", event.kube.ns); 381 | } 382 | 383 | printf("\n"); 384 | ebpf_free_event(&event); 385 | } 386 | 387 | /* ******************************************* */ 388 | 389 | void task2json(struct taskInfo *t, struct json_object **t_res) { 390 | struct json_object *j = json_object_new_object(); 391 | struct passwd *uid_info; 392 | struct group *gg; 393 | 394 | json_object_object_add(j, "PID", json_object_new_int(t->pid)); 395 | 396 | json_object_object_add(j, "UID", json_object_new_int(t->uid)); 397 | if((uid_info = getpwuid(t->pid)) != NULL) 398 | json_object_object_add(j, "UID_NAME", json_object_new_string(uid_info->pw_name)); 399 | 400 | json_object_object_add(j, "GID", json_object_new_int(t->gid)); 401 | 402 | if((gg = getgrgid(t->gid)) != NULL) 403 | json_object_object_add(j, "GID_NAME", json_object_new_string(gg->gr_name)); 404 | 405 | json_object_object_add(j, "TID", json_object_new_int(t->tid)); 406 | 407 | json_object_object_add(j, "PROCESS_PATH", 408 | json_object_new_string(t->full_task_path != NULL ? t->full_task_path : t->task)); 409 | 410 | json_object_object_add(j, "PROCESS_CMDLINE", 411 | json_object_new_string(t->cmdline != NULL ? t->cmdline : "")); 412 | 413 | *t_res = j; 414 | } 415 | 416 | /* ******************************************* */ 417 | 418 | void event2json(eBPFevent *t_event, struct json_object **t_res) { 419 | char buf1[128], buf2[128]; 420 | char *saddr, *daddr; 421 | const char *t_saddr, *t_daddr; 422 | struct json_object *j = json_object_new_object(), *k, *docker_json, *kube_json; 423 | struct json_object *proc, *father; 424 | 425 | snprintf(buf1, sizeof(buf1), "%u.%06u", 426 | (unsigned int)t_event->event_time.tv_sec, 427 | (unsigned int)t_event->event_time.tv_usec); 428 | json_object_object_add(j, "timestamp", json_object_new_string(buf1)); 429 | 430 | // json_object_object_add(j, "ktime", json_object_new_int64(t_event->ktime)); 431 | 432 | json_object_object_add(j, "INTERFACE_NAME", json_object_new_string(t_event->ifname)); 433 | json_object_object_add(j, "IP_PROTOCOL_VERSION", json_object_new_int(t_event->ip_version)); 434 | 435 | json_object_object_add(j, 436 | (t_event->proto == IPPROTO_TCP) ? "TCP_EVENT_TYPE" : "UDP_EVENT_TYPE", 437 | json_object_new_string(event_summary(t_event))); 438 | 439 | if(t_event->ip_version == 4) { 440 | saddr = intoaV4(htonl(t_event->addr.v4.saddr), buf1, sizeof(buf1)); 441 | daddr = intoaV4(htonl(t_event->addr.v4.daddr), buf2, sizeof(buf2)); 442 | t_saddr = "IPV4_SRC_ADDR", t_daddr = "IPV4_DST_ADDR"; 443 | } else { 444 | saddr = intoaV6(&t_event->addr.v6.saddr, buf1, sizeof(buf1)); 445 | daddr = intoaV6(&t_event->addr.v6.daddr, buf2, sizeof(buf2)); 446 | t_saddr = "IPV6_SRC_ADDR", t_daddr = "IPV6_DST_ADDR"; 447 | } 448 | 449 | json_object_object_add(j, t_saddr, json_object_new_string(saddr)); 450 | json_object_object_add(j, t_daddr, json_object_new_string(daddr)); 451 | 452 | json_object_object_add(j, "PROTOCOL", json_object_new_int(t_event->proto)); 453 | json_object_object_add(j, "L4_SRC_PORT", json_object_new_int(t_event->sport)); 454 | json_object_object_add(j, "L4_DST_PORT", json_object_new_int(t_event->dport)); 455 | 456 | if(t_event->latency_usec > 0) { 457 | double v = t_event->latency_usec/(double)1000; 458 | 459 | snprintf(buf1, sizeof(buf1), "%.3f", v); 460 | 461 | #ifdef HAVE_DOUBLES 462 | json_object_object_add(j, "NW_LATENCY_MS", json_object_new_double_s(v, buf1)); 463 | #else 464 | json_object_object_add(j, "NW_LATENCY_MS", json_object_new_string(buf1)); 465 | #endif 466 | } 467 | 468 | if(t_event->retransmissions > 0) 469 | json_object_object_add(j, "RETRAN_PKTS", json_object_new_int(t_event->retransmissions)); 470 | 471 | if(t_event->proc.task[0] != '\0') { 472 | task2json(&t_event->proc, &proc); 473 | json_object_object_add(j, "LOCAL_PROCESS", proc); 474 | } 475 | 476 | if(t_event->father.task[0] != '\0') { 477 | task2json(&t_event->father, &father); 478 | json_object_object_add(j, "LOCAL_FATHER_PROCESS", father); 479 | } 480 | 481 | if(t_event->docker.name != NULL) { 482 | if((k = json_object_new_object()) != NULL) { 483 | if((docker_json = json_object_new_object()) != NULL) { 484 | if(t_event->container_id[0] != '\0') 485 | json_object_object_add(docker_json, "ID", json_object_new_string(t_event->container_id)); 486 | 487 | json_object_object_add(docker_json, "NAME", json_object_new_string(t_event->docker.name)); 488 | json_object_object_add(k, "DOCKER", docker_json); 489 | json_object_object_add(j, "LOCAL_CONTAINER", k); 490 | } else 491 | json_object_put(k); 492 | } 493 | } 494 | 495 | if(t_event->kube.pod) { 496 | if((k = json_object_new_object()) != NULL) { 497 | if((kube_json = json_object_new_object()) != NULL) { 498 | if(t_event->container_id[0] != '\0') 499 | json_object_object_add(kube_json, "ID", json_object_new_string(t_event->container_id)); 500 | 501 | if(t_event->kube.name != NULL) 502 | json_object_object_add(kube_json, "NAME", json_object_new_string(t_event->kube.name)); 503 | 504 | if(t_event->kube.pod != NULL) 505 | json_object_object_add(kube_json, "POD", json_object_new_string(t_event->kube.pod)); 506 | 507 | if(t_event->kube.ns != NULL) 508 | json_object_object_add(kube_json, "NS", json_object_new_string(t_event->kube.ns)); 509 | 510 | json_object_object_add(k, "K8S", kube_json); 511 | json_object_object_add(j, "LOCAL_CONTAINER", k); 512 | } else 513 | json_object_put(k); 514 | } 515 | } 516 | 517 | *t_res = j; 518 | } 519 | 520 | /* ******************************************* */ 521 | 522 | static void zmqHandler(void* t_bpfctx, void* t_data, int t_datasize) { 523 | struct json_object *json_event; 524 | char *json_str; 525 | eBPFevent *e = (eBPFevent*)t_data; 526 | eBPFevent event; 527 | 528 | memcpy(&event, e, sizeof(eBPFevent)); 529 | ebpf_preprocess_event(&event); 530 | event2json(&event, &json_event); 531 | json_str = (char*) json_object_get_string(json_event); 532 | 533 | if(verbose) printf("%s\n", json_str); 534 | 535 | // writing event ----- // 536 | struct zmq_msg_hdr msg_hdr; 537 | 538 | /* 1 Send the event in JSON format */ 539 | strncpy(msg_hdr.url, "flow", sizeof(msg_hdr.url)); 540 | msg_hdr.version = 0; 541 | msg_hdr.size = strlen(json_str); 542 | zmq_send(gZMQsocket, &msg_hdr, sizeof(msg_hdr), ZMQ_SNDMORE); 543 | zmq_send(gZMQsocket, json_str, msg_hdr.size, 0); 544 | 545 | /* 2 Send the event in binary format */ 546 | strncpy(msg_hdr.url, "ebpf", sizeof(msg_hdr.url)); 547 | msg_hdr.version = 0; 548 | msg_hdr.size = sizeof(eBPFevent); 549 | zmq_send(gZMQsocket, &msg_hdr, sizeof(msg_hdr), ZMQ_SNDMORE); 550 | zmq_send(gZMQsocket, &event, msg_hdr.size, 0); 551 | 552 | json_object_put(json_event); 553 | ebpf_free_event(&event); 554 | } 555 | 556 | /* ******************************************* */ 557 | 558 | static void handleTermination(int t_s) { 559 | if(!gRUNNING) return; 560 | 561 | printf("\r* Terminating * \n"); 562 | gRUNNING = 0; 563 | } 564 | -------------------------------------------------------------------------------- /ebpflowexport.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | ebpf_flow "./go" 5 | "os" 6 | "syscall" 7 | "os/signal" 8 | "fmt" 9 | ) 10 | 11 | var gRUNNING bool = true 12 | 13 | func main () { 14 | // Open ebpflow 15 | ebpf := ebpf_flow.NewEbpflow(event_handler, 0) 16 | fmt.Println("Initialzed") 17 | 18 | // Handle interruption 19 | c := make(chan os.Signal) 20 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 21 | go func() { 22 | <-c 23 | gRUNNING = false 24 | }() 25 | 26 | // Poll events 27 | for gRUNNING == true { 28 | ebpf.PollEvent(10) 29 | } 30 | 31 | // Clean resources 32 | ebpf.Close() 33 | } 34 | 35 | func event_handler (event ebpf_flow.EBPFevent) { 36 | fmt.Printf("[pid:%d][etype:%d][%s][task:%s][path:%s]", 37 | event.Proc.Pid, event.EType, event.Ifname, 38 | event.Proc.Task, event.Proc.Full_Task_Path) 39 | fmt.Printf("[%s:%d <-> %s:%d]", 40 | event.Saddr.String(), event.Sport, event.Daddr.String(), event.Dport) 41 | 42 | if (event.Docker != nil) { 43 | fmt.Printf("[container_id: %s][name: %s]", event.Container_id[:16], event.Docker.Name) 44 | } else if (event.Kube != nil) { 45 | fmt.Printf("[container_id: %s][name: %s][ns: %s][pod: %s]", 46 | event.Container_id[:16], event.Kube.Name, event.Kube.Ns, event.Kube.Pod) 47 | } 48 | fmt.Printf("\n") 49 | } 50 | -------------------------------------------------------------------------------- /examples/c++/README.md: -------------------------------------------------------------------------------- 1 | ### Build 2 | Before building the example libebpfflow.a must be built from the project root. 3 | The example can be built with g++. 4 | ```sh 5 | $ g++ usage_libebpfflow.cpp -o example ../../libebpfflow.a -lbcc -ljson-c -lcurl 6 | ``` 7 | 8 | ### Usage 9 | ``` 10 | $ sudo ./example 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/c++/usage_libebpfflow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../../ebpf_flow.h" 3 | 4 | int gRUNNING = 1; 5 | 6 | static void event_handler(void* t_bpfctx, void* t_data, int t_datasize); 7 | static void handl_signint(int t_s); 8 | 9 | int main(int argc, char **argv) { 10 | // Return code 11 | ebpfRetCode rc; 12 | // This will store the object from which to poll 13 | void *ebpf; 14 | 15 | // Initialize the library to capture TCP events 16 | ebpf = init_ebpf_flow(NULL, event_handler, &rc, 17 | LIBEBPF_TCP|LIBEBPF_INCOMING|LIBEBPF_OUTCOMING); 18 | 19 | if(!ebpf) { 20 | printf("Error: %s\n", ebpf_print_error(rc)); 21 | return(rc); 22 | } 23 | 24 | printf("Initialized, start polling events"); 25 | 26 | // Polling event with a timeout of 10ms 27 | while(gRUNNING) { 28 | ebpf_poll_event(ebpf, 10); 29 | } 30 | 31 | // Cleaning environment 32 | term_ebpf_flow(ebpf); 33 | 34 | return 0; 35 | } 36 | 37 | 38 | static void event_handler(void* t_bpfctx, void* t_data, int t_datasize) { 39 | eBPFevent *e = (eBPFevent*)t_data; 40 | eBPFevent event; 41 | 42 | // Copy needed as ebpf_preprocess_event will modify the memory 43 | memcpy(&event, e, sizeof(eBPFevent)); 44 | ebpf_preprocess_event(&event, 1, NULL); 45 | 46 | printf("[pid: %lu][%s]", 47 | (long unsigned int)event.proc.pid, event.proc.task); 48 | 49 | // Cleaning environment 50 | ebpf_free_event(&event); 51 | } 52 | 53 | 54 | static void handl_signint(int t_s) { 55 | printf("Terminating"); 56 | gRUNNING = 0; 57 | } 58 | -------------------------------------------------------------------------------- /go/ebpf_flow.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2018-19 - ntop.org 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software Foundation, 17 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | */ 20 | 21 | /* 22 | * This package implements an interface to capture network events 23 | * by using eBPF probes attached to Linux kernel functions. 24 | * Note: only one event handler at a time is supported 25 | */ 26 | package goebpf_flow 27 | 28 | import ( 29 | "fmt" 30 | "net" 31 | "encoding/binary" 32 | "unsafe" 33 | ) 34 | 35 | /* 36 | #cgo CFLAGS: -I. 37 | #cgo LDFLAGS: -L.. -lebpfflow -lcurl -lbcc -ljson-c -lstdc++ 38 | #include "../ebpf_flow.h" 39 | 40 | #include 41 | 42 | void go_handleEvent(void *t_bpfctx, void *t_data, int t_datalen); 43 | 44 | static void* wrapper_init_ebpf_flow(__u16 flags) { 45 | ebpfRetCode rc; 46 | return init_ebpf_flow(NULL, go_handleEvent, &rc, flags); 47 | } 48 | 49 | static eBPFevent* preprocess(eBPFevent *e) { 50 | eBPFevent *event = malloc(sizeof(eBPFevent)); 51 | memcpy(event, e, sizeof(eBPFevent)); 52 | ebpf_preprocess_event(event); 53 | return event; 54 | } 55 | */ 56 | import "C" 57 | 58 | 59 | /* ****************************************** */ 60 | // ===== ===== KERNEL->USER EVENT ===== ===== // 61 | /* ****************************************** */ 62 | const COMMAND_LEN = 16 // defined in sched.h 63 | const IFNAMSIZ = 16 // max is in limits.h -> NAME_MAX 64 | const CGROUP_ID_LEN = 64 65 | 66 | /* 67 | * Events types are forged as follows: 68 | * I_digit (=1): init events (e.g. connection creation) 69 | * (=2): update events on existing connection 70 | * (=3): connection closing 71 | * (=5): operation failed 72 | * II_digit (=0): tcp events 73 | * (=1): udp events 74 | * III_digit: discriminate the single event 75 | * The type is reported in eEBPFevent->etype 76 | */ 77 | type etype uint32 78 | const ( 79 | eTCP_ACPT = 100 80 | eTCP_CONN = 101 81 | eTCP_CONN_FAIL = 500 82 | eUDP_RECV = 210 83 | eUDP_SEND = 211 84 | eTCP_RETR = 200 85 | eTCP_CLOSE = 300 86 | ) 87 | 88 | /* 89 | * Supported flags to filter events when initializating libebpfflow 90 | * Combinations of this flags allow to capture only subsets of events 91 | */ 92 | type libebpflow_flag uint16 93 | const ( 94 | LIBEBPF_TCP = 1 << 0 95 | LIBEBPF_UDP = 1 << 1 96 | LIBEBPF_INCOMING = 1 << 2 97 | LIBEBPF_OUTCOMING = 1 << 3 98 | LIBEBPF_TCP_CLOSE = 1 << 4 99 | LIBEBPF_TCP_RETR = 1 << 5 100 | ) 101 | 102 | type TaskInfo struct { 103 | Pid uint32 104 | Uid uint32 105 | Gid uint32 106 | Task string // task name 107 | Full_Task_Path string 108 | } 109 | 110 | type DockerInfo struct { 111 | Name string // Docker container name 112 | } 113 | 114 | type KubeInfo struct { 115 | Name string // Container name 116 | Pod string // Container pod 117 | Ns string // Kubernetes namespace 118 | } 119 | 120 | type EBPFevent struct { 121 | Ktime uint64 // Absolute kernel time 122 | Ifname string // net-dev name 123 | Event_time uint64 // Event time, filled during event preprocessing 124 | Ip_version uint8 125 | Sent_packet uint8 126 | EType etype // event type, supported events are listed in event_type enum 127 | 128 | Saddr net.IP 129 | Daddr net.IP 130 | 131 | Proto uint8 132 | Sport, Dport uint16 133 | Latency_usec uint32 134 | Retransmissions uint16 135 | 136 | Proc TaskInfo 137 | Father TaskInfo 138 | 139 | Container_id string // Container identifier 140 | // Both next fields are initializated to nil and populated only during preprocessing 141 | Docker *DockerInfo 142 | Kube *KubeInfo 143 | } 144 | 145 | /* 146 | * Implements the methods to manage eBPF events. The correct usage requires: 147 | * 1. Initialization with NewEbpflow 148 | * 2. Event polling by using the funcion PollEvent 149 | * 3. Invocation of Term to clean resources 150 | */ 151 | type Ebpflow struct { 152 | ebpf unsafe.Pointer 153 | } 154 | 155 | var gHandler func(EBPFevent) 156 | 157 | /* 158 | * Creates a new Ebpflow object from which start capuring events 159 | * Args: 160 | * handler - function handler, called whenever a new event is captured 161 | * and the function Poll is invoked 162 | * flags - filter the events based on which bits are active. If the value 163 | * is zero all events are captured. Check libebpflow_flag for more details 164 | */ 165 | func NewEbpflow (handler func(EBPFevent), flags uint16) *Ebpflow { 166 | ebpfp, _ := C.wrapper_init_ebpf_flow(0) 167 | if ebpfp == nil { 168 | fmt.Println("Error, unable to initialize libebpfflow") 169 | return nil 170 | } 171 | 172 | gHandler = handler 173 | return &Ebpflow{ ebpf: ebpfp } 174 | } 175 | 176 | /* 177 | * Frees the resources used 178 | */ 179 | func (e Ebpflow) Close () { 180 | C.term_ebpf_flow(e.ebpf); 181 | } 182 | 183 | /* 184 | * If a new event has been capured the handler provided on creation is 185 | * invoked with the new event as argument. 186 | * Args: 187 | * timeout - waits at most timeout milliseconds if no new event is captured 188 | */ 189 | func (e Ebpflow) PollEvent(timeout int) { 190 | C.ebpf_poll_event(e.ebpf, (C.uint)(timeout)) 191 | } 192 | 193 | /* 194 | * Translates information concerning a task from a C structure to a Go struct 195 | */ 196 | func c2TaskInfo (p C.struct_taskInfo) TaskInfo { 197 | return TaskInfo { 198 | Pid: (uint32)(p.pid), 199 | Uid: (uint32)(p.uid), 200 | Gid: (uint32)(p.gid), 201 | Task: C.GoString(&p.task[0]), 202 | Full_Task_Path: C.GoString(p.full_task_path), 203 | } 204 | } 205 | 206 | //export go_handleEvent 207 | func go_handleEvent(t_bpfctx unsafe.Pointer, t_data unsafe.Pointer, t_datalen C.int) { 208 | event := (* C.eBPFevent)(t_data) 209 | filled_event := C.preprocess(event); 210 | 211 | goevent := EBPFevent { 212 | Ktime: (uint64)(filled_event.ktime), 213 | Ifname: C.GoString(&filled_event.ifname[0]), 214 | Proto: (uint8)(filled_event.proto), 215 | Latency_usec: (uint32)(filled_event.latency_usec), 216 | Retransmissions: (uint16)(filled_event.retransmissions), 217 | Ip_version: (uint8)(filled_event.ip_version), 218 | Sent_packet: (uint8)(filled_event.sent_packet), 219 | Sport: (uint16)(filled_event.sport), 220 | Dport: (uint16)(filled_event.dport), 221 | Proc: c2TaskInfo(filled_event.proc), 222 | Father: c2TaskInfo(filled_event.father), 223 | Container_id: C.GoString(&filled_event.container_id[0]), 224 | } 225 | if(filled_event.ip_version == 4) { 226 | ipv4 := (*C.struct_ipv4_addr_t)(unsafe.Pointer(&filled_event.addr[0])) 227 | goevent.Saddr = make(net.IP, 4) 228 | binary.LittleEndian.PutUint32(goevent.Saddr, (uint32)(ipv4.saddr)) 229 | goevent.Daddr = make(net.IP, 4) 230 | binary.LittleEndian.PutUint32(goevent.Daddr, (uint32)(ipv4.daddr)) 231 | } else { 232 | ipv6 := (*C.struct_ipv6_addr_t)(unsafe.Pointer(&filled_event.addr[0])) 233 | saddr := ([16]byte)(ipv6.saddr) 234 | goevent.Saddr = net.ParseIP(string(saddr[:])) 235 | daddr := ([16]byte)(ipv6.daddr) 236 | goevent.Daddr = net.ParseIP(string(daddr[:])) 237 | } 238 | 239 | if(filled_event.docker.name != nil) { 240 | goevent.Docker = &DockerInfo { 241 | Name: C.GoString(filled_event.docker.name), 242 | } 243 | } else { 244 | goevent.Docker = nil 245 | } 246 | 247 | if(filled_event.kube.pod != nil) { 248 | goevent.Kube = &KubeInfo { 249 | Name: C.GoString(filled_event.kube.name), 250 | Pod: C.GoString(filled_event.kube.pod), 251 | Ns: C.GoString(filled_event.kube.ns), 252 | } 253 | } else { 254 | goevent.Kube = nil 255 | } 256 | 257 | gHandler(goevent) 258 | C.ebpf_free_event(filled_event) 259 | } 260 | 261 | 262 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /pid2veth/Makefile: -------------------------------------------------------------------------------- 1 | pid2veth: pid2veth.c Makefile 2 | gcc -g pid2veth.c -o pid2veth 3 | 4 | 5 | clean: 6 | /bin/rm -f *~ pid2veth 7 | -------------------------------------------------------------------------------- /pid2veth/README.md: -------------------------------------------------------------------------------- 1 | # pid2veth 2 | print the name of the virtual Ethernet associated with a container given the pid of a task in it. 3 | ### Build 4 | ```sh 5 | $ make 6 | ``` 7 | ### Testing 8 | Start a container and get the pid of a process in it. 9 | ```sh 10 | $ sudo docker run --name=pid2veth_test -id ubuntu 11 | 2c93a6cf1e9719ef1c842981ae4efaae37b684fe67f4f3eace4e09cba2c6c2d3 12 | $ docker ps -q | xargs docker inspect --format '[Name:{{.Name}}][Pid:{{.State.Pid}}][ID:{{.ID}}]' 13 | [Name:/pid2veth_test][Pid:13033][ID:2c93a6cf1e97] 14 | ``` 15 | Build and execute pid2veth. 16 | ```sh 17 | $ sudo ./pid2veth 13033 18 | veth0eb3759 19 | ``` 20 | 21 | 22 | -------------------------------------------------------------------------------- /pid2veth/pid2veth.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | 17 | #ifndef NETLINK_GENERIC 18 | #define NETLINK_GENERIC 16 19 | #endif 20 | 21 | #ifndef SIOCETHTOOL 22 | #define SIOCETHTOOL 0x8946 23 | #endif 24 | 25 | #define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ 26 | 27 | struct ethtool_stats { 28 | __u32 cmd; 29 | __u32 n_stats; 30 | __u64 data[10]; 31 | }; 32 | 33 | /* ************************************************** */ 34 | 35 | /* 36 | * Returns the peer of a veth network interface 37 | */ 38 | int getp(const char *ifname) { 39 | struct ifreq ifr; 40 | struct ethtool_stats *stats; 41 | int err, fd, res; 42 | 43 | // Opening socket fd 44 | //(more details at http://man7.org/linux/man-pages/man7/netdevice.7.html) 45 | fd = socket(AF_INET, SOCK_DGRAM, 0); 46 | if(fd < 0) 47 | fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); 48 | 49 | // Setting target interface 50 | strcpy(ifr.ifr_name, ifname); 51 | // Setting request args 52 | //(more details at https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ethtool.h#L662) 53 | stats =(struct ethtool_stats *) calloc(1, sizeof(struct ethtool_stats) +(sizeof(char) * 100)); 54 | stats->cmd = ETHTOOL_GSTATS; 55 | ifr.ifr_data =(char*) stats; 56 | 57 | // Sending request to driver 58 | err = ioctl(fd, SIOCETHTOOL, &ifr); 59 | if(err < 0) { 60 | perror("> Cannot get stats information"); 61 | res = -1; 62 | goto clean; 63 | } 64 | res =(int)(stats->data[0]); 65 | 66 | clean: 67 | if(fd != -1) 68 | close(fd); 69 | if(stats != NULL) 70 | free(stats); 71 | return res; 72 | } 73 | 74 | /* ************************************************** */ 75 | 76 | /* 77 | * Returns the namespace filescritor in which the process with @pid is in 78 | */ 79 | int getnsfd(int pid, FILE **f) { 80 | char buff[120]; 81 | int fp; 82 | 83 | // Getting user ns file desc 84 | snprintf(buff, sizeof(buff), "/proc/%d/ns/net", pid); 85 | *f = fopen(buff, "r"); 86 | if(*f == NULL) { 87 | printf("%s\n", strerror(errno)); 88 | fp = -1; 89 | } 90 | else { 91 | fp = fileno(*f); 92 | } 93 | 94 | return fp; 95 | } 96 | 97 | /* ************************************************** */ 98 | 99 | /* 100 | * Given a container's pid, store in buff the veth interface's name 101 | * assigned to the container. The buffer must allow for the storage of at least IF_NAMESIZE bytes 102 | */ 103 | int pid2ifname(int pid, char *t_buff) { 104 | FILE *usrf, *containerf; 105 | int usrns_fd, containerns_fd; 106 | int if_idx, res = 0; 107 | 108 | // Getting ns file desc 109 | usrns_fd = getnsfd(1, &usrf); 110 | containerns_fd = getnsfd(pid, &containerf); 111 | 112 | // Jumping to ns 113 | setns(containerns_fd, CLONE_NEWNET); 114 | 115 | // Getting veth peer 116 | if_idx = getp("eth0"); 117 | if(if_idx == -1) { 118 | res = -1; 119 | goto clean; 120 | } 121 | 122 | // Jumping back 123 | setns(usrns_fd, CLONE_NEWNET); 124 | 125 | // Converting 126 | if_indextoname(if_idx, t_buff); 127 | 128 | clean: 129 | if(usrf != NULL) 130 | fclose(usrf); 131 | 132 | if(containerf != NULL) 133 | fclose(containerf); 134 | 135 | return res; 136 | } 137 | 138 | /* ************************************************** */ 139 | 140 | void help() { 141 | printf( 142 | "pid2veth: print the name of the virtual Ethernet associated with a container \n" 143 | "given the pid of a task in it \n" 144 | "Usage: ./pid2veth \n" 145 | ); 146 | } 147 | 148 | /* ************************************************** */ 149 | 150 | int main(int argc, char **argv) { 151 | char buff[IF_NAMESIZE] = { '\0' }; 152 | int pid; 153 | 154 | if(argc < 2) { 155 | printf("> Please provide the pid of a task inside a container \n\n"); 156 | help(); 157 | return(-1); 158 | } 159 | 160 | if(getuid() != 0) { 161 | printf("> Please run as root \n\n"); 162 | help(); 163 | return 0; 164 | } 165 | 166 | pid = atoi(argv[1]); 167 | if(pid2ifname(pid, buff) == 0) { 168 | printf("%s\n", buff); 169 | } 170 | 171 | return 0; 172 | } 173 | -------------------------------------------------------------------------------- /utils/README.md: -------------------------------------------------------------------------------- 1 | kubectl_show_veth.sh 2 | Kubernetes tool that shows veth and container names 3 | 4 | docker_show_veth.sh 5 | Docket tool that shows veth and container names 6 | 7 | 8 | -------------------------------------------------------------------------------- /utils/docker_show_veth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Prints the veth used by running docker containers 5 | # 6 | 7 | if [ "$EUID" -ne 0 ] 8 | then echo "Please run as root" 9 | exit 10 | fi 11 | 12 | echo "veth containerId" 13 | echo "-----------------------" 14 | 15 | for container in $(docker ps --format '{{.Names}}'); do 16 | iflink=$(docker exec -it $container bash -c 'cat /sys/class/net/eth*/iflink') 17 | for net in $iflink; do 18 | net=$(echo $net|tr -d '\r') 19 | veth=$(grep -l $net /sys/class/net/veth*/ifindex) 20 | veth=$(echo $veth|sed -e 's;^.*net/\(.*\)/ifindex$;\1;') 21 | echo $veth $container 22 | done 23 | done 24 | -------------------------------------------------------------------------------- /utils/kubectl_show_veth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$EUID" -ne 0 ] 4 | then echo "Please run as root" 5 | exit 6 | fi 7 | 8 | if [ -x "/snap/bin/microk8s.kubectl" ]; then 9 | kubectl="/snap/bin/microk8s.kubectl" 10 | else 11 | kubectl=$(which kubectl) 12 | fi 13 | 14 | for listNamespace in $($kubectl get namespace -o jsonpath='{.items[*].metadata.name}'); do 15 | for listPod in $($kubectl get pod --namespace=$listNamespace -o jsonpath='{.items[*].metadata.name}'); do 16 | 17 | id=`$kubectl exec $listPod -- cat /sys/class/net/eth0/iflink 2>1 /dev/null` 18 | if test "$id" != ""; then 19 | ifname=`ip -o link|grep ^$id:|cut -d ':' -f 2|cut -d '@' -f 1|tr -d '[:blank:]'` 20 | echo "$ifname $listPod" 21 | fi 22 | done 23 | done 24 | -------------------------------------------------------------------------------- /wireshark/Makefile: -------------------------------------------------------------------------------- 1 | 2 | EXTCAP_PATH_LINUX=/usr/lib/x86_64-linux-gnu/wireshark/extcap/ebpfdump 3 | 4 | ############################################################### 5 | 6 | HAS_JSON=$(shell pkg-config --exists json-c; echo $$?) 7 | ifeq ($(HAS_JSON), 0) 8 | JSON_INC = $(shell pkg-config --cflags json-c) -DHAVE_JSONC 9 | JSON_LIB = $(shell pkg-config --libs json-c) 10 | endif 11 | 12 | ############################################################### 13 | 14 | HAS_LIBCURL=$(shell pkg-config --exists libcurl; echo $$?) 15 | ifeq ($(HAS_LIBCURL), 0) 16 | LIBCURL_INC = $(shell pkg-config --cflags libcurl) -DHAVE_LIBCURL 17 | LIBCURL_LIB = $(shell pkg-config --libs libcurl) 18 | endif 19 | 20 | ############################################################### 21 | 22 | CFLAGS=-g -I.. $(JSON_INC) $(LIBCURL_INC) -Wformat-truncation 23 | BASE_LIBS=$(JSON_LIB) $(LIBCURL_LIB) -lpcap -lzmq 24 | 25 | OS := $(shell uname -s) 26 | ifeq ($(OS),Linux) 27 | LIBS=-L .. -lebpfflow -lbcc $(BASE_LIBS) 28 | else 29 | LIBS=$(BASE_LIBS) 30 | endif 31 | 32 | ebpfdump: ebpfdump.c Makefile 33 | g++ $(CFLAGS) -g ebpfdump.c -o ebpfdump $(LIBS) 34 | 35 | 36 | install: ebpfdump 37 | sudo cp ebpfdump $(EXTCAP_PATH_LINUX) 38 | sudo chown root:root $(EXTCAP_PATH_LINUX) 39 | sudo chmod gou+s ebpfdump $(EXTCAP_PATH_LINUX) 40 | 41 | clean: 42 | /bin/rm -f ebpfdump 43 | 44 | -------------------------------------------------------------------------------- /wireshark/README.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | This is an extcap plugin that allows wireshark to capture system-generated events 3 | 4 | # Installation 5 | Once you compiled this plugin you need to install it in the 'Extcap path' as specified in the wireshark Help menu 6 | 7 | As of the ebpf.lua you need to copy it into ~/.wireshark/plugins/ to interpret eBPF events 8 | 9 | # Usage 10 | Starting wireshark you will see a new interface named 'eBPF interface'. Select it, and start the packet capture. 11 | 12 | -------------------------------------------------------------------------------- /wireshark/ebpf.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- (C) 2019 - ntop.org 3 | -- 4 | -- This plugin is part of libebpflow (https://github.com/ntop/libebpfflow) 5 | -- 6 | -- This program is free software; you can redistribute it and/or modify 7 | -- it under the terms of the GNU General Public License as published by 8 | -- the Free Software Foundation; either version 3 of the License, or 9 | -- (at your option) any later version. 10 | -- 11 | -- This program is distributed in the hope that it will be useful, 12 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | -- GNU General Public License for more details. 15 | -- 16 | -- You should have received a copy of the GNU General Public License 17 | -- along with this program; if not, write to the Free Software Foundation, 18 | -- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 19 | -- 20 | 21 | local ebpfflow_proto = Proto("ebpfflow", "ebpfflow Protocol Interpreter") 22 | 23 | ebpfflow_proto.fields = {} 24 | 25 | local f_frame_len = Field.new("frame.len") 26 | 27 | local ebpfflow_fds = ebpfflow_proto.fields 28 | ebpfflow_fds.ktime_sec = ProtoField.new("Kernel time (sec)", "ebpfflow.ktime.sec", ftypes.UINT32) 29 | ebpfflow_fds.ktime_usec = ProtoField.new("Kernel time (usec)", "ebpfflow.ktime.sec", ftypes.UINT32) 30 | ebpfflow_fds.ifname = ProtoField.new("Interface name", "ebpfflow.ifname", ftypes.STRING) 31 | ebpfflow_fds.evttime_sec = ProtoField.uint64("ebpfflow.evttime.sec", "Event time (sec)") 32 | ebpfflow_fds.evttime_usec = ProtoField.uint64("ebpfflow.evttime.sec", "Event time (usec)") 33 | ebpfflow_fds.ip_version = ProtoField.uint8("ebpfflow.ip_version", "Event IP protocol version") 34 | ebpfflow_fds.direction = ProtoField.new("Event direction", "ebpfflow.direction", ftypes.STRING) 35 | ebpfflow_fds.etype = ProtoField.new("Event type", "ebpfflow.etype", ftypes.STRING) 36 | ebpfflow_fds.sipaddr4 = ProtoField.new("IPv4 src address", "ebpfflow.srcipv4", ftypes.IPv4) 37 | ebpfflow_fds.dipaddr4 = ProtoField.new("IPv4 dst address", "ebpfflow.dstipv4", ftypes.IPv4) 38 | ebpfflow_fds.sipaddr6 = ProtoField.new("IPv6 src address", "ebpfflow.srcipv6", ftypes.IPv6) 39 | ebpfflow_fds.dipaddr6 = ProtoField.new("IPv6 dst address", "ebpfflow.dstipv6", ftypes.IPv6) 40 | ebpfflow_fds.proto = ProtoField.new("Event protocol", "ebpfflow.proto", ftypes.STRING) 41 | ebpfflow_fds.sport = ProtoField.uint16("ebpfflow.sport", "Event source port") 42 | ebpfflow_fds.dport = ProtoField.uint16("ebpfflow.dport", "Event destination port") 43 | ebpfflow_fds.latency = ProtoField.uint32("ebpfflow.latency", "Event latency (usec)") 44 | ebpfflow_fds.retr = ProtoField.uint16("ebpfflow.retr", "Event retransmissions") 45 | 46 | ebpfflow_fds.proc_pid = ProtoField.uint32("ebpfflow.proc_pid", "Event Process PID") 47 | ebpfflow_fds.proc_tid = ProtoField.uint32("ebpfflow.proc_tid", "Event Process TID") 48 | ebpfflow_fds.proc_uid = ProtoField.uint32("ebpfflow.proc_uid", "Event Process UID") 49 | ebpfflow_fds.proc_gid = ProtoField.uint32("ebpfflow.proc_gid", "Event Process GID") 50 | ebpfflow_fds.proc_task = ProtoField.new("Event Process Task", "ebpfflow.proc_task", ftypes.STRING) 51 | 52 | ebpfflow_fds.father_pid = ProtoField.uint32("ebpfflow.father_pid", "Event Father PID") 53 | ebpfflow_fds.father_tid = ProtoField.uint32("ebpfflow.father_tid", "Event Father TID") 54 | ebpfflow_fds.father_uid = ProtoField.uint32("ebpfflow.father_uid", "Event Father UID") 55 | ebpfflow_fds.father_gid = ProtoField.uint32("ebpfflow.father_gid", "Event Father GID") 56 | ebpfflow_fds.father_task = ProtoField.new("Event Father Task", "ebpfflow.father_task", ftypes.STRING) 57 | 58 | ebpfflow_fds.container_id = ProtoField.new("Event Container Id", "ebpfflow.container_id", ftypes.STRING) 59 | 60 | local f_eth_trailer = Field.new("eth.trailer") 61 | local f_eth_type = Field.new("eth.type") 62 | 63 | local ebpfflow_event = "eBPFFlow Event" 64 | 65 | -- ebpfflow_fds.application_protocol = ProtoField.new("ebpfflow Application Protocol", "ebpfflow.protocol.application", ftypes.UINT8, nil, base.DEC) 66 | -- ebpfflow_fds.name = ProtoField.new("ebpfflow Protocol Name", "ebpfflow.protocol.name", ftypes.STRING) 67 | 68 | -- ############################################### 69 | 70 | local f_null_type = Field.new("null.type") 71 | 72 | local debug = false 73 | 74 | -- ############################################### 75 | 76 | function ebpfflow_proto.init() 77 | 78 | end 79 | 80 | -- ############################################### 81 | 82 | -- Print contents of `tbl`, with indentation. 83 | -- You can call it as tprint(mytable) 84 | -- The other two parameters should not be set 85 | function tprint(s, l, i) 86 | l = (l) or 1000; i = i or "";-- default item limit, indent string 87 | if (l<1) then io.write("ERROR: Item limit reached.\n"); return l-1 end; 88 | local ts = type(s); 89 | if (ts ~= "table") then io.write(i..' '..ts..' '..tostring(s)..'\n'); return l-1 end 90 | io.write(i..' '..ts..'\n'); 91 | for k,v in pairs(s) do 92 | local indent = "" 93 | 94 | if(i ~= "") then 95 | indent = i .. "." 96 | end 97 | indent = indent .. tostring(k) 98 | 99 | l = tprint(v, l, indent); 100 | if (l < 0) then break end 101 | end 102 | 103 | return l 104 | end 105 | 106 | -- ############################################### 107 | 108 | local function direction2str(d) 109 | if(d == 1) then 110 | return("Sent") 111 | else 112 | return("Received") 113 | end 114 | end 115 | 116 | -- ############################################### 117 | 118 | local function proto2str(_p) 119 | p = tonumber(_p) 120 | if(p == 6) then return("TCP") 121 | elseif(p == 17) then return("UDP") 122 | else return(_p) 123 | end 124 | end 125 | 126 | -- ############################################### 127 | 128 | local function event2str(e) 129 | if(e == 100) then return("TCP_Accept") 130 | elseif(e == 101) then return("TCP_Connect") 131 | elseif(e == 200) then return("TCP_Retransmission") 132 | elseif(e == 210) then return("UDP_Receive") 133 | elseif(e == 211) then return("UDP_Send") 134 | elseif(e == 300) then return("TCP_Close") 135 | elseif(e == 500) then return("TCP_ConnectionFailed") 136 | else 137 | return(""..e) 138 | end 139 | end 140 | 141 | -- ############################################### 142 | 143 | local function getstring(finfo) 144 | local ok, val = pcall(tostring, finfo) 145 | if not ok then val = "(unknown)" end 146 | return val 147 | end 148 | 149 | local function getval(finfo) 150 | local ok, val = pcall(tostring, finfo) 151 | if not ok then val = nil end 152 | return val 153 | end 154 | 155 | function dump_pinfo(pinfo) 156 | local fields = { all_field_infos() } 157 | for ix, finfo in ipairs(fields) do 158 | -- output = output .. "\t[" .. ix .. "] " .. finfo.name .. " = " .. getstring(finfo) .. "\n" 159 | --print(finfo.name .. "\n") 160 | print("\t[" .. ix .. "] " .. finfo.name .. " = " .. getstring(finfo) .. "\n") 161 | end 162 | end 163 | 164 | -- ############################################### 165 | 166 | function ebpfflow_parse_extended_ebpf_data(pinfo, tvb, tree, offset) 167 | local ebpf_subtree = tree:add(ebpfflow_proto, tvb(), "eBPFFlow Protocol") 168 | 169 | pinfo.cols.protocol:set("eBPF") 170 | pinfo.cols.info:set(ebpfflow_event) 171 | 172 | ebpf_subtree:add_le(ebpfflow_fds.ktime_sec, tvb:range(offset,4)) 173 | offset = offset + 4 174 | 175 | ebpf_subtree:add_le(ebpfflow_fds.ktime_usec, tvb:range(offset,4)) 176 | offset = offset + 4 177 | 178 | ebpf_subtree:add(ebpfflow_fds.ifname, tvb:range(offset,16):stringz()) 179 | offset = offset + 16 180 | 181 | ebpf_subtree:add_le(ebpfflow_fds.evttime_sec, tvb:range(offset,8)) 182 | offset = offset + 8 183 | 184 | ebpf_subtree:add_le(ebpfflow_fds.evttime_usec, tvb:range(offset,8)) 185 | offset = offset + 8 186 | 187 | r = tvb:range(offset,1) 188 | ip_version = r:le_uint() 189 | ebpf_subtree:add_le(ebpfflow_fds.ip_version, r, ip_version) 190 | offset = offset + 1 191 | 192 | r = tvb:range(offset,1) 193 | direction = r:le_uint() 194 | ebpf_subtree:add_le(ebpfflow_fds.direction, r, direction2str(direction)) 195 | offset = offset + 1 196 | 197 | etype_r = tvb:range(offset,2) 198 | etype = etype_r:le_uint(etype_r) 199 | evt = event2str(etype) 200 | pinfo.cols.info:set(evt) 201 | ebpf_subtree:add(ebpfflow_fds.etype, evt) 202 | offset = offset + 1 203 | 204 | if(ip_version == 4) then 205 | offset = offset + 5 206 | ebpf_subtree:add(ebpfflow_fds.sipaddr4, tvb:range(offset,4)) 207 | offset = offset + 8 208 | ebpf_subtree:add(ebpfflow_fds.dipaddr4, tvb:range(offset,4)) 209 | offset = offset + 19 210 | else 211 | ebpf_subtree:add(ebpfflow_fds.sipaddr6, tvb:range(offset,16)) 212 | offset = offset + 16 213 | -- 214 | ebpf_subtree:add(ebpfflow_fds.dipaddr6, tvb:range(offset,16)) 215 | offset = offset + 16 216 | end 217 | 218 | offset = offset + 5 -- padding 219 | 220 | r = tvb:range(offset,1) 221 | proto = r:le_uint() 222 | ebpf_subtree:add(ebpfflow_fds.proto, proto2str(proto)) 223 | offset = offset + 1 224 | 225 | offset = offset + 1 -- pad 226 | 227 | ebpf_subtree:add_le(ebpfflow_fds.sport, tvb:range(offset,2)) 228 | offset = offset + 2 229 | 230 | ebpf_subtree:add_le(ebpfflow_fds.dport, tvb:range(offset,2)) 231 | offset = offset + 2 232 | 233 | offset = offset + 2 -- padding 234 | if(proto == 6) then 235 | -- TCP 236 | ebpf_subtree:add_le(ebpfflow_fds.latency, tvb:range(offset,4)) 237 | offset = offset + 4 238 | 239 | ebpf_subtree:add_le(ebpfflow_fds.retr, tvb:range(offset,2)) 240 | offset = offset + 2 241 | else 242 | offset = offset + 6 243 | end 244 | 245 | offset = offset + 2 -- pad 246 | 247 | -- Tasks 248 | ebpf_subtree:add_le(ebpfflow_fds.proc_pid, tvb:range(offset,4)) 249 | offset = offset + 4 250 | 251 | ebpf_subtree:add_le(ebpfflow_fds.proc_tid, tvb:range(offset,4)) 252 | offset = offset + 4 253 | 254 | ebpf_subtree:add_le(ebpfflow_fds.proc_uid, tvb:range(offset,4)) 255 | offset = offset + 4 256 | 257 | ebpf_subtree:add_le(ebpfflow_fds.proc_gid, tvb:range(offset,4)) 258 | offset = offset + 4 259 | 260 | ebpf_subtree:add(ebpfflow_fds.proc_task, tvb:range(offset,16):stringz()) 261 | offset = offset + 16 262 | 263 | offset = offset + 8 -- ptr 264 | 265 | -- Father Task 266 | ebpf_subtree:add_le(ebpfflow_fds.father_pid, tvb:range(offset,4)) 267 | offset = offset + 4 268 | 269 | ebpf_subtree:add_le(ebpfflow_fds.father_tid, tvb:range(offset,4)) 270 | offset = offset + 4 271 | 272 | ebpf_subtree:add_le(ebpfflow_fds.father_uid, tvb:range(offset,4)) 273 | offset = offset + 4 274 | 275 | ebpf_subtree:add_le(ebpfflow_fds.father_gid, tvb:range(offset,4)) 276 | offset = offset + 4 277 | 278 | ebpf_subtree:add(ebpfflow_fds.father_task, tvb:range(offset,16):stringz()) 279 | offset = offset + 16 280 | 281 | offset = offset + 8 -- ptr 282 | 283 | -- Container 284 | ebpf_subtree:add(ebpfflow_fds.container_id, tvb:range(offset,128)) 285 | offset = offset + 128 286 | end 287 | 288 | -- ############################################### 289 | 290 | function ebpfflow_parse_ebpf_data(pinfo, tvb, tree, offset) 291 | local ebpf_subtree = tree:add(ebpfflow_proto, tvb(), "eBPFFlow Protocol") 292 | 293 | ebpf_subtree:add_le(ebpfflow_fds.proc_pid, tvb:range(offset,4)) 294 | offset = offset + 4 295 | 296 | ebpf_subtree:add_le(ebpfflow_fds.proc_tid, tvb:range(offset,4)) 297 | offset = offset + 4 298 | 299 | ebpf_subtree:add_le(ebpfflow_fds.proc_uid, tvb:range(offset,4)) 300 | offset = offset + 4 301 | 302 | ebpf_subtree:add_le(ebpfflow_fds.proc_gid, tvb:range(offset,4)) 303 | offset = offset + 4 304 | 305 | ebpf_subtree:add_le(ebpfflow_fds.proc_task, tvb:range(offset,8)) 306 | offset = offset + 8 307 | 308 | ebpf_subtree:add_le(ebpfflow_fds.container_id, tvb:range(offset,12)) 309 | offset = offset + 12 310 | end 311 | 312 | -- ############################################### 313 | 314 | -- the dissector function callback 315 | function ebpfflow_proto.dissector(tvb, pinfo, tree) 316 | -- Wireshark dissects the packet twice. We ignore the first 317 | -- run as on that step the packet is still undecoded 318 | -- The trick below avoids to process the packet twice 319 | 320 | if(pinfo.visited == true) then 321 | local null_type = f_null_type() 322 | local eth_trailer = f_eth_trailer() 323 | local eth_type = f_eth_type() 324 | 325 | if(eth_type ~= nil) then 326 | local eth_type = getval(eth_type) 327 | if(eth_type == "0x00000000") then 328 | ebpfflow_parse_extended_ebpf_data(pinfo, tvb, tree, 14) 329 | end 330 | end 331 | 332 | if(eth_trailer ~= nil) then 333 | local eth_trailer = getval(eth_trailer) 334 | local magic = string.sub(eth_trailer, 1, 5) 335 | 336 | if(magic == "19:68") then 337 | local frame_len = getval(f_frame_len()) 338 | 339 | ebpfflow_parse_ebpf_data(pinfo, tvb, tree, frame_len - 36) 340 | end 341 | end 342 | 343 | if(null_type ~= nil) then 344 | local null_type = getval(null_type) 345 | 346 | if(null_type == "0x000007e3") then 347 | ebpfflow_parse_extended_ebpf_data(pinfo, tvb, tree, 4) 348 | end 349 | end 350 | end 351 | 352 | -- ########################################### 353 | 354 | -- As we do not need to add fields to the dissection 355 | -- there is no need to process the packet multiple times 356 | if(pinfo.visited == true) then return end 357 | 358 | num_pkts = num_pkts + 1 359 | if((num_pkts > 1) and (pinfo.number == 1)) then return end 360 | 361 | ebpfflow_dissector(tvb, pinfo, tree) 362 | end 363 | 364 | register_postdissector(ebpfflow_proto) 365 | -------------------------------------------------------------------------------- /wireshark/ebpfdump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * (C) 2019 - ntop.org 4 | * 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Lessed General Public License as published by 8 | * the Free Software Foundation; either version 2.1 of the License, or 9 | * (at your option) any later version. 10 | * 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #ifdef __linux__ 30 | #include 31 | #include 32 | #define th_sport source 33 | #define th_dport dest 34 | #define uh_sport source 35 | #define uh_dport dest 36 | #else 37 | #include 38 | #include 39 | #define s6_addr32 __u6_addr.__u6_addr32 40 | #endif 41 | #include "pcapio.c" 42 | #include "ebpf_flow.h" 43 | 44 | struct ebpf_event { 45 | u_int32_t pid, tid, uid, gid; 46 | char process_name[8]; 47 | char container_id[12]; 48 | }; 49 | 50 | #define EBPFDUMP_INTERFACE "ebpf" 51 | 52 | #define SOCKET_LIBEBPF 2019 53 | #define EXIT_SUCCESS 0 54 | 55 | #define EBPFDUMP_MAX_NBPF_LEN 8192 56 | #define EBPFDUMP_MAX_DATE_LEN 26 57 | #define EBPFDUMP_MAX_NAME_LEN 4096 58 | 59 | #define EBPFDUMP_VERSION_MAJOR "0" 60 | #define EBPFDUMP_VERSION_MINOR "1" 61 | #define EBPFDUMP_VERSION_RELEASE "0" 62 | 63 | #define EXTCAP_OPT_LIST_INTERFACES 'l' 64 | #define EXTCAP_OPT_VERSION 'v' 65 | #define EXTCAP_OPT_LIST_DLTS 'L' 66 | #define EXTCAP_OPT_INTERFACE 'i' 67 | #define EXTCAP_OPT_CONFIG 'c' 68 | #define EXTCAP_OPT_CAPTURE 'C' 69 | #define EXTCAP_OPT_FIFO 'F' 70 | #define EXTCAP_OPT_DEBUG 'D' 71 | #define EBPFDUMP_OPT_HELP 'h' 72 | #define EBPFDUMP_OPT_IFNAME 'n' 73 | #define EBPFDUMP_OPT_CUSTOM_NAME 'N' 74 | 75 | #define EBPFDUMP_EBPFEVENTS_NAME "ebpfevents" 76 | #define EBPFDUMP_EBPFZMQEVENTS_NAME "ebpfzmqevents" 77 | #define EBPFDUMP_ZMQ_ADDRESS "tcp://0.0.0.0:6789" 78 | #define EBPFDUMP_ZMQ_TOPIC "ebpf" 79 | 80 | static struct option longopts[] = { 81 | /* mandatory extcap options */ 82 | { "extcap-interfaces", no_argument, NULL, EXTCAP_OPT_LIST_INTERFACES }, 83 | { "extcap-version", optional_argument, NULL, EXTCAP_OPT_VERSION }, 84 | { "extcap-dlts", no_argument, NULL, EXTCAP_OPT_LIST_DLTS }, 85 | { "extcap-interface", required_argument, NULL, EXTCAP_OPT_INTERFACE }, 86 | { "extcap-config", no_argument, NULL, EXTCAP_OPT_CONFIG }, 87 | { "capture", no_argument, NULL, EXTCAP_OPT_CAPTURE }, 88 | { "fifo", required_argument, NULL, EXTCAP_OPT_FIFO }, 89 | { "debug", optional_argument, NULL, EXTCAP_OPT_DEBUG }, 90 | 91 | /* custom extcap options */ 92 | { "help", no_argument, NULL, EBPFDUMP_OPT_HELP }, 93 | { "ifname", required_argument, NULL, EBPFDUMP_OPT_IFNAME }, 94 | { "custom-name", required_argument, NULL, EBPFDUMP_OPT_CUSTOM_NAME }, 95 | 96 | {0, 0, 0, 0} 97 | }; 98 | 99 | typedef struct _extcap_interface { 100 | const char * interface; 101 | const char * description; 102 | u_int16_t dlt; 103 | const char * dltname; 104 | const char * dltdescription; 105 | } extcap_interface; 106 | 107 | #define DLT_EN10MB 1 108 | 109 | static extcap_interface extcap_interfaces[] = { 110 | { EBPFDUMP_INTERFACE, "eBPF interface", DLT_EN10MB, NULL, "The EN10MB Ethernet2 DLT" }, 111 | }; 112 | 113 | struct zmq_msg_hdr { 114 | char url[32]; 115 | u_int32_t version; 116 | u_int32_t size; 117 | }; 118 | 119 | struct zmq_info { 120 | u_int8_t initialized; 121 | void *z_socket, *z_context; 122 | }; 123 | 124 | struct zmq_info zmq; 125 | 126 | #define MAX_NUM_INT 32 127 | 128 | static size_t extcap_interfaces_num = sizeof(extcap_interfaces) / sizeof(extcap_interface); 129 | static char *extcap_selected_interface = NULL; 130 | static char *pcap_selected_interface = NULL; 131 | static char *extcap_capture_fifo = NULL; 132 | static FILE *fp = NULL; 133 | static FILE *log_fp = NULL; 134 | static char *all_interfaces[MAX_NUM_INT] = { NULL }; 135 | static u_int8_t num_all_interfaces = 0; 136 | static int32_t thiszone; 137 | static char *containerId = NULL; 138 | 139 | /* ***************************************************** */ 140 | /* ***************************************************** */ 141 | 142 | /* LRU cache */ 143 | 144 | #define NUM_LRU_ENTRIES 256 145 | 146 | struct lru_cache_entry { 147 | u_int32_t key; 148 | u_int8_t is_full; 149 | struct ebpf_event value; 150 | }; 151 | 152 | struct lru_cache { 153 | struct lru_cache_entry entries[NUM_LRU_ENTRIES]; 154 | }; 155 | 156 | void lru_cache_init(struct lru_cache *c) { 157 | memset(c, 0, sizeof(lru_cache)); 158 | } 159 | 160 | u_int8_t lru_find_cache(struct lru_cache *c, u_int32_t key, 161 | struct ebpf_event *value) { 162 | u_int32_t slot = key % NUM_LRU_ENTRIES; 163 | 164 | if(c->entries[slot].is_full) { 165 | memcpy(value, &c->entries[slot].value, sizeof(struct ebpf_event)); 166 | return(1); 167 | } else 168 | return(0); 169 | } 170 | 171 | void lru_add_to_cache(struct lru_cache *c, u_int32_t key, struct ebpf_event *value) { 172 | u_int32_t slot = key % NUM_LRU_ENTRIES; 173 | 174 | c->entries[slot].is_full = 1, c->entries[slot].key = key; 175 | memcpy(&c->entries[slot].value, value, sizeof(struct ebpf_event)); 176 | } 177 | 178 | struct lru_cache received_events; 179 | 180 | /* ***************************************************** */ 181 | /* ***************************************************** */ 182 | 183 | inline u_int min(u_int a, u_int b) { return((a < b) ? a : b); } 184 | 185 | void sigproc(int sig) { 186 | fprintf(stdout, "Exiting..."); 187 | fflush(stdout); 188 | exit(0); 189 | } 190 | 191 | /* ***************************************************** */ 192 | 193 | static int initZMQ() { 194 | int val = 1; 195 | 196 | zmq.z_context = zmq_ctx_new(); 197 | zmq.z_socket = zmq_socket(zmq.z_context, ZMQ_SUB); 198 | zmq_setsockopt(zmq.z_socket, ZMQ_TCP_KEEPALIVE, &val, sizeof(val)); 199 | 200 | if(zmq_bind(zmq.z_socket, EBPFDUMP_ZMQ_ADDRESS) != 0) { 201 | printf("Unable to bind to ZMQ socket %s: %s\n", 202 | EBPFDUMP_ZMQ_ADDRESS, strerror(errno)); 203 | return(-1); 204 | } else { 205 | if(log_fp) fprintf(log_fp, "Listening on %s for ZMQ events\n", EBPFDUMP_ZMQ_ADDRESS); 206 | } 207 | 208 | if(zmq_setsockopt(zmq.z_socket, ZMQ_SUBSCRIBE, EBPFDUMP_ZMQ_TOPIC, strlen(EBPFDUMP_ZMQ_TOPIC)) != 0) 209 | return(-1); 210 | 211 | zmq.initialized = 1; 212 | 213 | return(0); 214 | } 215 | 216 | /* ***************************************************** */ 217 | 218 | static void termZMQ() { 219 | if(zmq.initialized) { 220 | zmq_close(zmq.z_socket); 221 | zmq_ctx_destroy(zmq.z_context); 222 | } 223 | } 224 | 225 | /* ***************************************************** */ 226 | 227 | void extcap_version() { 228 | /* Print version */ 229 | printf("extcap {version=%s.%s.%s}\n", 230 | EBPFDUMP_VERSION_MAJOR, EBPFDUMP_VERSION_MINOR, 231 | EBPFDUMP_VERSION_RELEASE); 232 | } 233 | 234 | /* ***************************************************** */ 235 | 236 | int docker_list_interfaces() { 237 | FILE *fd; 238 | int rc, found = 0; 239 | struct stat statbuf; 240 | const char *dcmd = "/usr/bin/docker";; 241 | char cmd[256]; 242 | 243 | snprintf(cmd, sizeof(cmd), "%s ps --format '{{.Names}}'", dcmd); 244 | 245 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 246 | 247 | if((fd = popen(cmd, "r")) != NULL) { 248 | char line[1024]; 249 | 250 | if(fgets(line, sizeof(line)-1, (FILE*) fd)) { 251 | char *tmp, *container = strtok_r(line, " ", &tmp); 252 | 253 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, line); 254 | 255 | while(container) { 256 | FILE *fd1; 257 | 258 | container[strlen(container)-1] = '\0'; /* Remove trailing \r */ 259 | snprintf(cmd, sizeof(cmd), "%s exec %s bash -c 'cat /sys/class/net/eth0/iflink'", dcmd, container); 260 | 261 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 262 | 263 | if((fd1 = popen(cmd, "r")) != NULL) { 264 | char netId[64]; 265 | 266 | if(fgets(netId, sizeof(netId)-1, (FILE*)fd1)) { 267 | FILE *fd2; 268 | 269 | netId[strlen(netId)-1] ='\0'; 270 | 271 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, netId); 272 | 273 | snprintf(cmd, sizeof(cmd), "/bin/grep -l %s /sys/class/net/veth*/ifindex", netId); 274 | 275 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 276 | 277 | if((fd2 = popen(cmd, "r")) != NULL) { 278 | char ifname[128]; 279 | 280 | if(fgets(ifname, sizeof(ifname)-1, (FILE*)fd2)) { 281 | char *veth = &ifname[15]; 282 | 283 | veth[11] = '\0'; 284 | 285 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, veth); 286 | 287 | printf("value {arg=0}{value=%s@%s}{display=Container %s}\n", veth, container, container); 288 | found = 1; 289 | } 290 | 291 | fclose(fd2); 292 | } 293 | } else 294 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] No output read :-(\n", __FILE__, __LINE__); 295 | 296 | fclose(fd1); 297 | } else { 298 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Command failed :-(\n", __FILE__, __LINE__); 299 | } 300 | 301 | container = strtok_r(NULL, " ", &tmp); 302 | } 303 | } 304 | 305 | fclose(fd); 306 | } 307 | 308 | 309 | return(found); 310 | } 311 | 312 | /* ***************************************************** */ 313 | 314 | void kubectl_list_interfaces() { 315 | FILE *fd; 316 | int rc ; 317 | struct stat statbuf; 318 | const char *kcmd; 319 | char cmd[256]; 320 | 321 | if(stat("/snap/bin/microk8s.kubectl", &statbuf) == 0) 322 | kcmd = "/snap/bin/microk8s.kubectl"; 323 | else if(stat("/usr/bin/kubectl", &statbuf) == 0) 324 | kcmd = "/usr/bin/kubectl"; 325 | else 326 | return; /* No kubectk */ 327 | 328 | snprintf(cmd, sizeof(cmd), "%s get namespace -o 'jsonpath={.items[*].metadata.name}'", 329 | kcmd); 330 | 331 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 332 | 333 | if((fd = popen(cmd, "r")) != NULL) { 334 | char line[1024]; 335 | 336 | if(fgets(line, sizeof(line)-1, (FILE*) fd)) { 337 | char *tmp, *ns = strtok_r(line, " ", &tmp); 338 | 339 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, line); 340 | 341 | while(ns) { 342 | FILE *fd1; 343 | 344 | snprintf(cmd, sizeof(cmd), "%s get pod --namespace=%s -o jsonpath='{.items[*].metadata.name}'", 345 | kcmd, ns); 346 | 347 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 348 | 349 | if((fd1 = popen(cmd, "r")) != NULL) { 350 | char pod[512]; 351 | 352 | while(fgets(pod, sizeof(pod)-1, (FILE*)fd1)) { 353 | char *tmp, *ns1; 354 | FILE *fd2; 355 | 356 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, pod); 357 | 358 | ns1 = strtok_r(pod, " ", &tmp); 359 | 360 | while(ns1 != NULL) { 361 | snprintf(cmd, sizeof(cmd), 362 | "%s exec %s --namespace=%s -- cat /sys/class/net/eth0/iflink 2>1 /dev/null", 363 | kcmd, ns1, ns); 364 | 365 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 366 | 367 | if((fd2 = popen(cmd, "r")) != NULL) { 368 | char ids[32]; 369 | 370 | while(fgets(ids, sizeof(ids)-1, (FILE*) fd2)) { 371 | FILE *fd3; 372 | 373 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, ids); 374 | 375 | snprintf(cmd, sizeof(cmd), "ip -o link|grep ^%d:|cut -d ':' -f 2|cut -d '@' -f 1|tr -d '[:blank:]' | sed 's/\\n//g'", 376 | atoi(ids)); 377 | 378 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Executing %s\n", __FILE__, __LINE__, cmd); 379 | 380 | if((fd3 = popen(cmd, "r")) != NULL) { 381 | char ifname[32]; 382 | 383 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Pipe open\n", __FILE__, __LINE__); 384 | 385 | while(fgets(ifname, sizeof(ifname)-1, (FILE*) fd3)) { 386 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Read %s\n", __FILE__, __LINE__, ifname); 387 | 388 | ifname[strlen(ifname)-1] = '\0'; 389 | printf("value {arg=0}{value=%s@%s}{display=Pod %s, Namespace %s}\n", ifname, ns1, ns1, ns); 390 | 391 | if(num_all_interfaces < MAX_NUM_INT) 392 | all_interfaces[num_all_interfaces++] = strdup(ifname); 393 | } 394 | 395 | fclose(fd3); 396 | } else { 397 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] popen failed\n", __FILE__, __LINE__); 398 | } 399 | } 400 | 401 | fclose(fd2); 402 | } else { 403 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] popen failed\n", __FILE__, __LINE__); 404 | } 405 | 406 | ns1 = strtok_r(NULL, " ", &tmp); 407 | 408 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] Next NS %s\n", __FILE__, __LINE__, ns1 ? ns1 : ""); 409 | } 410 | } 411 | 412 | fclose(fd1); 413 | } else { 414 | if(log_fp) fprintf(log_fp, "[DEBUG][%s:%u] popen failed\n", __FILE__, __LINE__); 415 | } 416 | 417 | ns = strtok_r(NULL, " ", &tmp); 418 | } 419 | } 420 | 421 | fclose(fd); 422 | } 423 | } 424 | 425 | /* ***************************************************** */ 426 | 427 | void print_pcap_interfaces() { 428 | char errbuf[PCAP_ERRBUF_SIZE]; 429 | pcap_if_t *devpointer; 430 | 431 | if(pcap_findalldevs(&devpointer, errbuf) == 0) { 432 | int i = 0; 433 | 434 | while(devpointer) { 435 | if(devpointer->description == NULL) { 436 | u_int8_t found = 0, i; 437 | 438 | for(i=0; iname) == 0) { 440 | found = 1; 441 | break; 442 | } 443 | 444 | if(!found) 445 | printf("value {arg=0}{value=%s}{display=%s}\n", devpointer->name, devpointer->name); 446 | } 447 | 448 | devpointer = devpointer->next; 449 | } 450 | } 451 | } 452 | 453 | /* ***************************************************** */ 454 | 455 | void extcap_list_all_interfaces() { 456 | u_int i; 457 | 458 | /* Add eBPF-only events */ 459 | #ifdef __linux__ 460 | printf("value {arg=0}{value=%s}{display=eBPF Events}\n", EBPFDUMP_EBPFEVENTS_NAME); 461 | #endif 462 | 463 | printf("value {arg=0}{value=%s}{display=eBPF Remote Events (ZMQ)}\n", EBPFDUMP_EBPFZMQEVENTS_NAME); 464 | 465 | #ifdef __linux__ 466 | /* Print kubernetes containers only if there are no docker containers */ 467 | i = docker_list_interfaces(); 468 | 469 | // if(i == 0) 470 | kubectl_list_interfaces(); 471 | 472 | /* Print additional interfaces */ 473 | print_pcap_interfaces(); 474 | 475 | for(i=0; i\n"); 490 | printf("--extcap-config\n"); 491 | printf("--capture\n"); 492 | printf("--fifo \n"); 493 | printf("--debug\n"); 494 | printf("--name \n"); 495 | printf("--custom-name \n"); 496 | printf("--help\n"); 497 | 498 | return(0); 499 | } 500 | 501 | /* ***************************************************** */ 502 | 503 | void extcap_config() { 504 | u_int argidx = 0; 505 | 506 | if(!extcap_selected_interface) { 507 | extcap_print_help(); 508 | return; 509 | } 510 | 511 | printf("arg {number=0}{call=--ifname}{display=Interface Name}" 512 | "{type=selector}{tooltip=Network Interface from which packets will be captured}\n"); 513 | 514 | extcap_list_all_interfaces(); 515 | } 516 | 517 | /* ***************************************************** */ 518 | 519 | void extcap_list_interfaces() { 520 | u_int i; 521 | 522 | for(i = 0; i < extcap_interfaces_num; i++) 523 | printf("interface {value=%s}{display=%s}\n", 524 | extcap_interfaces[i].interface, 525 | extcap_interfaces[i].description); 526 | } 527 | 528 | /* ***************************************************** */ 529 | 530 | void extcap_dlts() { 531 | int i; 532 | 533 | if(!extcap_selected_interface) return; 534 | for(i = 0; i < extcap_interfaces_num; i++) { 535 | extcap_interface *eif = &extcap_interfaces[i]; 536 | 537 | if(!strncmp(extcap_selected_interface, eif->interface, strlen(eif->interface))) { 538 | printf("dlt {number=%u}{name=%s}{display=%s}\n", 539 | eif->dlt, eif->interface, eif->dltdescription); 540 | break; 541 | } 542 | } 543 | } 544 | 545 | /* ***************************************************** */ 546 | 547 | int exec_head(const char *bin, char *line, size_t line_len) { 548 | FILE *fp; 549 | 550 | fp = popen(bin, "r"); 551 | 552 | if(fp == NULL) 553 | return -1; 554 | 555 | if(fgets(line, line_len-1, fp) == NULL) { 556 | pclose(fp); 557 | return -1; 558 | } 559 | 560 | pclose(fp); 561 | return 0; 562 | } 563 | 564 | /* ***************************************************** */ 565 | 566 | float wireshark_version() { 567 | char line[1035]; 568 | char *version, *rev; 569 | float v = 0; 570 | 571 | if(exec_head("/usr/bin/wireshark -v", line, sizeof(line)) != 0 && 572 | exec_head("/usr/local/bin/wireshark -v", line, sizeof(line)) != 0) 573 | return 0; 574 | 575 | version = strchr(line, ' '); 576 | if(version == NULL) return 0; 577 | version++; 578 | rev = strchr(version, '.'); 579 | if(rev == NULL) return 0; 580 | rev++; 581 | rev = strchr(rev, '.'); 582 | if(rev == NULL) return 0; 583 | *rev = '\0'; 584 | 585 | sscanf(version, "%f", &v); 586 | 587 | return v; 588 | } 589 | 590 | /* ***************************************************** */ 591 | 592 | /* ******************************************** */ 593 | // ===== ===== IP ADDRESS TO STRING ===== ===== // 594 | /* ******************************************** */ 595 | 596 | static char* intoaV4(unsigned int addr, char* buf, u_short bufLen) { 597 | char *cp, *retStr; 598 | int n; 599 | 600 | cp = &buf[bufLen]; 601 | *--cp = '\0'; 602 | 603 | n = 4; 604 | do { 605 | u_int byte = addr & 0xff; 606 | 607 | *--cp = byte % 10 + '0'; 608 | byte /= 10; 609 | if(byte > 0) { 610 | *--cp = byte % 10 + '0'; 611 | byte /= 10; 612 | if(byte > 0) 613 | *--cp = byte + '0'; 614 | } 615 | *--cp = '.'; 616 | addr >>= 8; 617 | } while (--n > 0); 618 | 619 | /* Convert the string to lowercase */ 620 | retStr = (char*)(cp+1); 621 | 622 | return(retStr); 623 | } 624 | 625 | /* ***************************************************** */ 626 | 627 | static char* intoaV6(void *addr, char* buf, u_short bufLen) { 628 | char *ret = (char*)inet_ntop(AF_INET6, addr, buf, bufLen); 629 | 630 | if(ret == NULL) 631 | buf[0] = '\0'; 632 | 633 | return(buf); 634 | } 635 | 636 | /* ***************************************************** */ 637 | 638 | static void IPV4Handler(eBPFevent *e, struct ipv4_addr_t *event, u_int32_t *hashval) { 639 | if(log_fp) { 640 | char buf1[32], buf2[32]; 641 | 642 | fprintf(log_fp, "[addr: %s:%u <-> %s:%u]\n", 643 | intoaV4(htonl(event->saddr), buf1, sizeof(buf1)), e->sport, 644 | intoaV4(htonl(event->daddr), buf2, sizeof(buf2)), e->dport); 645 | } 646 | 647 | *hashval = e->proto + ntohl(event->saddr) + ntohl(event->daddr) + e->sport + e->dport; 648 | } 649 | 650 | /* ***************************************************** */ 651 | 652 | static void IPV6Handler(eBPFevent *e, struct ipv6_addr_t *event, u_int32_t *hashval) { 653 | u_int32_t *s = (u_int32_t*)&event->saddr; 654 | u_int32_t *d = (u_int32_t*)&event->daddr; 655 | 656 | if(log_fp) { 657 | char buf1[128], buf2[128]; 658 | 659 | fprintf(log_fp, "[addr: %s:%u <-> %s:%u]\n", 660 | intoaV6(&event->saddr, buf1, sizeof(buf1)), e->sport, 661 | intoaV6(&event->daddr, buf2, sizeof(buf2)), e->dport); 662 | } 663 | 664 | *hashval = e->proto + e->sport + e->dport; 665 | 666 | *hashval += ntohl(s[0]) + ntohl(s[1]) + ntohl(s[2]) + ntohl(s[3]) 667 | + ntohl(d[0]) + ntohl(d[1]) + ntohl(d[2]) + ntohl(d[3]); 668 | } 669 | 670 | /* ***************************************************** */ 671 | 672 | static void ebpf_process_event(void* t_bpfctx, void* t_data, int t_datasize) { 673 | eBPFevent *e = (eBPFevent*)t_data; 674 | u_int len = sizeof(eBPFevent)+32; 675 | char buf[len]; 676 | struct timespec tp; 677 | struct timeval now; 678 | u_int64_t bytes_written = 0; 679 | int err; 680 | u_int32_t *null_sock_type = (u_int32_t*)buf; 681 | eBPFevent event; 682 | 683 | memcpy(&event, e, sizeof(eBPFevent)); /* Copy needed as ebpf_preprocess_event will modify the memory */ 684 | 685 | #ifdef __linux__ 686 | if(t_bpfctx) ebpf_preprocess_event(&event); 687 | #endif 688 | 689 | gettimeofday(&now, NULL); 690 | 691 | *null_sock_type = htonl(SOCKET_LIBEBPF); 692 | 693 | if(log_fp) 694 | fprintf(log_fp, "[ifname: %s][extcap: %s/pcap: %s]\n", 695 | event.ifname, 696 | extcap_selected_interface ? extcap_selected_interface : "", 697 | pcap_selected_interface ? pcap_selected_interface : ""); 698 | 699 | if(/* extcap_selected_interface || */ 700 | pcap_selected_interface 701 | || (containerId && (strstr(event.container_id, containerId))) 702 | || (containerId && (!strcmp(event.kube.pod, containerId))) 703 | ) { 704 | /* 705 | We are capturing from a physical interface and here we need 706 | to glue events with packets 707 | */ 708 | 709 | if(log_fp) fprintf(log_fp, "==> [%s][%s][%s][%s][%s][%s]\n", 710 | event.container_id, containerId, event.docker.name, 711 | event.kube.name, event.kube.pod, event.kube.ns); 712 | 713 | if( 714 | (extcap_selected_interface && (strcmp(event.ifname, extcap_selected_interface) == 0)) 715 | || (pcap_selected_interface && (strcmp(event.ifname, pcap_selected_interface) == 0)) 716 | || (containerId && event.docker.name && (!strcmp(event.docker.name, containerId))) 717 | || (containerId && event.kube.pod && (!strcmp(event.kube.pod, containerId))) 718 | ) { 719 | u_int32_t hashval = 0; 720 | struct ebpf_event evt; 721 | 722 | if(log_fp) { 723 | printf("[%s][%s][IPv4/%s][pid/tid: %u/%u [%s], uid/gid: %u/%u]" 724 | "[father pid/tid: %u/%u [%s], uid/gid: %u/%u]", 725 | event.ifname, event.sent_packet ? "Sent" : "Rcvd", 726 | (event.proto == IPPROTO_TCP) ? "TCP" : "UDP", 727 | event.proc.pid, event.proc.tid, 728 | (event.proc.full_task_path == NULL) ? event.proc.task : event.proc.full_task_path, 729 | event.proc.uid, event.proc.gid, 730 | event.father.pid, event.father.tid, 731 | (event.father.full_task_path == NULL) ? event.father.task : event.father.full_task_path, 732 | event.father.uid, event.father.gid); 733 | 734 | if(event.ip_version == 4) 735 | IPV4Handler(&event, &event.addr.v4, &hashval); 736 | else 737 | IPV6Handler(&event, &event.addr.v6, &hashval); 738 | 739 | if(event.container_id[0] != '\0') { 740 | printf("[containerID: %s]", event.container_id); 741 | 742 | if(event.docker.name != NULL) 743 | printf("[docker_name: %s]", event.docker.name); 744 | 745 | if(event.kube.ns) printf("[kube_name: %s]", event.kube.name); 746 | if(event.kube.pod) printf("[kube_pod: %s]", event.kube.pod); 747 | if(event.kube.ns) printf("[kube_ns: %s]", event.kube.ns); 748 | } 749 | 750 | printf("[hashval: %u]\n", hashval); 751 | } 752 | 753 | if(!lru_find_cache(&received_events, hashval, &evt)) { 754 | u_int l; /* Trick to avoid silly compiler warnings */ 755 | 756 | memset(&evt, 0, sizeof(evt)); 757 | 758 | evt.pid = event.proc.pid, evt.tid = event.proc.tid, 759 | evt.uid = event.proc.uid, evt.gid = event.proc.gid; 760 | 761 | l = min(sizeof(evt.process_name), strlen(event.proc.task)); 762 | memcpy(evt.process_name, event.proc.task, l); 763 | 764 | l = min(sizeof(evt.container_id), strlen(event.container_id)); 765 | memcpy(evt.container_id, event.container_id, l); 766 | 767 | if(log_fp) 768 | fprintf(log_fp, "========>>>>>> Adding %u [process_name: %s][container_id: %s][pid: %u][tid: %u][uid: %u][gid: %u]\n", 769 | hashval, evt.process_name, evt.container_id, 770 | evt.pid, evt.tid, evt.uid, evt.gid); 771 | 772 | lru_add_to_cache(&received_events, hashval, &evt); 773 | // printf("++++ Adding %u\n", hashval); 774 | } 775 | 776 | /* ************************************************* */ 777 | 778 | /* Uncomment for dumping events with packets */ 779 | #if 0 780 | memset(buf, 0, 14); 781 | memcpy(&buf[14], &event, sizeof(eBPFevent)); 782 | if(!libpcap_write_packet(fp, now.tv_sec, now.tv_usec, len, len, 783 | (const u_int8_t*)buf, &bytes_written, &err)) { 784 | time_t now = time(NULL); 785 | fprintf(stderr, "Error while writing packet @ %s", ctime(&now)); 786 | } else 787 | fflush(fp); /* Flush buffer */ 788 | #endif 789 | 790 | /* ************************************************* */ 791 | } else { 792 | if(log_fp) 793 | printf("Skipping event for interface %s\n", event.ifname); 794 | } 795 | } else { 796 | memcpy(&buf[4], &event, sizeof(eBPFevent)); 797 | 798 | if(!libpcap_write_packet(fp, now.tv_sec, now.tv_usec, len, len, 799 | (const u_int8_t*)buf, &bytes_written, &err)) { 800 | time_t now = time(NULL); 801 | fprintf(stderr, "Error while writing packet @ %s", ctime(&now)); 802 | } else 803 | fflush(fp); /* Flush buffer */ 804 | } 805 | 806 | #ifdef __linux__ 807 | if(t_bpfctx) ebpf_free_event(&event); 808 | #endif 809 | } 810 | 811 | /* ****************************************************** */ 812 | 813 | /* 814 | * A faster replacement for inet_ntoa(). 815 | */ 816 | char* __intoa(unsigned int addr, char* buf, u_short bufLen) { 817 | char *cp, *retStr; 818 | u_int byte; 819 | int n; 820 | 821 | cp = &buf[bufLen]; 822 | *--cp = '\0'; 823 | 824 | n = 4; 825 | do { 826 | byte = addr & 0xff; 827 | *--cp = byte % 10 + '0'; 828 | byte /= 10; 829 | if (byte > 0) { 830 | *--cp = byte % 10 + '0'; 831 | byte /= 10; 832 | if (byte > 0) 833 | *--cp = byte + '0'; 834 | } 835 | *--cp = '.'; 836 | addr >>= 8; 837 | } while (--n > 0); 838 | 839 | /* Convert the string to lowercase */ 840 | retStr = (char*)(cp+1); 841 | 842 | return(retStr); 843 | } 844 | 845 | /* ************************************ */ 846 | 847 | static char buf[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"]; 848 | 849 | char* intoa(unsigned int addr) { 850 | return(__intoa(addr, buf, sizeof(buf))); 851 | } 852 | 853 | /* ************************************ */ 854 | 855 | static inline char* in6toa(struct in6_addr addr6) { 856 | snprintf(buf, sizeof(buf), 857 | "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", 858 | addr6.s6_addr[0], addr6.s6_addr[1], addr6.s6_addr[2], 859 | addr6.s6_addr[3], addr6.s6_addr[4], addr6.s6_addr[5], addr6.s6_addr[6], 860 | addr6.s6_addr[7], addr6.s6_addr[8], addr6.s6_addr[9], addr6.s6_addr[10], 861 | addr6.s6_addr[11], addr6.s6_addr[12], addr6.s6_addr[13], addr6.s6_addr[14], 862 | addr6.s6_addr[15]); 863 | 864 | return(buf); 865 | } 866 | 867 | /* *************************************** */ 868 | 869 | int32_t gmt_to_local(time_t t) { 870 | int dt, dir; 871 | struct tm *gmt, *loc; 872 | struct tm sgmt; 873 | 874 | if (t == 0) 875 | t = time(NULL); 876 | gmt = &sgmt; 877 | *gmt = *gmtime(&t); 878 | loc = localtime(&t); 879 | dt = (loc->tm_hour - gmt->tm_hour) * 60 * 60 + 880 | (loc->tm_min - gmt->tm_min) * 60; 881 | 882 | /* 883 | * If the year or julian day is different, we span 00:00 GMT 884 | * and must add or subtract a day. Check the year first to 885 | * avoid problems when the julian day wraps. 886 | */ 887 | dir = loc->tm_year - gmt->tm_year; 888 | if (dir == 0) 889 | dir = loc->tm_yday - gmt->tm_yday; 890 | dt += dir * 24 * 60 * 60; 891 | 892 | return (dt); 893 | } 894 | 895 | /* ****************************************************** */ 896 | 897 | const char* proto2str(u_short proto) { 898 | static char protoName[8]; 899 | 900 | switch(proto) { 901 | case IPPROTO_TCP: return("TCP"); 902 | case IPPROTO_UDP: return("UDP"); 903 | case IPPROTO_ICMP: return("ICMP"); 904 | default: 905 | snprintf(protoName, sizeof(protoName), "%d", proto); 906 | return(protoName); 907 | } 908 | } 909 | 910 | /* ****************************************************** */ 911 | 912 | static char hex[] = "0123456789ABCDEF"; 913 | 914 | char* etheraddr_string(const u_char *ep, char *buf) { 915 | u_int i, j; 916 | char *cp; 917 | 918 | cp = buf; 919 | if ((j = *ep >> 4) != 0) 920 | *cp++ = hex[j]; 921 | else 922 | *cp++ = '0'; 923 | 924 | *cp++ = hex[*ep++ & 0xf]; 925 | 926 | for(i = 5; (int)--i >= 0;) { 927 | *cp++ = ':'; 928 | if ((j = *ep >> 4) != 0) 929 | *cp++ = hex[j]; 930 | else 931 | *cp++ = '0'; 932 | 933 | *cp++ = hex[*ep++ & 0xf]; 934 | } 935 | 936 | *cp = '\0'; 937 | return (buf); 938 | } 939 | 940 | /* ***************************************************** */ 941 | 942 | void pcap_processs_packet(u_char *_deviceId, 943 | const struct pcap_pkthdr *h, 944 | const u_char *pkt) { 945 | struct ether_header ehdr; 946 | u_short eth_type, vlan_id; 947 | const u_char *p = pkt; 948 | char buf1[32], buf2[32]; 949 | int s; 950 | struct tcphdr *tp; 951 | struct udphdr *up; 952 | u_int8_t proto = 0; 953 | u_int32_t hashval = 0; 954 | u_int64_t bytes_written = 0; 955 | int err; 956 | 957 | s = (h->ts.tv_sec + thiszone) % 86400; 958 | 959 | if(log_fp) 960 | fprintf(log_fp, "%02d:%02d:%02d.%06u ", 961 | s / 3600, (s % 3600) / 60, s % 60, 962 | (unsigned)h->ts.tv_usec); 963 | 964 | memcpy(&ehdr, p, sizeof(struct ether_header)); 965 | eth_type = ntohs(ehdr.ether_type); 966 | 967 | if(log_fp) 968 | fprintf(log_fp, "[%s -> %s] ", 969 | etheraddr_string(ehdr.ether_shost, buf1), 970 | etheraddr_string(ehdr.ether_dhost, buf2)); 971 | 972 | if(eth_type == 0x8100) { 973 | vlan_id = (p[14] & 15)*256 + p[15]; 974 | eth_type = (p[16])*256 + p[17]; 975 | 976 | if(log_fp) 977 | fprintf(log_fp, "[vlan %u] ", vlan_id); 978 | 979 | p += 4; 980 | } 981 | 982 | p += sizeof(ehdr); 983 | 984 | if(eth_type == 0x0800) { 985 | struct ip *ip = (struct ip*)p; 986 | 987 | proto = ip->ip_p; 988 | 989 | if(log_fp) { 990 | fprintf(log_fp, "[%s]", proto2str(ip->ip_p)); 991 | fprintf(log_fp, "[%s ", intoa(ntohl(ip->ip_src.s_addr))); 992 | fprintf(log_fp, "-> %s]", intoa(ntohl(ip->ip_dst.s_addr))); 993 | } 994 | 995 | hashval = proto + ntohl(ip->ip_src.s_addr) + ntohl(ip->ip_dst.s_addr); 996 | 997 | p += ((u_int16_t)ip->ip_hl * 4); 998 | } else if(eth_type == 0x86DD) { 999 | struct ip6_hdr *ip6 = (struct ip6_hdr*)p; 1000 | 1001 | proto = ip6->ip6_nxt; 1002 | 1003 | if(log_fp) { 1004 | fprintf(log_fp, "[%s ", in6toa(ip6->ip6_src)); 1005 | fprintf(log_fp, "-> %s]", in6toa(ip6->ip6_dst)); 1006 | } 1007 | 1008 | hashval = proto 1009 | + ntohl(ip6->ip6_src.s6_addr32[0]) 1010 | + ntohl(ip6->ip6_src.s6_addr32[1]) 1011 | + ntohl(ip6->ip6_src.s6_addr32[2]) 1012 | + ntohl(ip6->ip6_src.s6_addr32[3]) 1013 | + ntohl(ip6->ip6_dst.s6_addr32[0]) 1014 | + ntohl(ip6->ip6_dst.s6_addr32[1]) 1015 | + ntohl(ip6->ip6_dst.s6_addr32[2]) 1016 | + ntohl(ip6->ip6_dst.s6_addr32[3]); 1017 | 1018 | p += sizeof(struct ip6_hdr)+htons(ip6->ip6_plen); 1019 | } else if(eth_type == 0x0806) { 1020 | if(log_fp) 1021 | fprintf(log_fp, "[ARP]"); 1022 | } else { 1023 | if(log_fp) 1024 | fprintf(log_fp, "[eth_type=0x%04X]", eth_type); 1025 | } 1026 | 1027 | if(proto) { 1028 | if(log_fp) fprintf(log_fp, "[%s]", proto2str(proto)); 1029 | 1030 | switch(proto) { 1031 | case IPPROTO_TCP: 1032 | { 1033 | struct tcphdr *t = (struct tcphdr*)p; 1034 | 1035 | if(log_fp) 1036 | fprintf(log_fp, "[%u -> %u]", ntohs(t->th_sport), ntohs(t->th_dport)); 1037 | 1038 | hashval += ntohs(t->th_sport) + ntohs(t->th_dport); 1039 | } 1040 | break; 1041 | case IPPROTO_UDP: 1042 | { 1043 | struct udphdr *u = (struct udphdr*)p;; 1044 | 1045 | if(log_fp) 1046 | fprintf(log_fp, "[%u -> %u]", ntohs(u->uh_sport), ntohs(u->uh_dport)); 1047 | 1048 | hashval += ntohs(u->uh_sport) + ntohs(u->uh_dport); 1049 | } 1050 | break; 1051 | } 1052 | } 1053 | 1054 | if(log_fp) 1055 | fprintf(log_fp, "[caplen=%u][len=%u][hashval=%u]\n", h->caplen, h->len, hashval); 1056 | 1057 | // if(log_fp) fprintf(log_fp, "[caplen=%u][len=%u][hashval=%u]\n", h->caplen, h->len, hashval); 1058 | 1059 | if(fp) { 1060 | struct ebpf_event evt; 1061 | 1062 | if(lru_find_cache(&received_events, hashval, &evt)) { 1063 | char *packet; 1064 | u_int new_len = h->caplen + sizeof(struct ebpf_event) + 2; 1065 | 1066 | // fprintf(log_fp, "++++ Found %u\n", hashval); 1067 | 1068 | packet = (char*)malloc(new_len); 1069 | 1070 | if(packet) { 1071 | if(log_fp) 1072 | fprintf(log_fp, "========>>>>>> Reading %u [process_name: %s][container_id: %s][pid: %u][tid: %u][uid: %u][gid: %u][len: %u -> %u]\n", 1073 | hashval, evt.process_name, evt.container_id, 1074 | evt.pid, evt.tid, evt.uid, evt.gid, 1075 | h->caplen, new_len); 1076 | 1077 | memcpy(packet, pkt, h->caplen); 1078 | packet[h->caplen] = 0x19; 1079 | packet[h->caplen+1] = 0x68; 1080 | memcpy(&packet[h->caplen+2], &evt, sizeof(evt)); 1081 | 1082 | if(!libpcap_write_packet(fp, h->ts.tv_sec, h->ts.tv_usec, new_len, new_len, 1083 | (const u_int8_t*)packet, &bytes_written, &err)) { 1084 | time_t now = time(NULL); 1085 | fprintf(stderr, "Error while writing packet @ %s", ctime(&now)); 1086 | } else 1087 | fflush(fp); /* Flush buffer */ 1088 | 1089 | free(packet); 1090 | } 1091 | } else { 1092 | if(!libpcap_write_packet(fp, h->ts.tv_sec, h->ts.tv_usec, h->caplen, h->len, 1093 | (const u_int8_t*)pkt, &bytes_written, &err)) { 1094 | time_t now = time(NULL); 1095 | fprintf(stderr, "Error while writing packet @ %s", ctime(&now)); 1096 | } else 1097 | fflush(fp); /* Flush buffer */ 1098 | } 1099 | } 1100 | } 1101 | 1102 | /* ***************************************************** */ 1103 | 1104 | void extcap_capture() { 1105 | ebpfRetCode rc; 1106 | void *ebpf; 1107 | u_int num = 0; 1108 | u_int64_t bytes_written = 0; 1109 | int err; 1110 | u_int8_t success; 1111 | pcap_t *pd = NULL; 1112 | int promisc = 1; 1113 | int snaplen = 1600; 1114 | char errbuf[PCAP_ERRBUF_SIZE]; 1115 | 1116 | if(log_fp) 1117 | fprintf(log_fp, "[DEBUG][%s:%u] Capturing [ifname: %s][fifo: %s]\n", 1118 | __FILE__, __LINE__, 1119 | pcap_selected_interface ? pcap_selected_interface : "", 1120 | extcap_capture_fifo ? extcap_capture_fifo : ""); 1121 | 1122 | if(log_fp) 1123 | fprintf(log_fp, "[DEBUG][%s:%u] Capturing [ifname: %s][fifo: %s]\n", 1124 | __FILE__, __LINE__, 1125 | pcap_selected_interface ? pcap_selected_interface : "", 1126 | extcap_capture_fifo ? extcap_capture_fifo : ""); 1127 | 1128 | if(pcap_selected_interface && (strcmp(pcap_selected_interface, EBPFDUMP_EBPFZMQEVENTS_NAME) == 0)) { 1129 | if(initZMQ() != 0) 1130 | return; 1131 | else 1132 | pcap_selected_interface = NULL; /* Trick to semplify the rest of the code */ 1133 | } else { 1134 | #ifdef __linux__ 1135 | ebpf = init_ebpf_flow(NULL, ebpf_process_event, &rc, 0xFFFF); 1136 | 1137 | if(ebpf == NULL) { 1138 | fprintf(stderr, "Unable to initialize libebpfflow\n"); 1139 | return; 1140 | } 1141 | #endif 1142 | } 1143 | 1144 | if(pcap_selected_interface) { 1145 | char *at = strchr(pcap_selected_interface, '@'); 1146 | 1147 | if(at) { 1148 | at[0] = '\0'; 1149 | containerId = &at[1]; 1150 | } 1151 | } 1152 | 1153 | if((fp = fopen(extcap_capture_fifo, "wb")) == NULL) { 1154 | fprintf(stderr, "Unable to create file %s", extcap_capture_fifo); 1155 | return; 1156 | } 1157 | 1158 | if(!libpcap_write_file_header(fp, 1159 | pcap_selected_interface ? DLT_EN10MB : 0 /* DLT_NULL */, 1160 | pcap_selected_interface ? 2048: sizeof(eBPFevent), FALSE, &bytes_written, &err)) { 1161 | fprintf(stderr, "Unable to write file %s header", extcap_capture_fifo); 1162 | return; 1163 | } 1164 | 1165 | if((signal(SIGINT, sigproc) == SIG_ERR) 1166 | || (signal(SIGTERM, sigproc) == SIG_ERR) 1167 | || (signal(SIGQUIT, sigproc) == SIG_ERR)) { 1168 | fprintf(stderr, "Unable to install SIGINT/SIGTERM signal handler"); 1169 | return; 1170 | } 1171 | 1172 | if(pcap_selected_interface) { 1173 | if((pd = pcap_open_live(pcap_selected_interface, snaplen, promisc, 1, errbuf)) == NULL) { 1174 | printf("pcap_open_live: %s\n", errbuf); 1175 | return; 1176 | } 1177 | 1178 | if(log_fp) fprintf(log_fp, "Reading packets from %s\n", pcap_selected_interface); 1179 | 1180 | while(1) { 1181 | if(pcap_dispatch(pd, 1, pcap_processs_packet, NULL) < 0) break; 1182 | #ifdef __linux__ 1183 | ebpf_poll_event(ebpf, 1); 1184 | #endif 1185 | } 1186 | 1187 | pcap_close(pd); 1188 | } else { 1189 | /* eBPF-only capture */ 1190 | if(zmq.initialized) { 1191 | /* We need to poll events via ZMQ */ 1192 | 1193 | while(1) { 1194 | struct zmq_msg_hdr h; 1195 | int size; 1196 | 1197 | #if 0 1198 | char json_str[2048]; 1199 | 1200 | size = zmq_recv(zmq.z_socket, &h, sizeof(h), 0); 1201 | zmq_recv(zmq.z_socket, json_str, h.size, 0); 1202 | if(log_fp) fprintf(log_fp, "%s\n", json_str); 1203 | json_str[h.size] = '\0'; 1204 | printf("%s\n", json_str); 1205 | #else 1206 | eBPFevent event; 1207 | 1208 | size = zmq_recv(zmq.z_socket, &h, sizeof(h), 0); 1209 | zmq_recv(zmq.z_socket, &event, h.size, 0); 1210 | // printf("Received %u bytes event\n", h.size); 1211 | ebpf_process_event(NULL, (void*)&event, h.size); 1212 | #endif 1213 | } 1214 | } else { 1215 | #ifdef __linux__ 1216 | while(1) { 1217 | /* fprintf(stderr, "%u\n", ++num); */ 1218 | ebpf_poll_event(ebpf, 10); 1219 | } 1220 | #endif 1221 | } 1222 | } 1223 | 1224 | #ifdef __linux__ 1225 | if(!zmq.initialized) 1226 | term_ebpf_flow(ebpf); 1227 | #endif 1228 | 1229 | fclose(fp); 1230 | } 1231 | 1232 | /* ***************************************************** */ 1233 | 1234 | int main(int argc, char *argv[]) { 1235 | int option_idx = 0, result; 1236 | time_t epoch; 1237 | char date_str[EBPFDUMP_MAX_DATE_LEN]; 1238 | struct tm* tm_info; 1239 | 1240 | memset(&zmq, 0, sizeof(zmq)); 1241 | 1242 | thiszone = gmt_to_local(0); 1243 | lru_cache_init(&received_events); 1244 | 1245 | log_fp = fopen("/tmp/ebpfdump.log", "w"); 1246 | 1247 | #if 0 1248 | /* test code */ 1249 | if(0) { 1250 | eBPFevent x; 1251 | 1252 | printf("%d\n", offsetof(eBPFevent, proc)); 1253 | printf("%d\n", offsetof(eBPFevent, father)); 1254 | 1255 | return(0); 1256 | } 1257 | #endif 1258 | 1259 | if(argc == 1) { 1260 | extcap_print_help(); 1261 | return EXIT_SUCCESS; 1262 | } 1263 | 1264 | u_int defer_dlts = 0, defer_config = 0, defer_capture = 0; 1265 | while((result = getopt_long(argc, argv, "h", longopts, &option_idx)) != -1) { 1266 | // fprintf(stderr, "OPT: '%c' VAL: '%s' \n", result, optarg != NULL ? optarg : ""); 1267 | 1268 | switch(result) { 1269 | /* mandatory extcap options */ 1270 | case EXTCAP_OPT_DEBUG: 1271 | break; 1272 | case EXTCAP_OPT_LIST_INTERFACES: 1273 | extcap_version(); 1274 | extcap_list_interfaces(); 1275 | defer_dlts = defer_config = defer_capture = 0; 1276 | break; 1277 | case EXTCAP_OPT_VERSION: 1278 | extcap_version(); 1279 | defer_dlts = defer_config = defer_capture = 0; 1280 | break; 1281 | case EXTCAP_OPT_LIST_DLTS: 1282 | defer_dlts = 1; defer_config = defer_capture = 0; 1283 | break; 1284 | case EXTCAP_OPT_INTERFACE: 1285 | extcap_selected_interface = strndup(optarg, EBPFDUMP_MAX_NAME_LEN); 1286 | break; 1287 | case EXTCAP_OPT_CONFIG: 1288 | defer_config = 1; defer_dlts = defer_capture = 0; 1289 | break; 1290 | case EXTCAP_OPT_CAPTURE: 1291 | defer_capture = 1; defer_dlts = defer_config = 0; 1292 | break; 1293 | break; 1294 | case EXTCAP_OPT_FIFO: 1295 | extcap_capture_fifo = strdup(optarg); 1296 | break; 1297 | 1298 | /* custom ebpfdump options */ 1299 | case EBPFDUMP_OPT_IFNAME: 1300 | if(strcmp(optarg, "ebpfevents") != 0) 1301 | pcap_selected_interface = strdup(optarg); 1302 | break; 1303 | 1304 | case EBPFDUMP_OPT_HELP: 1305 | extcap_print_help(); 1306 | return EXIT_SUCCESS; 1307 | break; 1308 | } 1309 | } 1310 | 1311 | if(defer_dlts) extcap_dlts(); 1312 | else if(defer_config) extcap_config(); 1313 | else if(defer_capture) extcap_capture(); 1314 | 1315 | if(extcap_selected_interface) free(extcap_selected_interface); 1316 | if(extcap_capture_fifo) free(extcap_capture_fifo); 1317 | 1318 | if(log_fp) fclose(log_fp); 1319 | 1320 | termZMQ(); 1321 | 1322 | return EXIT_SUCCESS; 1323 | } 1324 | -------------------------------------------------------------------------------- /wireshark/pcapio.c: -------------------------------------------------------------------------------- 1 | /* pcapio.c 2 | * Our own private code for writing libpcap files when capturing. 3 | * 4 | * We have these because we want a way to open a stream for output given 5 | * only a file descriptor. libpcap 0.9[.x] has "pcap_dump_fopen()", which 6 | * provides that, but 7 | * 8 | * 1) earlier versions of libpcap doesn't have it 9 | * 10 | * and 11 | * 12 | * 2) WinPcap doesn't have it, because a file descriptor opened 13 | * by code built for one version of the MSVC++ C library 14 | * can't be used by library routines built for another version 15 | * (e.g., threaded vs. unthreaded). 16 | * 17 | * Libpcap's pcap_dump() also doesn't return any error indications. 18 | * 19 | * Wireshark - Network traffic analyzer 20 | * By Gerald Combs 21 | * Copyright 1998 Gerald Combs 22 | * 23 | * Derived from code in the Wiretap Library 24 | * Copyright (c) 1998 by Gilbert Ramirez 25 | * 26 | * Modified by ntop - September 2019 27 | * 28 | * SPDX-License-Identifier: GPL-2.0-or-later 29 | */ 30 | 31 | #ifdef __linux__ 32 | #include 33 | #endif 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifdef _WIN32 41 | #include 42 | #endif 43 | 44 | #include "pcapio.h" 45 | 46 | /* Magic numbers in "libpcap" files. 47 | 48 | "libpcap" file records are written in the byte order of the host that 49 | writes them, and the reader is expected to fix this up. 50 | 51 | PCAP_MAGIC is the magic number, in host byte order; PCAP_SWAPPED_MAGIC 52 | is a byte-swapped version of that. 53 | 54 | PCAP_NSEC_MAGIC is for Ulf Lamping's modified "libpcap" format, 55 | which uses the same common file format as PCAP_MAGIC, but the 56 | timestamps are saved in nanosecond resolution instead of microseconds. 57 | PCAP_SWAPPED_NSEC_MAGIC is a byte-swapped version of that. */ 58 | #define PCAP_MAGIC 0xa1b2c3d4 59 | #define PCAP_SWAPPED_MAGIC 0xd4c3b2a1 60 | #define PCAP_NSEC_MAGIC 0xa1b23c4d 61 | #define PCAP_SWAPPED_NSEC_MAGIC 0x4d3cb2a1 62 | 63 | /* "libpcap" file header. */ 64 | struct pcap_hdr { 65 | u_int32_t magic; /* magic number */ 66 | u_int16_t version_major; /* major version number */ 67 | u_int16_t version_minor; /* minor version number */ 68 | u_int32_t thiszone; /* GMT to local correction */ 69 | u_int32_t sigfigs; /* accuracy of timestamps */ 70 | u_int32_t snaplen; /* max length of captured packets, in octets */ 71 | u_int32_t network; /* data link type */ 72 | }; 73 | 74 | /* "libpcap" record header. */ 75 | struct pcaprec_hdr { 76 | u_int32_t ts_sec; /* timestamp seconds */ 77 | u_int32_t ts_usec; /* timestamp microseconds (nsecs for PCAP_NSEC_MAGIC) */ 78 | u_int32_t incl_len; /* number of octets of packet saved in file */ 79 | u_int32_t orig_len; /* actual length of packet */ 80 | }; 81 | 82 | /* Magic numbers in ".pcapng" files. 83 | * 84 | * .pcapng file records are written in the byte order of the host that 85 | * writes them, and the reader is expected to fix this up. 86 | * PCAPNG_MAGIC is the magic number, in host byte order; 87 | * PCAPNG_SWAPPED_MAGIC is a byte-swapped version of that. 88 | */ 89 | #define PCAPNG_MAGIC 0x1A2B3C4D 90 | #define PCAPNG_SWAPPED_MAGIC 0x4D3C2B1A 91 | 92 | /* Currently we are only supporting the initial version of 93 | the file format. */ 94 | #define PCAPNG_MAJOR_VERSION 1 95 | #define PCAPNG_MINOR_VERSION 0 96 | 97 | /* Section Header Block without options and trailing Block Total Length */ 98 | struct shb { 99 | u_int32_t block_type; 100 | u_int32_t block_total_length; 101 | u_int32_t byte_order_magic; 102 | u_int16_t major_version; 103 | u_int16_t minor_version; 104 | u_int64_t section_length; 105 | }; 106 | #define SECTION_HEADER_BLOCK_TYPE 0x0A0D0D0A 107 | 108 | /* Interface Description Block without options and trailing Block Total Length */ 109 | struct idb { 110 | u_int32_t block_type; 111 | u_int32_t block_total_length; 112 | u_int16_t link_type; 113 | u_int16_t reserved; 114 | u_int32_t snap_len; 115 | }; 116 | #define INTERFACE_DESCRIPTION_BLOCK_TYPE 0x00000001 117 | 118 | /* Interface Statistics Block without actual packet, options, and trailing 119 | Block Total Length */ 120 | struct isb { 121 | u_int32_t block_type; 122 | u_int32_t block_total_length; 123 | u_int32_t interface_id; 124 | u_int32_t timestamp_high; 125 | u_int32_t timestamp_low; 126 | }; 127 | #define INTERFACE_STATISTICS_BLOCK_TYPE 0x00000005 128 | 129 | /* Enhanced Packet Block without actual packet, options, and trailing 130 | Block Total Length */ 131 | struct epb { 132 | u_int32_t block_type; 133 | u_int32_t block_total_length; 134 | u_int32_t interface_id; 135 | u_int32_t timestamp_high; 136 | u_int32_t timestamp_low; 137 | u_int32_t captured_len; 138 | u_int32_t packet_len; 139 | }; 140 | #define ENHANCED_PACKET_BLOCK_TYPE 0x00000006 141 | 142 | struct pcap_option { 143 | u_int16_t type; 144 | u_int16_t value_length; 145 | }; 146 | #define OPT_ENDOFOPT 0 147 | #define OPT_COMMENT 1 148 | #define EPB_FLAGS 2 149 | #define SHB_HARDWARE 2 /* currently not used */ 150 | #define SHB_OS 3 151 | #define SHB_USERAPPL 4 152 | #define IDB_NAME 2 153 | #define IDB_DESCRIPTION 3 154 | #define IDB_IF_SPEED 8 155 | #define IDB_TSRESOL 9 156 | #define IDB_FILTER 11 157 | #define IDB_OS 12 158 | #define ISB_STARTTIME 2 159 | #define ISB_ENDTIME 3 160 | #define ISB_IFRECV 4 161 | #define ISB_IFDROP 5 162 | #define ISB_FILTERACCEPT 6 163 | #define ISB_OSDROP 7 164 | #define ISB_USRDELIV 8 165 | #define ADD_PADDING(x) ((((x) + 3) >> 2) << 2) 166 | 167 | #ifndef TRUE 168 | #define TRUE 1 169 | #endif 170 | 171 | #ifndef FALSE 172 | #define FALSE 0 173 | #endif 174 | 175 | #ifndef G_MAXUINT16 176 | #define G_MAXUINT16 ((u_int16_t)-1) 177 | #endif 178 | 179 | #ifndef G_MAXUINT64 180 | #define G_MAXUINT64 ((u_int64_t)-1) 181 | #endif 182 | 183 | /* Write to capture file */ 184 | static u_int8_t 185 | write_to_file(FILE* pfile, const u_int8_t* data, size_t data_length, 186 | u_int64_t *bytes_written, int *err) 187 | { 188 | size_t nwritten; 189 | 190 | nwritten = fwrite(data, data_length, 1, pfile); 191 | if (nwritten != 1) { 192 | if (ferror(pfile)) { 193 | *err = errno; 194 | } else { 195 | *err = 0; 196 | } 197 | return FALSE; 198 | } 199 | 200 | (*bytes_written) += data_length; 201 | return TRUE; 202 | } 203 | 204 | /* Writing pcap files */ 205 | 206 | /* Write the file header to a dump file. 207 | Returns TRUE on success, FALSE on failure. 208 | Sets "*err" to an error code, or 0 for a short write, on failure*/ 209 | u_int8_t 210 | libpcap_write_file_header(FILE* pfile, int linktype, int snaplen, u_int8_t ts_nsecs, u_int64_t *bytes_written, int *err) 211 | { 212 | struct pcap_hdr file_hdr; 213 | 214 | file_hdr.magic = ts_nsecs ? PCAP_NSEC_MAGIC : PCAP_MAGIC; 215 | /* current "libpcap" format is 2.4 */ 216 | file_hdr.version_major = 2; 217 | file_hdr.version_minor = 4; 218 | file_hdr.thiszone = 0; /* XXX - current offset? */ 219 | file_hdr.sigfigs = 0; /* unknown, but also apparently unused */ 220 | file_hdr.snaplen = snaplen; 221 | file_hdr.network = linktype; 222 | 223 | return write_to_file(pfile, (const u_int8_t*)&file_hdr, sizeof(file_hdr), bytes_written, err); 224 | } 225 | 226 | /* Write a record for a packet to a dump file. 227 | Returns TRUE on success, FALSE on failure. */ 228 | u_int8_t 229 | libpcap_write_packet(FILE* pfile, 230 | time_t sec, u_int32_t usec, 231 | u_int32_t caplen, u_int32_t len, 232 | const u_int8_t *pd, 233 | u_int64_t *bytes_written, int *err) 234 | { 235 | struct pcaprec_hdr rec_hdr; 236 | 237 | rec_hdr.ts_sec = (u_int32_t)sec; /* Y2.038K issue in pcap format.... */ 238 | rec_hdr.ts_usec = usec; 239 | rec_hdr.incl_len = caplen; 240 | rec_hdr.orig_len = len; 241 | if (!write_to_file(pfile, (const u_int8_t*)&rec_hdr, sizeof(rec_hdr), bytes_written, err)) 242 | return FALSE; 243 | 244 | return write_to_file(pfile, pd, caplen, bytes_written, err); 245 | } 246 | 247 | /* Writing pcapng files */ 248 | 249 | static u_int32_t 250 | pcapng_count_string_option(const char *option_value) 251 | { 252 | if ((option_value != NULL) && (strlen(option_value) > 0) && (strlen(option_value) < G_MAXUINT16)) { 253 | /* There's a value to write; get its length */ 254 | return (u_int32_t)(sizeof(struct pcap_option) + 255 | (u_int16_t)ADD_PADDING(strlen(option_value))); 256 | } 257 | return 0; /* nothing to write */ 258 | } 259 | 260 | static u_int8_t 261 | pcapng_write_string_option(FILE* pfile, 262 | u_int16_t option_type, const char *option_value, 263 | u_int64_t *bytes_written, int *err) 264 | { 265 | size_t option_value_length; 266 | struct pcap_option option; 267 | const u_int32_t padding = 0; 268 | 269 | if (option_value == NULL) 270 | return TRUE; /* nothing to write */ 271 | option_value_length = strlen(option_value); 272 | if ((option_value_length > 0) && (option_value_length < G_MAXUINT16)) { 273 | /* something to write */ 274 | option.type = option_type; 275 | option.value_length = (u_int16_t)option_value_length; 276 | 277 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 278 | return FALSE; 279 | 280 | if (!write_to_file(pfile, (const u_int8_t*)option_value, (int) option_value_length, bytes_written, err)) 281 | return FALSE; 282 | 283 | if (option_value_length % 4) { 284 | if (!write_to_file(pfile, (const u_int8_t*)&padding, 4 - option_value_length % 4, bytes_written, err)) 285 | return FALSE; 286 | } 287 | } 288 | return TRUE; 289 | } 290 | 291 | /* Write a pre-formatted pcapng block directly to the output file */ 292 | u_int8_t 293 | pcapng_write_block(FILE* pfile, 294 | const u_int8_t *data, 295 | u_int32_t length, 296 | u_int64_t *bytes_written, 297 | int *err) 298 | { 299 | u_int32_t block_length, end_length; 300 | /* Check 301 | * - length and data are aligned to 4 bytes 302 | * - block_total_length field is the same at the start and end of the block 303 | * 304 | * The block_total_length is not checked against the provided length but 305 | * getting the trailing block_total_length from the length argument gives 306 | * us an implicit check of correctness without needing to do an endian swap 307 | */ 308 | if (((length & 3) != 0) || ((data[0] & 3) != 0)) { 309 | *err = EINVAL; 310 | return FALSE; 311 | } 312 | block_length = *(const u_int32_t *) (data+sizeof(u_int32_t)); 313 | end_length = *(const u_int32_t *) (data+length-sizeof(u_int32_t)); 314 | if (block_length != end_length) { 315 | *err = EBADMSG; 316 | return FALSE; 317 | } 318 | return write_to_file(pfile, data, length, bytes_written, err); 319 | } 320 | 321 | u_int8_t 322 | pcapng_write_section_header_block(FILE* pfile, 323 | const char *comment, 324 | const char *hw, 325 | const char *os, 326 | const char *appname, 327 | u_int64_t section_length, 328 | u_int64_t *bytes_written, 329 | int *err) 330 | { 331 | struct shb shb; 332 | struct pcap_option option; 333 | u_int32_t block_total_length; 334 | u_int32_t options_length; 335 | 336 | /* Size of base header */ 337 | block_total_length = sizeof(struct shb) + 338 | sizeof(u_int32_t); 339 | options_length = 0; 340 | options_length += pcapng_count_string_option(comment); 341 | options_length += pcapng_count_string_option(hw); 342 | options_length += pcapng_count_string_option(os); 343 | options_length += pcapng_count_string_option(appname); 344 | /* If we have options add size of end-of-options */ 345 | if (options_length != 0) { 346 | options_length += (u_int32_t)sizeof(struct pcap_option); 347 | } 348 | block_total_length += options_length; 349 | 350 | /* write shb header */ 351 | shb.block_type = SECTION_HEADER_BLOCK_TYPE; 352 | shb.block_total_length = block_total_length; 353 | shb.byte_order_magic = PCAPNG_MAGIC; 354 | shb.major_version = PCAPNG_MAJOR_VERSION; 355 | shb.minor_version = PCAPNG_MINOR_VERSION; 356 | shb.section_length = section_length; 357 | 358 | if (!write_to_file(pfile, (const u_int8_t*)&shb, sizeof(struct shb), bytes_written, err)) 359 | return FALSE; 360 | 361 | if (!pcapng_write_string_option(pfile, OPT_COMMENT, comment, 362 | bytes_written, err)) 363 | return FALSE; 364 | if (!pcapng_write_string_option(pfile, SHB_HARDWARE, hw, 365 | bytes_written, err)) 366 | return FALSE; 367 | if (!pcapng_write_string_option(pfile, SHB_OS, os, 368 | bytes_written, err)) 369 | return FALSE; 370 | if (!pcapng_write_string_option(pfile, SHB_USERAPPL, appname, 371 | bytes_written, err)) 372 | return FALSE; 373 | if (options_length != 0) { 374 | /* write end of options */ 375 | option.type = OPT_ENDOFOPT; 376 | option.value_length = 0; 377 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 378 | return FALSE; 379 | } 380 | 381 | /* write the trailing block total length */ 382 | return write_to_file(pfile, (const u_int8_t*)&block_total_length, sizeof(u_int32_t), bytes_written, err); 383 | } 384 | 385 | u_int8_t 386 | pcapng_write_interface_description_block(FILE* pfile, 387 | const char *comment, /* OPT_COMMENT 1 */ 388 | const char *name, /* IDB_NAME 2 */ 389 | const char *descr, /* IDB_DESCRIPTION 3 */ 390 | const char *filter, /* IDB_FILTER 11 */ 391 | const char *os, /* IDB_OS 12 */ 392 | int link_type, 393 | int snap_len, 394 | u_int64_t *bytes_written, 395 | u_int64_t if_speed, /* IDB_IF_SPEED 8 */ 396 | u_int8_t tsresol, /* IDB_TSRESOL 9 */ 397 | int *err) 398 | { 399 | struct idb idb; 400 | struct pcap_option option; 401 | u_int32_t block_total_length; 402 | u_int32_t options_length; 403 | const u_int32_t padding = 0; 404 | 405 | block_total_length = (u_int32_t)(sizeof(struct idb) + sizeof(u_int32_t)); 406 | options_length = 0; 407 | /* 01 - OPT_COMMENT */ 408 | options_length += pcapng_count_string_option(comment); 409 | 410 | /* 02 - IDB_NAME */ 411 | options_length += pcapng_count_string_option(name); 412 | 413 | /* 03 - IDB_DESCRIPTION */ 414 | options_length += pcapng_count_string_option(descr); 415 | 416 | /* 08 - IDB_IF_SPEED */ 417 | if (if_speed != 0) { 418 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 419 | sizeof(u_int64_t)); 420 | } 421 | 422 | /* 09 - IDB_TSRESOL */ 423 | if (tsresol != 0) { 424 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 425 | sizeof(struct pcap_option)); 426 | } 427 | 428 | /* 11 - IDB_FILTER */ 429 | if ((filter != NULL) && (strlen(filter) > 0) && (strlen(filter) < G_MAXUINT16)) { 430 | /* No, this isn't a string, it has an extra type byte */ 431 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 432 | (u_int16_t)(ADD_PADDING(strlen(filter)+ 1))); 433 | } 434 | 435 | /* 12 - IDB_OS */ 436 | options_length += pcapng_count_string_option(os); 437 | 438 | /* If we have options add size of end-of-options */ 439 | if (options_length != 0) { 440 | options_length += (u_int32_t)sizeof(struct pcap_option); 441 | } 442 | block_total_length += options_length; 443 | 444 | /* write block header */ 445 | idb.block_type = INTERFACE_DESCRIPTION_BLOCK_TYPE; 446 | idb.block_total_length = block_total_length; 447 | idb.link_type = link_type; 448 | idb.reserved = 0; 449 | idb.snap_len = snap_len; 450 | if (!write_to_file(pfile, (const u_int8_t*)&idb, sizeof(struct idb), bytes_written, err)) 451 | return FALSE; 452 | 453 | /* 01 - OPT_COMMENT - write comment string if applicable */ 454 | if (!pcapng_write_string_option(pfile, OPT_COMMENT, comment, 455 | bytes_written, err)) 456 | return FALSE; 457 | 458 | /* 02 - IDB_NAME - write interface name string if applicable */ 459 | if (!pcapng_write_string_option(pfile, IDB_NAME, name, 460 | bytes_written, err)) 461 | return FALSE; 462 | 463 | /* 03 - IDB_DESCRIPTION */ 464 | /* write interface description string if applicable */ 465 | if (!pcapng_write_string_option(pfile, IDB_DESCRIPTION, descr, 466 | bytes_written, err)) 467 | return FALSE; 468 | 469 | /* 08 - IDB_IF_SPEED */ 470 | if (if_speed != 0) { 471 | option.type = IDB_IF_SPEED; 472 | option.value_length = sizeof(u_int64_t); 473 | 474 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 475 | return FALSE; 476 | 477 | if (!write_to_file(pfile, (const u_int8_t*)&if_speed, sizeof(u_int64_t), bytes_written, err)) 478 | return FALSE; 479 | } 480 | 481 | /* 09 - IDB_TSRESOL */ 482 | if (tsresol != 0) { 483 | option.type = IDB_TSRESOL; 484 | option.value_length = sizeof(u_int8_t); 485 | 486 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 487 | return FALSE; 488 | 489 | if (!write_to_file(pfile, (const u_int8_t*)&tsresol, sizeof(u_int8_t), bytes_written, err)) 490 | return FALSE; 491 | 492 | if (!write_to_file(pfile, (const u_int8_t*)&padding, 3, bytes_written, err)) 493 | return FALSE; 494 | } 495 | 496 | /* 11 - IDB_FILTER - write filter string if applicable 497 | * We only write version 1 of the filter, pcapng string 498 | */ 499 | if ((filter != NULL) && (strlen(filter) > 0) && (strlen(filter) < G_MAXUINT16 - 1)) { 500 | option.type = IDB_FILTER; 501 | option.value_length = (u_int16_t)(strlen(filter) + 1 ); 502 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 503 | return FALSE; 504 | 505 | /* The first byte of the Option Data keeps a code of the filter used, 0 = lipbpcap filter string */ 506 | if (!write_to_file(pfile, (const u_int8_t*)&padding, 1, bytes_written, err)) 507 | return FALSE; 508 | if (!write_to_file(pfile, (const u_int8_t*)filter, (int) strlen(filter), bytes_written, err)) 509 | return FALSE; 510 | if ((strlen(filter) + 1) % 4) { 511 | if (!write_to_file(pfile, (const u_int8_t*)&padding, 4 - (strlen(filter) + 1) % 4, bytes_written, err)) 512 | return FALSE; 513 | } 514 | } 515 | 516 | /* 12 - IDB_OS - write os string if applicable */ 517 | if (!pcapng_write_string_option(pfile, IDB_OS, os, 518 | bytes_written, err)) 519 | return FALSE; 520 | 521 | if (options_length != 0) { 522 | /* write end of options */ 523 | option.type = OPT_ENDOFOPT; 524 | option.value_length = 0; 525 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 526 | return FALSE; 527 | } 528 | 529 | /* write the trailing Block Total Length */ 530 | return write_to_file(pfile, (const u_int8_t*)&block_total_length, sizeof(u_int32_t), bytes_written, err); 531 | } 532 | 533 | /* Write a record for a packet to a dump file. 534 | Returns TRUE on success, FALSE on failure. */ 535 | u_int8_t 536 | pcapng_write_enhanced_packet_block(FILE* pfile, 537 | const char *comment, 538 | time_t sec, u_int32_t usec, 539 | u_int32_t caplen, u_int32_t len, 540 | u_int32_t interface_id, 541 | u_int32_t ts_mul, 542 | const u_int8_t *pd, 543 | u_int32_t flags, 544 | u_int64_t *bytes_written, 545 | int *err) 546 | { 547 | struct epb epb; 548 | struct pcap_option option; 549 | u_int32_t block_total_length; 550 | u_int64_t timestamp; 551 | u_int32_t options_length; 552 | const u_int32_t padding = 0; 553 | u_int8_t buff[8]; 554 | u_int8_t i; 555 | u_int8_t pad_len = 0; 556 | 557 | block_total_length = (u_int32_t)(sizeof(struct epb) + 558 | ADD_PADDING(caplen) + 559 | sizeof(u_int32_t)); 560 | options_length = 0; 561 | options_length += pcapng_count_string_option(comment); 562 | if (flags != 0) { 563 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 564 | sizeof(u_int32_t)); 565 | } 566 | /* If we have options add size of end-of-options */ 567 | if (options_length != 0) { 568 | options_length += (u_int32_t)sizeof(struct pcap_option); 569 | } 570 | block_total_length += options_length; 571 | timestamp = (u_int64_t)sec * ts_mul + (u_int64_t)usec; 572 | epb.block_type = ENHANCED_PACKET_BLOCK_TYPE; 573 | epb.block_total_length = block_total_length; 574 | epb.interface_id = interface_id; 575 | epb.timestamp_high = (u_int32_t)((timestamp>>32) & 0xffffffff); 576 | epb.timestamp_low = (u_int32_t)(timestamp & 0xffffffff); 577 | epb.captured_len = caplen; 578 | epb.packet_len = len; 579 | if (!write_to_file(pfile, (const u_int8_t*)&epb, sizeof(struct epb), bytes_written, err)) 580 | return FALSE; 581 | if (!write_to_file(pfile, pd, caplen, bytes_written, err)) 582 | return FALSE; 583 | /* Use more efficient write in case of no "extras" */ 584 | if(caplen % 4) { 585 | pad_len = 4 - (caplen % 4); 586 | } 587 | /* 588 | * If we have no options to write, just write out the padding and 589 | * the block total length with one fwrite() call. 590 | */ 591 | if(!comment && flags == 0 && options_length==0){ 592 | /* Put padding in the buffer */ 593 | for (i = 0; i < pad_len; i++) { 594 | buff[i] = 0; 595 | } 596 | /* Write the total length */ 597 | memcpy(&buff[i], &block_total_length, sizeof(u_int32_t)); 598 | i += sizeof(u_int32_t); 599 | return write_to_file(pfile, (const u_int8_t*)&buff, i, bytes_written, err); 600 | } 601 | if (pad_len) { 602 | if (!write_to_file(pfile, (const u_int8_t*)&padding, pad_len, bytes_written, err)) 603 | return FALSE; 604 | } 605 | if (!pcapng_write_string_option(pfile, OPT_COMMENT, comment, 606 | bytes_written, err)) 607 | return FALSE; 608 | if (flags != 0) { 609 | option.type = EPB_FLAGS; 610 | option.value_length = sizeof(u_int32_t); 611 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 612 | return FALSE; 613 | if (!write_to_file(pfile, (const u_int8_t*)&flags, sizeof(u_int32_t), bytes_written, err)) 614 | return FALSE; 615 | } 616 | if (options_length != 0) { 617 | /* write end of options */ 618 | option.type = OPT_ENDOFOPT; 619 | option.value_length = 0; 620 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 621 | return FALSE; 622 | } 623 | 624 | return write_to_file(pfile, (const u_int8_t*)&block_total_length, sizeof(u_int32_t), bytes_written, err); 625 | } 626 | 627 | u_int8_t 628 | pcapng_write_interface_statistics_block(FILE* pfile, 629 | u_int32_t interface_id, 630 | u_int64_t *bytes_written, 631 | const char *comment, /* OPT_COMMENT 1 */ 632 | u_int64_t isb_starttime, /* ISB_STARTTIME 2 */ 633 | u_int64_t isb_endtime, /* ISB_ENDTIME 3 */ 634 | u_int64_t isb_ifrecv, /* ISB_IFRECV 4 */ 635 | u_int64_t isb_ifdrop, /* ISB_IFDROP 5 */ 636 | int *err) 637 | { 638 | struct isb isb; 639 | #ifdef _WIN32 640 | FILETIME now; 641 | #else 642 | struct timeval now; 643 | #endif 644 | struct pcap_option option; 645 | u_int32_t block_total_length; 646 | u_int32_t options_length; 647 | u_int64_t timestamp; 648 | 649 | #ifdef _WIN32 650 | /* 651 | * Current time, represented as 100-nanosecond intervals since 652 | * January 1, 1601, 00:00:00 UTC. 653 | * 654 | * I think DWORD might be signed, so cast both parts of "now" 655 | * to u_int32_t so that the sign bit doesn't get treated specially. 656 | * 657 | * Windows 8 provides GetSystemTimePreciseAsFileTime which we 658 | * might want to use instead. 659 | */ 660 | GetSystemTimeAsFileTime(&now); 661 | timestamp = (((u_int64_t)(u_int32_t)now.dwHighDateTime) << 32) + 662 | (u_int32_t)now.dwLowDateTime; 663 | 664 | /* 665 | * Convert to same thing but as 1-microsecond, i.e. 1000-nanosecond, 666 | * intervals. 667 | */ 668 | timestamp /= 10; 669 | 670 | /* 671 | * Subtract difference, in microseconds, between January 1, 1601 672 | * 00:00:00 UTC and January 1, 1970, 00:00:00 UTC. 673 | */ 674 | timestamp -= G_U_INT64_T_CONSTANT(11644473600000000); 675 | #else 676 | /* 677 | * Current time, represented as seconds and microseconds since 678 | * January 1, 1970, 00:00:00 UTC. 679 | */ 680 | gettimeofday(&now, NULL); 681 | 682 | /* 683 | * Convert to delta in microseconds. 684 | */ 685 | timestamp = (u_int64_t)(now.tv_sec) * 1000000 + 686 | (u_int64_t)(now.tv_usec); 687 | #endif 688 | block_total_length = (u_int32_t)(sizeof(struct isb) + sizeof(u_int32_t)); 689 | options_length = 0; 690 | if (isb_ifrecv != G_MAXUINT64) { 691 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 692 | sizeof(u_int64_t)); 693 | } 694 | if (isb_ifdrop != G_MAXUINT64) { 695 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 696 | sizeof(u_int64_t)); 697 | } 698 | /* OPT_COMMENT */ 699 | options_length += pcapng_count_string_option(comment); 700 | if (isb_starttime !=0) { 701 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 702 | sizeof(u_int64_t)); /* ISB_STARTTIME */ 703 | } 704 | if (isb_endtime !=0) { 705 | options_length += (u_int32_t)(sizeof(struct pcap_option) + 706 | sizeof(u_int64_t)); /* ISB_ENDTIME */ 707 | } 708 | /* If we have options add size of end-of-options */ 709 | if (options_length != 0) { 710 | options_length += (u_int32_t)sizeof(struct pcap_option); 711 | } 712 | block_total_length += options_length; 713 | 714 | isb.block_type = INTERFACE_STATISTICS_BLOCK_TYPE; 715 | isb.block_total_length = block_total_length; 716 | isb.interface_id = interface_id; 717 | isb.timestamp_high = (u_int32_t)((timestamp>>32) & 0xffffffff); 718 | isb.timestamp_low = (u_int32_t)(timestamp & 0xffffffff); 719 | if (!write_to_file(pfile, (const u_int8_t*)&isb, sizeof(struct isb), bytes_written, err)) 720 | return FALSE; 721 | 722 | /* write comment string if applicable */ 723 | if (!pcapng_write_string_option(pfile, OPT_COMMENT, comment, 724 | bytes_written, err)) 725 | return FALSE; 726 | 727 | if (isb_starttime !=0) { 728 | u_int32_t high, low; 729 | 730 | option.type = ISB_STARTTIME; 731 | option.value_length = sizeof(u_int64_t); 732 | high = (u_int32_t)((isb_starttime>>32) & 0xffffffff); 733 | low = (u_int32_t)(isb_starttime & 0xffffffff); 734 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 735 | return FALSE; 736 | 737 | if (!write_to_file(pfile, (const u_int8_t*)&high, sizeof(u_int32_t), bytes_written, err)) 738 | return FALSE; 739 | 740 | if (!write_to_file(pfile, (const u_int8_t*)&low, sizeof(u_int32_t), bytes_written, err)) 741 | return FALSE; 742 | } 743 | if (isb_endtime !=0) { 744 | u_int32_t high, low; 745 | 746 | option.type = ISB_ENDTIME; 747 | option.value_length = sizeof(u_int64_t); 748 | high = (u_int32_t)((isb_endtime>>32) & 0xffffffff); 749 | low = (u_int32_t)(isb_endtime & 0xffffffff); 750 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 751 | return FALSE; 752 | 753 | if (!write_to_file(pfile, (const u_int8_t*)&high, sizeof(u_int32_t), bytes_written, err)) 754 | return FALSE; 755 | 756 | if (!write_to_file(pfile, (const u_int8_t*)&low, sizeof(u_int32_t), bytes_written, err)) 757 | return FALSE; 758 | } 759 | if (isb_ifrecv != G_MAXUINT64) { 760 | option.type = ISB_IFRECV; 761 | option.value_length = sizeof(u_int64_t); 762 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 763 | return FALSE; 764 | 765 | if (!write_to_file(pfile, (const u_int8_t*)&isb_ifrecv, sizeof(u_int64_t), bytes_written, err)) 766 | return FALSE; 767 | } 768 | if (isb_ifdrop != G_MAXUINT64) { 769 | option.type = ISB_IFDROP; 770 | option.value_length = sizeof(u_int64_t); 771 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 772 | return FALSE; 773 | 774 | if (!write_to_file(pfile, (const u_int8_t*)&isb_ifdrop, sizeof(u_int64_t), bytes_written, err)) 775 | return FALSE; 776 | } 777 | if (options_length != 0) { 778 | /* write end of options */ 779 | option.type = OPT_ENDOFOPT; 780 | option.value_length = 0; 781 | if (!write_to_file(pfile, (const u_int8_t*)&option, sizeof(struct pcap_option), bytes_written, err)) 782 | return FALSE; 783 | } 784 | 785 | return write_to_file(pfile, (const u_int8_t*)&block_total_length, sizeof(u_int32_t), bytes_written, err); 786 | } 787 | 788 | /* 789 | * Editor modelines - https://www.wireshark.org/tools/modelines.html 790 | * 791 | * Local variables: 792 | * c-basic-offset: 8 793 | * tab-width: 8 794 | * indent-tabs-mode: nil 795 | * End: 796 | * 797 | * vi: set shiftwidth=8 tabstop=8 expandtab: 798 | * :indentSize=8:tabSize=8:noTabs=true: 799 | */ 800 | -------------------------------------------------------------------------------- /wireshark/pcapio.h: -------------------------------------------------------------------------------- 1 | /* pcapio.h 2 | * Declarations of our own routines for writing libpcap files. 3 | * 4 | * Wireshark - Network traffic analyzer 5 | * By Gerald Combs 6 | * Copyright 1998 Gerald Combs 7 | * 8 | * Derived from code in the Wiretap Library 9 | * Copyright (c) 1998 by Gilbert Ramirez 10 | * 11 | * SPDX-License-Identifier: GPL-2.0-or-later 12 | */ 13 | 14 | /* Writing pcap files */ 15 | 16 | /** Write the file header to a dump file. 17 | Returns TRUE on success, FALSE on failure. 18 | Sets "*err" to an error code, or 0 for a short write, on failure*/ 19 | extern u_int8_t 20 | libpcap_write_file_header(FILE* pfile, int linktype, int snaplen, 21 | u_int8_t ts_nsecs, u_int64_t *bytes_written, int *err); 22 | 23 | /** Write a record for a packet to a dump file. 24 | Returns TRUE on success, FALSE on failure. */ 25 | extern u_int8_t 26 | libpcap_write_packet(FILE* pfile, 27 | time_t sec, u_int32_t usec, 28 | u_int32_t caplen, u_int32_t len, 29 | const u_int8_t *pd, 30 | u_int64_t *bytes_written, int *err); 31 | 32 | /* Writing pcapng files */ 33 | 34 | /* Write a pre-formatted pcapng block */ 35 | extern u_int8_t 36 | pcapng_write_block(FILE* pfile, 37 | const u_int8_t *data, 38 | u_int32_t block_total_length, 39 | u_int64_t *bytes_written, 40 | int *err); 41 | 42 | /** Write a section header block (SHB) 43 | * 44 | */ 45 | extern u_int8_t 46 | pcapng_write_section_header_block(FILE* pfile, /**< Write information */ 47 | const char *comment, /**< Comment on the section, Optinon 1 opt_comment 48 | * A UTF-8 string containing a comment that is associated to the current block. 49 | */ 50 | const char *hw, /**< HW, Optinon 2 shb_hardware 51 | * An UTF-8 string containing the description of the hardware used to create this section. 52 | */ 53 | const char *os, /**< Operating system name, Optinon 3 shb_os 54 | * An UTF-8 string containing the name of the operating system used to create this section. 55 | */ 56 | const char *appname, /**< Application name, Optinon 4 shb_userappl 57 | * An UTF-8 string containing the name of the application used to create this section. 58 | */ 59 | u_int64_t section_length, /**< Length of section */ 60 | u_int64_t *bytes_written, /**< Number of written bytes */ 61 | int *err /**< Error type */ 62 | ); 63 | 64 | extern u_int8_t 65 | pcapng_write_interface_description_block(FILE* pfile, 66 | const char *comment, /* OPT_COMMENT 1 */ 67 | const char *name, /* IDB_NAME 2 */ 68 | const char *descr, /* IDB_DESCRIPTION 3 */ 69 | const char *filter, /* IDB_FILTER 11 */ 70 | const char *os, /* IDB_OS 12 */ 71 | int link_type, 72 | int snap_len, 73 | u_int64_t *bytes_written, 74 | u_int64_t if_speed, /* IDB_IF_SPEED 8 */ 75 | u_int8_t tsresol, /* IDB_TSRESOL 9 */ 76 | int *err); 77 | 78 | extern u_int8_t 79 | pcapng_write_interface_statistics_block(FILE* pfile, 80 | u_int32_t interface_id, 81 | u_int64_t *bytes_written, 82 | const char *comment, /* OPT_COMMENT 1 */ 83 | u_int64_t isb_starttime, /* ISB_STARTTIME 2 */ 84 | u_int64_t isb_endtime, /* ISB_ENDTIME 3 */ 85 | u_int64_t isb_ifrecv, /* ISB_IFRECV 4 */ 86 | u_int64_t isb_ifdrop, /* ISB_IFDROP 5 */ 87 | int *err); 88 | 89 | extern u_int8_t 90 | pcapng_write_enhanced_packet_block(FILE* pfile, 91 | const char *comment, 92 | time_t sec, u_int32_t usec, 93 | u_int32_t caplen, u_int32_t len, 94 | u_int32_t interface_id, 95 | u_int32_t ts_mul, 96 | const u_int8_t *pd, 97 | u_int32_t flags, 98 | u_int64_t *bytes_written, 99 | int *err); 100 | 101 | /* 102 | * Editor modelines - https://www.wireshark.org/tools/modelines.html 103 | * 104 | * Local variables: 105 | * c-basic-offset: 4 106 | * tab-width: 8 107 | * indent-tabs-mode: nil 108 | * End: 109 | * 110 | * vi: set shiftwidth=4 tabstop=8 expandtab: 111 | * :indentSize=4:tabSize=8:noTabs=true: 112 | */ 113 | --------------------------------------------------------------------------------