├── deps └── .gitignore ├── tests ├── __init__.py ├── runner.py ├── base.py └── test_basic.py ├── .lcovrc ├── tools ├── llvm-gcov.sh ├── hello-world-inet-register.py ├── hello-world-server.py └── socket-activation.py ├── .gitignore ├── .clang-format ├── ebpf ├── inet-kern-shared.h ├── inet-kern-shared.c └── inet-kern.c ├── src ├── tbpf.h ├── tbpf.c ├── help.txt ├── inet.h ├── inet-scm.c ├── main.c ├── utils.c └── inet-commands.c ├── README.md ├── tbpf-decode-elf.py └── Makefile /deps/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test_basic import * 2 | -------------------------------------------------------------------------------- /.lcovrc: -------------------------------------------------------------------------------- 1 | geninfo_auto_base = 1 2 | lcov_branch_coverage = 1 3 | -------------------------------------------------------------------------------- /tools/llvm-gcov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec llvm-cov gcov "$@" 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | deps 3 | inet-ebpf.c 4 | inet-tool 5 | __pycache__ 6 | *.gcda 7 | *.gcno 8 | cov_html 9 | cov.info 10 | *.gcno.info -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 8 3 | UseTab: Always 4 | BreakBeforeBraces: Linux 5 | AllowShortIfStatementsOnASingleLine: false 6 | IndentCaseLabels: false 7 | AlwaysBreakBeforeMultilineStrings: true 8 | AllowShortBlocksOnASingleLine: false 9 | 10 | ContinuationIndentWidth: 8 11 | 12 | -------------------------------------------------------------------------------- /ebpf/inet-kern-shared.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct addr { 4 | __u32 prefixlen; 5 | __u8 protocol; 6 | __u16 port; 7 | struct ip { 8 | __u32 ip_as_w[4]; 9 | } addr; 10 | }; 11 | 12 | /* FD names passed by systemd can be 255 characters long. Match the limit. */ 13 | struct srvname { 14 | char name[255]; 15 | }; 16 | 17 | enum { REDIR_MAP, 18 | BIND_MAP, 19 | SRVNAME_MAP, 20 | }; 21 | -------------------------------------------------------------------------------- /src/tbpf.h: -------------------------------------------------------------------------------- 1 | /* See https://lkml.org/lkml/2014/8/13/116 and 2 | * https://patchwork.ozlabs.org/patch/930413/ for the reocation type 3 | * BPF_PSEUDO_MAP_FD or R_BPF_MAP_FD with value 1 */ 4 | 5 | /* Relocations, as exposed in format consumeable by C */ 6 | struct tbpf_reloc { 7 | char *name; /* Name of the symbol */ 8 | int type; /* Type of relocation, expected 1 */ 9 | int offset; /* Offset: ebpf instruction number */ 10 | }; 11 | 12 | int tbpf_fill_symbol(struct bpf_insn *insns, struct tbpf_reloc *relocs, 13 | const char *symbol, int32_t value); 14 | -------------------------------------------------------------------------------- /ebpf/inet-kern-shared.c: -------------------------------------------------------------------------------- 1 | struct bpf_map_def SEC("maps") redir_map = { 2 | .type = BPF_MAP_TYPE_SOCKMAP, 3 | .max_entries = 512, 4 | .key_size = sizeof(__u32), 5 | .value_size = sizeof(__u64), 6 | }; 7 | 8 | struct bpf_map_def SEC("maps") bind_map = { 9 | .type = BPF_MAP_TYPE_LPM_TRIE, 10 | .max_entries = 4096, 11 | .key_size = sizeof(struct addr), 12 | .value_size = sizeof(struct srvname), 13 | .map_flags = BPF_F_NO_PREALLOC, 14 | }; 15 | 16 | struct bpf_map_def SEC("maps") srvname_map = { 17 | .type = BPF_MAP_TYPE_HASH, 18 | .max_entries = 512, 19 | .key_size = sizeof(struct srvname), 20 | .value_size = sizeof(__u32), 21 | .map_flags = BPF_F_NO_PREALLOC, 22 | }; 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | inet-tool 3 | --------- 4 | 5 | The tool to manage SK_LOOKUP program. 6 | 7 | Dependencies: 8 | 9 | apt install \ 10 | build-essential \ 11 | clang \ 12 | clang-format \ 13 | lcov \ 14 | libc6-dev-i386 \ 15 | libz-dev \ 16 | python3-virtualenv \ 17 | virtualenv 18 | 19 | Clang is needed to compile C into the eBPF program. 20 | 21 | First, you need to run experimental kernel with SK_LOOKUP 22 | patches. For example: 23 | 24 | cd /tmp 25 | git clone https://github.com/jsitnicki/linux.git --branch bpf-inet-lookup --depth 1 26 | 27 | Then you can build the `inet-tool`: 28 | 29 | (cd /tmp/linux && make headers_install && make -C tools/lib/bpf) 30 | make KERNEL_DIR=/tmp/linux 31 | 32 | Tu run tests (requires root): 33 | 34 | sudo make test 35 | 36 | -------------------------------------------------------------------------------- /tests/runner.py: -------------------------------------------------------------------------------- 1 | # 2 | # Selects TestRunner based on the environment we're running in. 3 | # 4 | 5 | import os 6 | import unittest 7 | 8 | try: 9 | from teamcity.unittestpy import TeamcityTestRunner 10 | teamcity = True 11 | except ImportError: 12 | teamcity = False 13 | 14 | 15 | def is_running_under_teamcity(): 16 | # We export a different enviroment variable than teamcity package expects, 17 | # i.e. TEAMCITY_VERSION, hence a custom predicate to detect TeamCity 18 | # builds. 19 | return bool(os.getenv("CI")) 20 | 21 | 22 | if __name__ == '__main__': 23 | if teamcity and is_running_under_teamcity(): 24 | runner = TeamcityTestRunner() 25 | else: 26 | # Let unittest create it and _configure_ it that we honor the command 27 | # line options like --verbose. 28 | runner = None 29 | 30 | unittest.main(module=None, testRunner=runner) 31 | -------------------------------------------------------------------------------- /src/tbpf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is based on bpf.c from: 3 | * https://github.com/torvalds/linux/blob/master/tools/lib/bpf/bpf.c 4 | * 5 | * As opposed to libbpf.c it does not have a dependency on libelf. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "tbpf.h" 14 | 15 | /* Fixup a relocation in ebpf bpf_insn table. */ 16 | int tbpf_fill_symbol(struct bpf_insn *insns, struct tbpf_reloc *relocs, 17 | const char *symbol, int32_t value) 18 | { 19 | int c = 0; 20 | while (relocs && relocs->name && relocs->name[0] != '\x00') { 21 | if (strcmp(relocs->name, symbol) == 0) { 22 | switch (relocs->type) { 23 | case 1: 24 | insns[relocs->offset].src_reg = 1; 25 | insns[relocs->offset].imm = value; 26 | c += 1; 27 | break; 28 | default: 29 | fprintf(stderr, 30 | "FATAL: unknown relocation %d\n", 31 | relocs->type); 32 | abort(); 33 | } 34 | } 35 | relocs++; 36 | } 37 | return c; 38 | } 39 | -------------------------------------------------------------------------------- /tools/hello-world-inet-register.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | import errno 4 | import itertools 5 | import os 6 | import select 7 | import socket 8 | import sys 9 | 10 | 11 | def main(argv): 12 | parser = argparse.ArgumentParser(description='Execute binary with some inherited sockets') 13 | parser.add_argument('-f', '--fdname', default='hello-world-svc', 14 | help='fdnames of socket') 15 | args = parser.parse_args(argv) 16 | 17 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | s.set_inheritable(True) 19 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 20 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 21 | s.bind(('127.0.0.1', 0)) 22 | s.listen(16) 23 | 24 | os.system('inet-tool register %s' % (args.fdname)) 25 | 26 | 27 | p = select.poll() 28 | p.register(s, select.POLLIN) 29 | 30 | while True: 31 | sockets = p.poll() 32 | for fd, _ in sockets: 33 | sd, _ = s.accept() 34 | sd.send(b"Hello world!\r\n") 35 | sd.close() 36 | 37 | if __name__ == '__main__': 38 | i = main(sys.argv[1:]) 39 | os.exit(i) 40 | -------------------------------------------------------------------------------- /tools/hello-world-server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import errno 3 | import itertools 4 | import select 5 | import socket 6 | 7 | 8 | MAXCONTIGOUSFDGAP=32 9 | 10 | gap = 0 11 | SOCKETS = {} 12 | for fd in itertools.count(3): 13 | # We can call getsockopt only on a socket object, this dup()s the fd. 14 | try: 15 | tmp_sd = socket.fromfd(fd, 0, 0, 0) 16 | except OSError as e: 17 | if e.errno == errno.EBADF: 18 | gap += 1 19 | if gap > MAXCONTIGOUSFDGAP: 20 | break 21 | continue 22 | else: 23 | raise e 24 | gap = 0 25 | 26 | try: 27 | # Trigger EBADF 28 | domain = tmp_sd.getsockopt(socket.SOL_SOCKET, socket.SO_DOMAIN) 29 | sock_type = tmp_sd.getsockopt(socket.SOL_SOCKET, socket.SO_TYPE) 30 | protocol = tmp_sd.getsockopt(socket.SOL_SOCKET, socket.SO_PROTOCOL) 31 | except OSError as e: 32 | # not a socket 33 | tmp_sd.close() 34 | continue 35 | 36 | tmp_sd.close() 37 | 38 | if domain in (socket.AF_INET, socket.AF_INET6): 39 | SOCKETS[fd] = socket.socket(domain, sock_type, protocol, fd) 40 | 41 | 42 | p = select.poll() 43 | for s in SOCKETS.values(): 44 | p.register(s, select.POLLIN) 45 | 46 | while True: 47 | sockets = p.poll() 48 | for fd, _ in sockets: 49 | s = SOCKETS[fd] 50 | sd, _ = s.accept() 51 | sd.send(b"Hello world!\r\n") 52 | sd.close() 53 | -------------------------------------------------------------------------------- /src/help.txt: -------------------------------------------------------------------------------- 1 | "Usage: inet-tool {unload,load,info,list,bind,unbind,register,unregister,scm_serve,scm_register} ...\n" 2 | "Manages SK_LOOKUP eBPF program, responsible for programmable socket dispatch.\n" 3 | "\n" 4 | " inet-tool load\n" 5 | " Loads SK_LOOKUP program.\n" 6 | "\n" 7 | " inet-tool unload\n" 8 | " Unloads SK_LOOKUP program.\n" 9 | "\n" 10 | " inet-tool info\n" 11 | " Prints info about SK_LOOKUP program.\n" 12 | "\n" 13 | " inet-tool list\n" 14 | " Prints services and bindings.\n" 15 | "\n" 16 | " inet-tool bind [protocol] [ip:port] [service]\n" 17 | " Create a binding between protocol,ip:port selector and a service.\n" 18 | " For example \"inet-tool bind 6 0.0.0.0:80 http-service\"\n" 19 | "\n" 20 | " inet-tool unbind [protocol] [ip:port]\n" 21 | " Removes a binding.\n" 22 | "\n" 23 | " inet-tool register [service]\n" 24 | " Allocates a slot for a service.\n" 25 | "\n" 26 | " inet-tool register [service...] -- [command]\n" 27 | " Registers sockets passed down from parent with socket activation,\n" 28 | " and launches a command. This is useful wrapper for socket activation.\n" 29 | "\n" 30 | " inet-tool unregister [service]\n" 31 | " Deregisters a service.\n" 32 | "\n" 33 | " inet-tool scm_serve --unix=UNIX_SOCKET\n" 34 | " Creates unix domain socket and waits for SCM_RIGHTS sockets on it.\n" 35 | " This is useful service for socket activation. Default path is @inet-scm-service.\n" 36 | "\n" 37 | " inet-tool scm_register --unix=UNIX_SOCKET [service...] -- [command]\n" 38 | " Registers sockets passed down from parent with socket activation,\n" 39 | " and launches a command. This is useful wrapper for socket activation.\n" 40 | "\n" 41 | 42 | -------------------------------------------------------------------------------- /tools/socket-activation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | import socket 5 | import sys 6 | import urllib.parse 7 | 8 | 9 | def main(argv): 10 | parser = argparse.ArgumentParser(description='Execute binary with some inherited sockets') 11 | parser.add_argument('-l', '--listen', action='append', default=[], 12 | help='Open this sockets') 13 | parser.add_argument('cmd', metavar='COMMAND', nargs='+', 14 | help='command to run') 15 | args = parser.parse_args(argv) 16 | 17 | S = [] 18 | for a in args.listen: 19 | o = urllib.parse.urlparse(a) 20 | if o.scheme in ['tcp', 'tcp4'] and ':' not in o.hostname: 21 | #print('[ ] Binding AF_INET SOCK_STREAM to %r' % ((o.hostname, o.port),)) 22 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 23 | elif o.scheme in ['tcp','tcp6']: 24 | #print('[ ] Binding AF_INET6 SOCK_STREAM to %r' % ((o.hostname, o.port),)) 25 | s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 26 | elif o.scheme in ['udp', 'udp4'] and ':' not in o.hostname: 27 | #print('[ ] Binding AF_INET SOCK_DGRAM to %r' % ((o.hostname, o.port),)) 28 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 29 | elif o.scheme in ['udp','udp6']: 30 | #print('[ ] Binding AF_INET6 SOCK_DGRAM to %r' % ((o.hostname, o.port),)) 31 | s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) 32 | else: 33 | raise ValueError("use tcp:// udp:// scheme") 34 | s.set_inheritable(True) 35 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 36 | 37 | ## TODO make it an option 38 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 39 | 40 | if s.family == socket.AF_INET6: 41 | if o.scheme[-1] == '6': 42 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) 43 | else: 44 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 45 | 46 | s.bind((o.hostname, o.port)) 47 | try: 48 | s.listen(16) 49 | except socket.error: 50 | pass 51 | S.append(s) 52 | 53 | # print("[+] Running %r" % (args.cmd,)) 54 | os.execv(args.cmd[0], args.cmd) 55 | 56 | 57 | 58 | if __name__ == '__main__': 59 | i = main(sys.argv[1:]) 60 | os.exit(i) 61 | -------------------------------------------------------------------------------- /ebpf/inet-kern.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "inet-kern-shared.h" 9 | 10 | #include "inet-kern-shared.c" 11 | 12 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 13 | 14 | SEC("sk_lookup") 15 | int _sk_lookup(struct bpf_sk_lookup *ctx) 16 | { 17 | /* /32 and /128 */ 18 | struct ip laddr_full = {}; 19 | if (ctx->family == AF_INET) { 20 | laddr_full.ip_as_w[2] = bpf_htonl(0x0000ffff); 21 | laddr_full.ip_as_w[3] = ctx->local_ip4; 22 | } 23 | if (ctx->family == AF_INET6) { 24 | laddr_full.ip_as_w[0] = ctx->local_ip6[0]; 25 | laddr_full.ip_as_w[1] = ctx->local_ip6[1]; 26 | laddr_full.ip_as_w[2] = ctx->local_ip6[2]; 27 | laddr_full.ip_as_w[3] = ctx->local_ip6[3]; 28 | } 29 | 30 | struct addr lookup_keys[] = { 31 | { 32 | .protocol = ctx->protocol, 33 | .port = ctx->local_port, 34 | .addr = laddr_full, 35 | }, 36 | { 37 | .protocol = ctx->protocol, 38 | .port = 0, 39 | .addr = laddr_full, 40 | }, 41 | }; 42 | 43 | int i = 0; 44 | #pragma clang loop unroll(full) 45 | for (i = 0; i < (int)ARRAY_SIZE(lookup_keys); i++) { 46 | struct srvname *srvname = NULL; 47 | /* eBPF voodoo. For some reason key = lookup_keys[i] aint work. 48 | */ 49 | struct addr key = { 50 | .protocol = lookup_keys[i].protocol, 51 | .port = lookup_keys[i].port, 52 | }; 53 | key.prefixlen = (sizeof(struct addr) - 4) * 8; 54 | key.addr = lookup_keys[i].addr; 55 | 56 | srvname = 57 | (struct srvname *)bpf_map_lookup_elem(&bind_map, &key); 58 | if (srvname != NULL) { 59 | __u32 *index = (__u32 *)bpf_map_lookup_elem( 60 | &srvname_map, srvname); 61 | if (index != NULL) { 62 | struct bpf_sock *sk = bpf_map_lookup_elem(&redir_map, index); 63 | if (!sk) { 64 | /* Service for the address registered, 65 | * but socket is missing (service 66 | * down?). Drop connections so they 67 | * don't end up in some other socket 68 | * bound to the address/port reserved 69 | * for this service. 70 | */ 71 | return SK_DROP; 72 | } 73 | int err = bpf_sk_assign(ctx, sk, 0); 74 | if (err) { 75 | /* Same as for no socket case above, 76 | * except here socket is not compatible 77 | * with the IP family or L4 transport 78 | * for the address/port it is mapped 79 | * to. Service misconfigured. 80 | */ 81 | bpf_sk_release(sk); 82 | return SK_DROP; 83 | } 84 | 85 | /* Found and selected a suitable socket. Direct 86 | * the incoming connection to it. */ 87 | bpf_sk_release(sk); 88 | return SK_PASS; 89 | } 90 | } 91 | } 92 | return SK_PASS; 93 | } 94 | -------------------------------------------------------------------------------- /src/inet.h: -------------------------------------------------------------------------------- 1 | /* common */ 2 | 3 | #define PFATAL(x...) \ 4 | do { \ 5 | fprintf(stderr, "[-] SYSTEM ERROR : " x); \ 6 | fprintf(stderr, "\n\tLocation : %s(), %s:%u\n", __FUNCTION__, \ 7 | __FILE__, __LINE__); \ 8 | perror(" OS message "); \ 9 | fprintf(stderr, "\n"); \ 10 | exit(EXIT_FAILURE); \ 11 | } while (0) 12 | 13 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 14 | #define KERNEL_VERSION(a, b, c) ((a)*65536 + (b)*256 + (c)) 15 | 16 | /* inet-tool.c */ 17 | struct state { 18 | char sys_fs_obj_prefix[256]; 19 | int link_fd; 20 | int map_fds[128]; 21 | 22 | char *unix_path; 23 | }; 24 | 25 | struct inet_addr { 26 | int protocol; // tcp or udp 27 | struct sockaddr_storage ss; // inet or inet6, port 28 | int subnet; // 32/128 by default. 0 is valid 29 | }; 30 | 31 | /* inet-commands.c */ 32 | void inet_load(struct state *state); 33 | int inet_unload(struct state *state); 34 | int inet_prog_info(struct state *state); 35 | int inet_prog_verify(void); 36 | void inet_open_verify_maps(struct state *state, int all_needed); 37 | void inet_open_verify_link(struct state *state); 38 | 39 | void inet_list(struct state *state); 40 | void inet_register(struct state *state, char **fdnames, char **srvnames); 41 | void inet_unregister(struct state *state, char *service); 42 | 43 | int find_inherited_fd(int last_fd, int *skip_fds, int *domain, int *sock_type, 44 | int *protocol); 45 | int inet_register_socket(struct state *state, int fd, char *fdname); 46 | 47 | void inet_bind(struct state *state, struct inet_addr *addr, char *service); 48 | void inet_unbind(struct state *state, struct inet_addr *addr); 49 | 50 | /* inet-scm.c */ 51 | void inet_scm_serve(struct state *state); 52 | void inet_scm_register(struct state *state, char **fdnames, char **srvnames); 53 | 54 | /* net.c */ 55 | int net_parse_sockaddr(struct sockaddr_storage *ss, const char *addr, 56 | int *subnet_ptr); 57 | const char *net_ntop(struct sockaddr_storage *ss); 58 | 59 | /* utils.c */ 60 | struct addr; 61 | struct option; 62 | const char *sprint_addr(struct addr *k); 63 | const char *optstring_from_long_options(const struct option *opt); 64 | char ***argv_split(char **argv, const char *delimiter, int upper_bound, 65 | int max); 66 | int argv_len(char **argv); 67 | char **parse_argv(const char *str, char delim); 68 | char *argv_join(char **argv, const char *delim); 69 | uint64_t get_net_ns_inode(); 70 | struct addr *parse_addr(char *txt_addr, struct addr *localaddr); 71 | 72 | void parse_inet_addr(struct inet_addr *addr, char *protocol, char *host); 73 | int get_bottombits(struct sockaddr_storage *ss, int subnet); 74 | 75 | void bump_memlimit(); 76 | 77 | /* misc */ 78 | #ifdef CODE_COVERAGE 79 | void __gcov_flush(void); 80 | #else 81 | inline static void __gcov_flush(void) {} 82 | #endif 83 | -------------------------------------------------------------------------------- /tbpf-decode-elf.py: -------------------------------------------------------------------------------- 1 | from elftools.elf.elffile import ELFFile 2 | import io 3 | import struct 4 | import sys 5 | 6 | 7 | bpf_insn_template = """\ 8 | { 9 | \t\t.code = 0x%x, 10 | \t\t.dst_reg = BPF_REG_%d, 11 | \t\t.src_reg = BPF_REG_%d, 12 | \t\t.off = %d, 13 | \t\t.imm = %d\t/*%s*/ 14 | \t}\ 15 | """ 16 | 17 | def parse_bpf(bytecode): 18 | list_of_insns = [] 19 | for b_offset in range(0, len(bytecode), 8): 20 | instruction = bytecode[b_offset : b_offset + 8] 21 | opcode, src_and_dst, offset, imm = struct.unpack("BBhi", instruction[0:8]) 22 | dst_reg, src_reg = src_and_dst & 0x0F, (src_and_dst & 0xF0) >> 4 23 | list_of_insns.append( (opcode, dst_reg, src_reg, offset, imm) ) 24 | return list_of_insns 25 | 26 | def process_file(f, section): 27 | elffile = ELFFile(f) 28 | 29 | symtab = elffile.get_section_by_name(".symtab") 30 | symtab_syms = list(symtab.iter_symbols()) 31 | 32 | s = elffile.get_section_by_name(section) 33 | list_of_insns = parse_bpf(s.data()) 34 | 35 | reladyn = elffile.get_section_by_name('.rel' + section) 36 | 37 | list_of_relocs = [] 38 | if reladyn: 39 | for reloc in reladyn.iter_relocations(): 40 | s = symtab_syms[reloc['r_info_sym']] 41 | list_of_relocs.append( (reloc['r_info_type'], reloc['r_offset'], s.name) ) 42 | 43 | return list_of_insns, list_of_relocs 44 | 45 | filename = sys.argv[1] 46 | f = io.BytesIO(open(filename, 'rb').read()) 47 | 48 | print('''\ 49 | /* AUTOGENERATED DO NOT EDIT */ 50 | #include 51 | #include 52 | #include 53 | 54 | /* Relocations, as exposed in format consumeable by C */ 55 | struct tbpf_reloc { 56 | char *name; /* Name of the symbol */ 57 | int type; /* Type of relocation, expected 1 */ 58 | int offset; /* Offset: ebpf instruction number */ 59 | }; 60 | ''') 61 | 62 | for section in sys.argv[2:]: 63 | list_of_insns, list_of_relocs = process_file(f, section) 64 | 65 | insns = [] 66 | for i, (opcode, dst_reg, src_reg, offset, imm) in enumerate(list_of_insns): 67 | r = '' 68 | for t, o, n in list_of_relocs: 69 | if o / 8 == i: 70 | r = ' relocation for %s ' % (n,) 71 | s = bpf_insn_template % (opcode, dst_reg, src_reg, offset, imm, r) 72 | insns.append( s ) 73 | 74 | if len(insns) == 0: 75 | sys.exit(-1) 76 | 77 | print('''\ 78 | size_t bpf_insn_%s_cnt = %s; 79 | 80 | struct bpf_insn bpf_insn_%s[] = { 81 | \t%s};\ 82 | ''' % (section, len(insns), section, ', '.join(insns))) 83 | 84 | reloc_template = '''\ 85 | { 86 | \t\t.name = %s, 87 | \t\t.type = %d, 88 | \t\t.offset = %d 89 | \t}\ 90 | ''' 91 | relocs = [] 92 | for t, o, n in list_of_relocs + [(False, 0, 0)]: 93 | s = reloc_template % ('"' + n + '"' if n else 'NULL', t, o / 8) 94 | relocs.append(s) 95 | 96 | print(''' 97 | 98 | struct tbpf_reloc bpf_reloc_%s[] = { 99 | \t%s}; 100 | ''' % (section, ', '.join(relocs))) 101 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CLANG ?= clang 2 | KERNEL_DIR ?= 3 | 4 | HOST_ARCH = $(shell uname -m)-linux-gnu 5 | LIBC_INC_DIR = /usr/include/$(HOST_ARCH) 6 | 7 | ifeq ($(KERNEL_DIR),) 8 | # Build against system packages. 9 | # 10 | # On Debian: 11 | # 12 | # $ apt install linux-libc-dev libbpf-dev 13 | # 14 | LINUX_INC_DIR = /usr/include 15 | LIBBPF_INC_DIR = /usr/include 16 | LIBBPF_LIB_DIR = /usr/lib/$(HOST_ARCH) 17 | else 18 | # Build against kernel source tree. 19 | # 20 | # Prepare headers and build libbpf: 21 | # 22 | # $ make headers_install && make -C tools/lib/bpf 23 | # 24 | LINUX_INC_DIR = $(KERNEL_DIR)/usr/include 25 | LIBBPF_INC_DIR = $(KERNEL_DIR)/tools/lib 26 | LIBBPF_LIB_DIR = $(KERNEL_DIR)/tools/lib/bpf 27 | endif 28 | 29 | # make: by default build inet-tool 30 | all: venv/.ok inet-tool 31 | 32 | .PHONY: check_kernel 33 | check_kernel: 34 | ifeq ("$(shell grep BPF_SK_LOOKUP $(LINUX_INC_DIR)/linux/bpf.h)","") 35 | $(error KERNEL_DIR must point to kernel with SK_LOOKUP patches) 36 | endif 37 | 38 | ##VERSION := $(shell git describe --tags --always --dirty="-dev") 39 | 40 | # Let the ebpf program version be its source checksum. 41 | EBPF_VERSION := $(shell cat ebpf/*.[ch]|sort|sha1sum |cut -c 1-8) 42 | 43 | DEPS_H = \ 44 | $(LINUX_INC_DIR)/linux/bpf.h \ 45 | $(LIBBPF_INC_DIR)/bpf/bpf.h \ 46 | $(LIBBPF_INC_DIR)/bpf/libbpf.h \ 47 | $(LIBBPF_INC_DIR)/bpf/bpf_endian.h \ 48 | $(LIBBPF_INC_DIR)/bpf/bpf_helpers.h 49 | 50 | DEPS = $(LIBBPF_LIB_DIR)/libbpf.a $(DEPS_H) 51 | INCLUDES = -I$(LIBC_INC_DIR) -I$(LINUX_INC_DIR) -I$(LIBBPF_INC_DIR) 52 | 53 | INET_TOOL_DEPS=src/*.[ch] $(DEPS) inet-ebpf.c Makefile ebpf/*shared* 54 | 55 | inet-tool: $(INET_TOOL_DEPS) 56 | $(CLANG) -g -Wall -Wextra -O2 \ 57 | $(EXTRA_CFLAGS) \ 58 | $(INCLUDES) \ 59 | src/tbpf.c \ 60 | src/main.c \ 61 | src/inet-commands.c \ 62 | src/utils.c \ 63 | inet-ebpf.c \ 64 | src/inet-scm.c \ 65 | $(LIBBPF_LIB_DIR)/libbpf.a \ 66 | -D INET_PROGRAM_VERSION=\"inet_$(EBPF_VERSION)\" \ 67 | -lelf -lz \ 68 | -o $@ 69 | 70 | $(DEPS): check_kernel 71 | 72 | inet-ebpf.c: ebpf/*.[ch] tbpf-decode-elf.py $(DEPS_H) ebpf/*shared* 73 | $(CLANG) -Wall -Wextra -O2 --target=bpf -c \ 74 | $(INCLUDES) \ 75 | ebpf/inet-kern.c \ 76 | -o - \ 77 | | ./venv/bin/python3 tbpf-decode-elf.py /dev/stdin \ 78 | sk_lookup \ 79 | > $@ 80 | 81 | inet-tool-test: $(INET_TOOL_DEPS) 82 | rm -f inet-tool 83 | $(MAKE) inet-tool EXTRA_CFLAGS="--coverage -D CODE_COVERAGE=1" 84 | mv -f inet-tool inet-tool-test 85 | 86 | 87 | # We need python3 virtualenv with pyelftools installed for the tbpf-decode-elf script 88 | venv/.ok: 89 | virtualenv venv --python=python3 90 | ./venv/bin/pip3 install pyelftools 91 | touch $@ 92 | 93 | # make format 94 | .PHONY: format 95 | format: 96 | clang-format -i src/*.[ch] ebpf/*.[ch] 97 | @grep -n "TODO" src/*.[ch] ebpf/*.[ch] || true 98 | 99 | # make test 100 | .PHONY: test 101 | test: inet-tool-test 102 | @rm -rf *.gcda cov_html cov.info 103 | INETTOOLBIN="./inet-tool-test" \ 104 | PYTHONPATH=. PYTHONIOENCODING=utf-8 python3 -m tests.runner tests 105 | @lcov -q --directory . --gcov-tool ./tools/llvm-gcov.sh --capture --no-external --config-file .lcovrc -o cov.info 2> /dev/null 106 | @genhtml -q cov.info -o cov_html -t inet-tool --config-file .lcovrc 107 | @echo "[*] Coverage report:" 108 | @lcov -q --no-list-full-path --list cov.info --config-file .lcovrc 109 | @rm -rf *.gcda cov.info 110 | @echo "[*] Run:\n xdg-open cov_html/src/index.html" 111 | 112 | # make clean 113 | .PHONY: clean 114 | clean: 115 | rm -f inet-tool deps/* inet-ebpf.c 116 | rm -rf venv tests/__pycache__ 117 | rm -rf inet-tool-test *.gcno 118 | rm -rf *.gcda cov_html cov.info 119 | -------------------------------------------------------------------------------- /src/inet-scm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "inet.h" 18 | 19 | #define SOCKADDR_UN_SIZE(sun) \ 20 | ((sun)->sun_path[0] == '\x00' ? 3 + strnlen(&(sun)->sun_path[1], 108) \ 21 | : sizeof(struct sockaddr_un)) 22 | 23 | int recv_fd(int sd, char *buf, int buf_sz) 24 | { 25 | struct iovec iov = { 26 | .iov_base = buf, 27 | .iov_len = buf_sz, 28 | }; 29 | char ctrl[512]; 30 | struct msghdr msg = { 31 | .msg_iov = &iov, 32 | .msg_iovlen = 1, 33 | .msg_control = ctrl, 34 | .msg_controllen = sizeof(ctrl), 35 | }; 36 | int r = recvmsg(sd, &msg, 0); 37 | if (r < 0) { 38 | PFATAL("recvmsg()"); 39 | } 40 | if (r < buf_sz) { 41 | buf[r] = '\x00'; 42 | } else { 43 | buf[buf_sz - 1] = '\x00'; 44 | } 45 | 46 | int fd = -1; 47 | struct cmsghdr *cmsg; 48 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; 49 | cmsg = CMSG_NXTHDR(&msg, cmsg)) { 50 | if (cmsg->cmsg_level == SOL_SOCKET && 51 | cmsg->cmsg_type == SCM_RIGHTS) { 52 | int recv_fds_no = 53 | (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); 54 | int *recv_fds = (int *)CMSG_DATA(cmsg); 55 | int i; 56 | for (i = 0; i < recv_fds_no; i++) { 57 | if (i == 0) { 58 | fd = recv_fds[i]; 59 | } else { 60 | printf("[!] too many fds passed"); 61 | close(recv_fds[i]); 62 | } 63 | } 64 | } 65 | } 66 | return fd; 67 | } 68 | 69 | void inet_scm_serve(struct state *state) 70 | { 71 | int sd = socket(AF_UNIX, SOCK_DGRAM, 0); 72 | if (sd < 0) { 73 | PFATAL("socket()"); 74 | } 75 | struct sockaddr_un unix_addr = { 76 | .sun_family = AF_UNIX, 77 | }; 78 | strncpy(unix_addr.sun_path, state->unix_path, 79 | sizeof(unix_addr.sun_path)); 80 | /* Abstract socket */ 81 | if (unix_addr.sun_path[0] == '@') { 82 | unix_addr.sun_path[0] = '\x00'; 83 | } 84 | 85 | int r = bind(sd, (struct sockaddr *)&unix_addr, 86 | SOCKADDR_UN_SIZE(&unix_addr)); 87 | if (r != 0) { 88 | if (errno == EADDRINUSE) { 89 | char buf[256]; 90 | strncpy(buf, unix_addr.sun_path, sizeof(buf)); 91 | char *dirn = dirname(buf); 92 | int dirfd = open(dirn, O_DIRECTORY | O_RDONLY); 93 | if (dirfd < 0) { 94 | errno = EADDRINUSE; 95 | PFATAL("bind()"); 96 | } 97 | 98 | strncpy(buf, unix_addr.sun_path, sizeof(buf)); 99 | char *basen = basename(buf); 100 | struct stat statbuf = {}; 101 | r = fstatat(dirfd, basen, &statbuf, 0); 102 | if (r) { 103 | close(dirfd); 104 | errno = EADDRINUSE; 105 | PFATAL("bind()"); 106 | } 107 | if ((statbuf.st_mode & S_IFMT) == S_IFSOCK) { 108 | unlinkat(dirfd, basen, 0); 109 | } 110 | close(dirfd); 111 | 112 | /* try again */ 113 | r = bind(sd, (struct sockaddr *)&unix_addr, 114 | SOCKADDR_UN_SIZE(&unix_addr)); 115 | } 116 | if (r != 0) { 117 | PFATAL("bind()"); 118 | } 119 | } 120 | 121 | while (1) { 122 | __gcov_flush(); 123 | char fdname[32]; 124 | int fd = recv_fd(sd, fdname, sizeof(fdname)); 125 | if (fd < 0) { 126 | continue; 127 | } 128 | 129 | int so_domain, so_type, so_protocol; 130 | socklen_t l = sizeof(int); 131 | getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &so_domain, &l); 132 | l = sizeof(int); 133 | getsockopt(fd, SOL_SOCKET, SO_TYPE, &so_type, &l); 134 | l = sizeof(int); 135 | getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &so_protocol, &l); 136 | 137 | struct sockaddr_storage ss = {}; 138 | l = sizeof(ss); 139 | r = getsockname(fd, (struct sockaddr *)&ss, &l); 140 | if (r < 0) { 141 | PFATAL("getsockname()"); 142 | } 143 | const char *addr = net_ntop(&ss); 144 | 145 | /* Fill in generic fdname */ 146 | if ((strnlen(fdname, sizeof(fdname)) == 1 && 147 | fdname[0] == '@') || 148 | (strnlen(fdname, sizeof(fdname)) == 0)) { 149 | strncpy(fdname, addr, sizeof(fdname)); 150 | } 151 | 152 | printf("[+] fd=%d fdname=%s domain=%d type=%d protocol=%d " 153 | "sockname=%s\n", 154 | fd, fdname, so_domain, so_type, so_protocol, addr); 155 | 156 | r = inet_register_socket(state, fd, fdname); 157 | if (r == -1) { 158 | fprintf(stderr, "[!] redir_map full!\n"); 159 | } 160 | close(fd); 161 | } 162 | close(sd); 163 | } 164 | 165 | void inet_scm_register(struct state *state, char **fdnames, char **srvnames) 166 | { 167 | int sd = socket(AF_UNIX, SOCK_DGRAM, 0); 168 | if (sd < 0) { 169 | PFATAL("socket()"); 170 | } 171 | struct sockaddr_un unix_addr = { 172 | .sun_family = AF_UNIX, 173 | }; 174 | strncpy(unix_addr.sun_path, state->unix_path, 175 | sizeof(unix_addr.sun_path)); 176 | /* Abstract socket */ 177 | if (unix_addr.sun_path[0] == '@') { 178 | unix_addr.sun_path[0] = '\x00'; 179 | } 180 | 181 | int r = connect(sd, (struct sockaddr *)&unix_addr, 182 | SOCKADDR_UN_SIZE(&unix_addr)); 183 | if (r != 0) { 184 | PFATAL("connect()"); 185 | } 186 | 187 | int fd = -1; 188 | int skip_fds[] = {0, 1, 2, sd, -1}; 189 | while (1) { 190 | int domain, sock_type, protocol; 191 | fd = find_inherited_fd(fd, skip_fds, &domain, &sock_type, 192 | &protocol); 193 | if (fd < 0) { 194 | break; 195 | } 196 | 197 | if (domain == AF_INET || domain == AF_INET6) { 198 | char *name = NULL; 199 | if (fdnames && fdnames[0]) { 200 | name = fdnames[0]; 201 | fdnames++; 202 | } else if (srvnames && srvnames[0]) { 203 | name = srvnames[0]; 204 | srvnames++; 205 | } else { 206 | name = "@"; 207 | } 208 | 209 | struct iovec iov = { 210 | .iov_base = name, 211 | .iov_len = strlen(name), 212 | }; 213 | 214 | char ctrl[CMSG_SPACE(sizeof(int))] = {}; 215 | struct msghdr msg = { 216 | .msg_iov = &iov, 217 | .msg_iovlen = 1, 218 | .msg_control = ctrl, 219 | .msg_controllen = sizeof(ctrl), 220 | }; 221 | 222 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 223 | cmsg->cmsg_level = SOL_SOCKET; 224 | cmsg->cmsg_type = SCM_RIGHTS; 225 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 226 | int *fdptr = (int *)CMSG_DATA(cmsg); 227 | *fdptr = fd; 228 | 229 | fprintf(stderr, 230 | "[.] Registering service \"%s\" fd=%d " 231 | "domain=%d type=%d protocol=%d\n", 232 | name, fd, domain, sock_type, protocol); 233 | r = sendmsg(sd, &msg, 0); 234 | if (r < 0) { 235 | PFATAL("sendmsg()"); 236 | } 237 | } 238 | } 239 | 240 | close(sd); 241 | } 242 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import errno 3 | import os 4 | import shlex 5 | import signal 6 | import socket 7 | import subprocess 8 | import sys 9 | import unittest 10 | import fcntl 11 | 12 | LIBC = ctypes.CDLL("libc.so.6") 13 | INETTOOLBIN = os.environ.get('INETTOOLBIN') 14 | IP_FREEBIND = 15 15 | CLONE_NEWNET = 0x40000000 16 | original_net_ns = open("/proc/self/ns/net", 'rb') 17 | SOCKET_TIMEOUT = 5 # seconds 18 | 19 | HELLO_WORLD_SERVER='./tools/hello-world-server.py' 20 | 21 | if True: 22 | r = LIBC.unshare(CLONE_NEWNET) 23 | if r != 0: 24 | print("[!] Are you root? Need unshare() syscall.") 25 | sys.exit(-1) 26 | LIBC.setns(original_net_ns.fileno(), CLONE_NEWNET) 27 | 28 | 29 | RUN_CMD_BUFFER = [] 30 | 31 | class Process(object): 32 | def __init__(self, argv, close_fds=True): 33 | self.p = subprocess.Popen(argv, 34 | stdout=subprocess.PIPE, 35 | stderr=subprocess.PIPE, 36 | close_fds=close_fds) 37 | self.rc = None 38 | RUN_CMD_BUFFER.append((' '.join(argv), self)) 39 | 40 | def stdout_line(self): 41 | while True: 42 | o = self.p.stdout.readline().decode('utf8') 43 | if o == 'PASS\n' or o.startswith("coverage: "): 44 | continue 45 | return o 46 | 47 | def stderr_line(self): 48 | while True: 49 | e = self.p.stderr.readline().decode('utf8') 50 | if not e: 51 | continue 52 | if e.startswith('[o]'): 53 | print(e) 54 | continue 55 | return e 56 | 57 | def close(self, kill=False): 58 | '''Returns process return code.''' 59 | if self.p: 60 | if kill: 61 | self.p.send_signal(signal.SIGINT) 62 | self.p.send_signal(signal.SIGTERM) 63 | self.rc = self.p.wait() 64 | self.p.stdout.close() 65 | self.p.stderr.close() 66 | self.p = None 67 | return self.rc 68 | 69 | 70 | class TestCase(unittest.TestCase): 71 | prev_errors = 0 72 | prev_failures = 0 73 | 74 | def run(self, result = None): 75 | # remember result for use in tearDown 76 | self.currentResult = result 77 | unittest.TestCase.run(self, result) 78 | 79 | def setUp(self): 80 | r = LIBC.unshare(CLONE_NEWNET) 81 | if r != 0: 82 | print("[!] Are you root? Need unshare() syscall.") 83 | sys.exit(-1) 84 | os.system("ip link set lo up") 85 | 86 | while RUN_CMD_BUFFER: 87 | _, p = RUN_CMD_BUFFER.pop() 88 | if getattr(p, '__class__') == Process: 89 | p.close(kill=True) 90 | else: 91 | p.close() 92 | if self.currentResult: 93 | self.prev_errors = len(self.currentResult.errors) 94 | self.prev_failures = len(self.currentResult.failures) 95 | 96 | def tearDown(self): 97 | # Clean up /sys/fs/bpf after test. Easiest to just call unload 98 | p = inet_tool("unload") 99 | p.close() 100 | 101 | LIBC.setns(original_net_ns.fileno(), CLONE_NEWNET) 102 | if len(self.currentResult.errors) > self.prev_errors or len(self.currentResult.failures) > self.prev_failures: 103 | print("\n[!] Test Failed. Executed programs:") 104 | for cmd, p in RUN_CMD_BUFFER: 105 | print("\t%s" % (cmd,)) 106 | p.close(kill=True) 107 | 108 | def inet_tool_list(self): 109 | p = inet_tool('list') 110 | list_of_services = [] 111 | list_of_bindings = [] 112 | 113 | self.assertIn("List of services:", p.stdout_line()) 114 | while True: 115 | line = p.stdout_line() 116 | if "List of" in line: 117 | break 118 | self.assertEqual(line[0], '\t') 119 | list_of_services.append(line[1:-1]) 120 | self.assertIn("List of bindings:", line) 121 | while True: 122 | line = p.stdout_line() 123 | if not line: 124 | break 125 | self.assertEqual(line[0], '\t') 126 | list_of_bindings.append(line[1:-1]) 127 | rc = p.close() 128 | self.assertEqual(rc, 0) 129 | return list_of_services, list_of_bindings 130 | 131 | def assertTcpConnRefused(self, ip="127.0.0.1", port=0): 132 | if len(ip.split(':')) <= 2: 133 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 134 | else: 135 | s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 136 | s.settimeout(SOCKET_TIMEOUT) 137 | 138 | with self.assertRaises(socket.error) as e: 139 | s.connect((ip, port)) 140 | s.close() 141 | self.assertEqual(e.exception.errno, errno.ECONNREFUSED) 142 | 143 | def assertTcpConnSuccess(self, ip="127.0.0.1", port=0): 144 | if len(ip.split(':')) <= 2: 145 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 146 | else: 147 | s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 148 | s.settimeout(SOCKET_TIMEOUT) 149 | 150 | s.connect((ip, port)) 151 | s.close() 152 | 153 | def assertTcpHelloWorld(self, ip="127.0.0.1", port=0): 154 | if len(ip.split(':')) <= 2: 155 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 156 | else: 157 | s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 158 | s.settimeout(SOCKET_TIMEOUT) 159 | 160 | s.connect((ip, port)) 161 | data = s.recv(2048) 162 | self.assertEqual(b'Hello world!\r\n', data) 163 | s.close() 164 | 165 | execno = 0 166 | last_cmd = "" 167 | 168 | def inet_tool(argv1=[], close_fds=True): 169 | global execno, last_cmd 170 | execno += 1 171 | argv0 = shlex.split(INETTOOLBIN % {"nr": execno}) 172 | 173 | if isinstance(argv1, str): 174 | argv1 = shlex.split(argv1) 175 | 176 | a = argv0 + argv1 177 | 178 | return Process(a, close_fds=close_fds) 179 | 180 | 181 | 182 | def bind_tcp(ip='127.0.0.1', port=0, cloexec=True, reuseaddr=True, reuseport=True, cleanup=True, backlog=8): 183 | if len(ip.split(':')) <= 2: 184 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 185 | else: 186 | s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 187 | 188 | s.settimeout(SOCKET_TIMEOUT) 189 | 190 | if cleanup: 191 | RUN_CMD_BUFFER.append(("bind_tcp", s)) 192 | 193 | flags = fcntl.fcntl(s, fcntl.F_GETFD) 194 | if cloexec: 195 | flags |= fcntl.FD_CLOEXEC 196 | else: 197 | flags &= ~fcntl.FD_CLOEXEC 198 | fcntl.fcntl(s, fcntl.F_SETFD, flags) 199 | 200 | if reuseaddr: 201 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 202 | if reuseport: 203 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 204 | 205 | s.setsockopt(socket.IPPROTO_IP, IP_FREEBIND, 1) 206 | 207 | s.bind((ip, port)) 208 | s.listen(backlog) 209 | 210 | addr = s.getsockname() 211 | return s, addr[1] 212 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "inet.h" 9 | 10 | void print_usage() 11 | { 12 | printf( 13 | #include "help.txt" 14 | ); 15 | } 16 | 17 | enum commands { 18 | CMD_UNLOAD, 19 | CMD_LOAD, 20 | CMD_INFO, 21 | CMD_LIST, 22 | CMD_BIND, 23 | CMD_UNBIND, 24 | CMD_REGISTER, 25 | CMD_UNREGISTER, 26 | CMD_SCM_SERVE, 27 | CMD_SCM_REGISTER, 28 | }; 29 | 30 | struct { 31 | char *txt; 32 | int cmd; 33 | } commands_txt[] = { 34 | {"unload", CMD_UNLOAD}, 35 | {"load", CMD_LOAD}, 36 | {"info", CMD_INFO}, 37 | {"list", CMD_LIST}, 38 | {"bind", CMD_BIND}, 39 | {"unbind", CMD_UNBIND}, 40 | {"register", CMD_REGISTER}, 41 | {"unregister", CMD_UNREGISTER}, 42 | {"scm_serve", CMD_SCM_SERVE}, 43 | {"scm_register", CMD_SCM_REGISTER}, 44 | {NULL, -1}, 45 | }; 46 | 47 | int main(int argc, char *argv[]) 48 | { 49 | int return_code = 0; 50 | 51 | struct state *state = (struct state *)calloc(sizeof(struct state), 1); 52 | state->unix_path = "@inet-scm-service"; 53 | uint64_t net_ns_inode = get_net_ns_inode(); 54 | if (net_ns_inode == 0) { 55 | PFATAL("open(/proc/self/ns/net)"); 56 | } 57 | 58 | snprintf(state->sys_fs_obj_prefix, sizeof(state->sys_fs_obj_prefix), 59 | "/sys/fs/bpf/%lu_", net_ns_inode); 60 | 61 | bump_memlimit(); 62 | 63 | /* Split argv into two parts - before and after -- * 64 | * getopt_long() does an interesting thing. It removes the 65 | * first iteration of "--" argument. The semantics are: on the 66 | * first "--" argument, stop parsing others. This is fine, but 67 | * we need more - we want to know just where that argument is 68 | * present. So we do a hack - we split the ist into two parts, 69 | * and getopt parse only first. */ 70 | char ***list_of_argv = argv_split(argv, "--", argc, 2); 71 | 72 | { 73 | static struct option long_options[] = { 74 | {"unix", required_argument, 0, 'u'}, 75 | {"help", no_argument, 0, 'h'}, 76 | {NULL, 0, 0, 0}}; 77 | optind = 1; 78 | while (1) { 79 | int option_index = 0; 80 | int arg = getopt_long( 81 | argv_len(list_of_argv[0]), list_of_argv[0], 82 | optstring_from_long_options(long_options), 83 | long_options, &option_index); 84 | if (arg == -1) { 85 | break; 86 | } 87 | 88 | switch (arg) { 89 | default: 90 | case 0: 91 | fprintf(stderr, "Unknown option: %s", 92 | list_of_argv[0][optind]); 93 | exit(-1); 94 | break; 95 | case '?': 96 | exit(-1); 97 | break; 98 | case 'u': 99 | state->unix_path = optarg; 100 | break; 101 | case 'h': 102 | print_usage(); 103 | exit(0); 104 | break; 105 | } 106 | } 107 | } 108 | 109 | char *cmd = list_of_argv[0][optind]; 110 | 111 | if (cmd == NULL) { 112 | fprintf(stderr, 113 | "[!] Select a command. Perhaps \"inet-tool info\" or " 114 | "\"inet-tool list\".\n"); 115 | exit(-1); 116 | } 117 | 118 | int command = -1; 119 | int i; 120 | for (i = 0; commands_txt[i].txt != NULL; i++) { 121 | if (strcmp(cmd, commands_txt[i].txt) == 0) { 122 | command = commands_txt[i].cmd; 123 | break; 124 | } 125 | } 126 | if (command < 0) { 127 | fprintf(stderr, 128 | "[!] Unknown operation \"%s\", try \"inet-tool " 129 | "--help\".\n", 130 | cmd); 131 | exit(3); 132 | } 133 | 134 | /* Parsing env needed for "register" and "scm_register" cmds. */ 135 | char **fdnames_argv = NULL; 136 | const char *listen_fdnames = getenv("LISTEN_FDNAMES"); 137 | if (listen_fdnames != NULL) { 138 | fdnames_argv = parse_argv(listen_fdnames, ':'); 139 | } 140 | 141 | if (command == CMD_UNLOAD) { 142 | /* we can unlink maps without opening them. */ 143 | return_code = inet_unload(state); 144 | goto cleanup; 145 | } 146 | 147 | if (command == CMD_SCM_REGISTER) { 148 | /* SCM_REGISTER is is unpriviledged. run it before 149 | * map operations. */ 150 | inet_scm_register(state, fdnames_argv, 151 | &list_of_argv[0][optind + 1]); 152 | 153 | if (list_of_argv[0] && list_of_argv[1]) { 154 | char **child_argv = list_of_argv[1]; 155 | char *flat_argv = argv_join(child_argv, " "); 156 | fprintf(stderr, "[+] %i running: %s\n", getpid(), 157 | flat_argv); 158 | free(flat_argv); 159 | __gcov_flush(); 160 | execvp(child_argv[0], child_argv); 161 | PFATAL("execvp()"); 162 | } 163 | goto cleanup; 164 | } 165 | 166 | inet_open_verify_maps(state, 0); 167 | inet_open_verify_link(state); 168 | 169 | if (command == CMD_LOAD) { 170 | inet_load(state); 171 | goto cleanup; 172 | } 173 | 174 | if (command == CMD_INFO) { 175 | return_code = inet_prog_info(state); 176 | goto cleanup; 177 | } 178 | 179 | int recognized = inet_prog_verify(); 180 | if (recognized == -1) { 181 | fprintf(stderr, 182 | "[!] SK_LOOKUP program not found. " 183 | "Consdider running \"inet-tool load\".\n"); 184 | exit(-1); 185 | } 186 | 187 | if (recognized == 0) { 188 | fprintf(stderr, 189 | "[!] SK_LOOKUP program version is " 190 | "unrecognized. " 191 | "Consdider running \"inet-tool unload; inet-tool " 192 | "load\"\n"); 193 | exit(-1); 194 | } 195 | 196 | inet_open_verify_maps(state, 1); 197 | 198 | if (command == CMD_LIST) { 199 | inet_list(state); 200 | goto cleanup; 201 | } 202 | 203 | if (command == CMD_BIND) { 204 | if (argv_len(&list_of_argv[0][optind + 1]) != 3) { 205 | PFATAL("bind takes three parameters"); 206 | } 207 | 208 | struct inet_addr addr = {}; 209 | parse_inet_addr(&addr, list_of_argv[0][optind + 1], 210 | list_of_argv[0][optind + 2]); 211 | 212 | char *service = list_of_argv[0][optind + 3]; 213 | 214 | inet_bind(state, &addr, service); 215 | goto cleanup; 216 | } 217 | 218 | if (command == CMD_UNBIND) { 219 | if (argv_len(&list_of_argv[0][optind + 1]) < 2) { 220 | PFATAL("unbind takes two parameters"); 221 | } 222 | struct inet_addr addr = {}; 223 | parse_inet_addr(&addr, list_of_argv[0][optind + 1], 224 | list_of_argv[0][optind + 2]); 225 | 226 | inet_unbind(state, &addr); 227 | goto cleanup; 228 | } 229 | 230 | if (command == CMD_REGISTER) { 231 | inet_register(state, fdnames_argv, 232 | &list_of_argv[0][optind + 1]); 233 | 234 | if (list_of_argv[0] && list_of_argv[1]) { 235 | char **child_argv = list_of_argv[1]; 236 | char *flat_argv = argv_join(child_argv, " "); 237 | fprintf(stderr, "[+] %i running: %s\n", getpid(), 238 | flat_argv); 239 | free(flat_argv); 240 | __gcov_flush(); 241 | execvp(child_argv[0], child_argv); 242 | PFATAL("execvp()"); 243 | } 244 | goto cleanup; 245 | } 246 | 247 | if (command == CMD_UNREGISTER) { 248 | if (argv_len(&list_of_argv[0][optind + 1]) != 1) { 249 | PFATAL("unregister takes one parameter"); 250 | } 251 | char *service = list_of_argv[0][optind + 1]; 252 | 253 | inet_unregister(state, service); 254 | goto cleanup; 255 | } 256 | 257 | if (command == CMD_SCM_SERVE) { 258 | setbuf(stdout, NULL); 259 | printf("[+] Waiting for SCM_RIGHTS sockets on %s\n", 260 | state->unix_path); 261 | inet_scm_serve(state); 262 | goto cleanup; 263 | } 264 | 265 | PFATAL("Unhandled command"); 266 | cleanup: 267 | /* Free memory */ 268 | if (fdnames_argv) { 269 | free(fdnames_argv); 270 | } 271 | 272 | { 273 | char ***child_argv = list_of_argv; 274 | while (*child_argv) { 275 | free(*child_argv); 276 | child_argv++; 277 | } 278 | free(list_of_argv); 279 | } 280 | 281 | free(state); 282 | return return_code; 283 | } 284 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../ebpf/inet-kern-shared.h" 14 | 15 | #include "inet.h" 16 | 17 | char *terminating_strncpy(char *dest, const char *src, size_t n) 18 | { 19 | char *r = strncpy(dest, src, n); 20 | dest[n - 1] = '\0'; 21 | return r; 22 | } 23 | 24 | static void net_addr_from_name(struct sockaddr_storage *ss, 25 | const char *src_host) 26 | { 27 | char buf[256]; 28 | terminating_strncpy(buf, src_host, sizeof(buf)); 29 | char *host = buf; 30 | 31 | struct sockaddr_in *sin = (struct sockaddr_in *)ss; 32 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; 33 | 34 | if (inet_pton(AF_INET, host, &sin->sin_addr) == 1) { 35 | sin->sin_family = AF_INET; 36 | return; 37 | } 38 | 39 | if (*host && host[0] == '[' && host[strlen(host) - 1] == ']') { 40 | host[strlen(host) - 1] = '\x0'; 41 | host += 1; 42 | } 43 | 44 | if (inet_pton(AF_INET6, host, &sin6->sin6_addr) == 1) { 45 | sin6->sin6_family = AF_INET6; 46 | return; 47 | } 48 | 49 | PFATAL("Neither INET nor INET6 address %s", host); 50 | } 51 | 52 | int net_parse_sockaddr(struct sockaddr_storage *ss, const char *src_addr, 53 | int *subnet_ptr) 54 | { 55 | char addr[256]; 56 | terminating_strncpy(addr, src_addr, sizeof(addr)); 57 | 58 | long subnet = -1; 59 | *ss = (struct sockaddr_storage){}; 60 | 61 | char *colon = strrchr(addr, ':'); 62 | if (colon == NULL || colon[1] == '\0') { 63 | PFATAL("%s doesn't contain a port number.", addr); 64 | } 65 | *colon = '\0'; 66 | 67 | char *endptr; 68 | long port = strtol(&colon[1], &endptr, 10); 69 | if (port < 0 || port > 65535 || *endptr != '\0') { 70 | PFATAL("Invalid port number %s", &colon[1]); 71 | } 72 | 73 | char *slash = strrchr(addr, '/'); 74 | if (slash) { 75 | *slash = '\0'; 76 | char *endptr; 77 | subnet = strtol(&slash[1], &endptr, 10); 78 | if (*endptr != '\0') { 79 | subnet = -2; 80 | } else if (subnet < 0 || subnet > 128) { 81 | subnet = -2; 82 | } 83 | } 84 | net_addr_from_name(ss, addr); 85 | 86 | struct sockaddr_in *sin = (struct sockaddr_in *)ss; 87 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; 88 | 89 | switch (ss->ss_family) { 90 | case AF_INET: 91 | sin->sin_port = htons(port); 92 | if (subnet > 32) { 93 | subnet = -2; 94 | } 95 | break; 96 | case AF_INET6: 97 | sin6->sin6_port = htons(port); 98 | if (subnet > 128) { 99 | subnet = -2; 100 | } 101 | break; 102 | default: 103 | PFATAL(""); 104 | } 105 | *subnet_ptr = subnet; 106 | return -1; 107 | } 108 | 109 | const char *net_ntop(struct sockaddr_storage *ss) 110 | { 111 | char s[INET6_ADDRSTRLEN + 1]; 112 | static char a[INET6_ADDRSTRLEN + 32]; 113 | struct sockaddr_in *sin = (struct sockaddr_in *)ss; 114 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; 115 | int port; 116 | const char *r; 117 | switch (ss->ss_family) { 118 | case AF_INET: 119 | port = htons(sin->sin_port); 120 | r = inet_ntop(sin->sin_family, &sin->sin_addr, s, sizeof(s)); 121 | if (r == NULL) { 122 | PFATAL("inet_ntop()"); 123 | } 124 | snprintf(a, sizeof(a), "%s:%i", s, port); 125 | break; 126 | case AF_INET6: 127 | r = inet_ntop(sin6->sin6_family, &sin6->sin6_addr, s, 128 | sizeof(s)); 129 | if (r == NULL) { 130 | PFATAL("inet_ntop()"); 131 | } 132 | port = htons(sin6->sin6_port); 133 | snprintf(a, sizeof(a), "[%s]:%i", s, port); 134 | break; 135 | default: 136 | PFATAL(""); 137 | } 138 | return a; 139 | } 140 | 141 | const char *sprint_addr(struct addr *k) 142 | { 143 | int do_subnet = 1; 144 | int subnet; 145 | char raw_ip[128]; 146 | if (k->addr.ip_as_w[0] == 0 && k->addr.ip_as_w[1] == 0 && 147 | k->addr.ip_as_w[2] == htonl(0x0000ffff)) { 148 | inet_ntop(AF_INET, &k->addr.ip_as_w[3], raw_ip, sizeof(raw_ip)); 149 | subnet = 32 - ((sizeof(struct addr) - 4) * 8 - k->prefixlen); 150 | if (subnet == 32) { 151 | do_subnet = 0; 152 | } 153 | } else { 154 | char c[128]; 155 | inet_ntop(AF_INET6, &k->addr.ip_as_w[0], c, sizeof(c)); 156 | snprintf(raw_ip, sizeof(raw_ip), "[%s]", c); 157 | subnet = 128 - ((sizeof(struct addr) - 4) * 8 - k->prefixlen); 158 | if (subnet == 128) { 159 | do_subnet = 0; 160 | } 161 | } 162 | 163 | static char buf[128]; 164 | if (do_subnet) { 165 | snprintf(buf, sizeof(buf), "%d %s/%d:%d", k->protocol, raw_ip, 166 | subnet, k->port); 167 | } else { 168 | snprintf(buf, sizeof(buf), "%d %s:%d", k->protocol, raw_ip, 169 | k->port); 170 | } 171 | return buf; 172 | } 173 | 174 | const char *optstring_from_long_options(const struct option *opt) 175 | { 176 | static char optstring[256] = {0}; 177 | char *osp = optstring; 178 | 179 | for (; opt->name != NULL; opt++) { 180 | if (opt->flag == 0 && opt->val > 0 && opt->val < 256) { 181 | *osp++ = opt->val; 182 | switch (opt->has_arg) { 183 | case optional_argument: 184 | *osp++ = ':'; 185 | *osp++ = ':'; 186 | break; 187 | case required_argument: 188 | *osp++ = ':'; 189 | break; 190 | } 191 | } 192 | } 193 | *osp++ = '\0'; 194 | 195 | if (osp - optstring >= (int)sizeof(optstring)) { 196 | abort(); 197 | } 198 | return optstring; 199 | } 200 | 201 | char ***argv_split(char **argv, const char *delimiter, int upper_bound, int max) 202 | { 203 | upper_bound += 1; 204 | 205 | int child_no = 0; 206 | char ***child_argv = malloc(upper_bound * sizeof(char *)); 207 | 208 | while (*argv) { 209 | int pos = 0; 210 | child_argv[child_no] = malloc(upper_bound * sizeof(char *)); 211 | for (; *argv; argv++) { 212 | if (strcmp(*argv, delimiter) == 0 && max > 1) { 213 | argv++; 214 | break; 215 | } else { 216 | child_argv[child_no][pos++] = *argv; 217 | } 218 | } 219 | max -= 1; 220 | child_argv[child_no][pos++] = NULL; 221 | child_argv[child_no] = 222 | realloc(child_argv[child_no], pos * sizeof(char *)); 223 | child_no += 1; 224 | } 225 | child_argv[child_no] = NULL; 226 | child_no += 1; 227 | return realloc(child_argv, child_no * sizeof(char *)); 228 | } 229 | 230 | int argv_len(char **argv) 231 | { 232 | if (argv == NULL) { 233 | return 0; 234 | } 235 | int i; 236 | for (i = 0; argv[i]; i++) { 237 | } 238 | return i; 239 | } 240 | 241 | char **parse_argv(const char *str, char delim) 242 | { 243 | int str_len = strlen(str); 244 | int i, items = 1; 245 | for (i = 0; i < str_len; i++) { 246 | if (str[i] == delim) { 247 | items += 1; 248 | } 249 | } 250 | 251 | char **argv = malloc(sizeof(char *) * (items + 1) + str_len + 1); 252 | char *nstr = (char *)&argv[items + 1]; 253 | memcpy(nstr, str, str_len + 1); 254 | 255 | char delim_s[2] = {delim, '\x00'}; 256 | char *s = nstr, *saveptr = NULL, **a = argv; 257 | 258 | for (;; s = NULL) { 259 | char *token = strtok_r(s, delim_s, &saveptr); 260 | if (token == NULL) 261 | break; 262 | 263 | a[0] = token; 264 | a += 1; 265 | } 266 | *a = NULL; 267 | 268 | return argv; 269 | } 270 | 271 | /* Returns malloced memory */ 272 | char *argv_join(char **argv, const char *delim) 273 | { 274 | int len = 0, delim_len = strlen(delim); 275 | char **a; 276 | for (a = argv; *a; a++) { 277 | len += strlen(*a) + delim_len; 278 | } 279 | if (len) 280 | len -= delim_len; 281 | char *s = malloc(len + 1), *p = s; 282 | for (a = argv; *a; a++) { 283 | if (a != argv) 284 | p = stpcpy(p, delim); 285 | p = stpcpy(p, *a); 286 | } 287 | *p = '\0'; 288 | return s; 289 | } 290 | 291 | uint64_t get_net_ns_inode() 292 | { 293 | int ns_fd = open("/proc/self/ns/net", O_RDONLY); 294 | if (ns_fd < 0) { 295 | return 0; 296 | } 297 | struct stat stat; 298 | int r = fstat(ns_fd, &stat); 299 | close(ns_fd); 300 | if (r != 0) { 301 | return 0; 302 | } 303 | 304 | return stat.st_ino; 305 | } 306 | 307 | int find_inherited_fd(int last_fd, int *skip_fds, int *domain, int *sock_type, 308 | int *protocol) 309 | { 310 | int ebadf_errors_allowed = 32; 311 | int fd; 312 | for (fd = last_fd + 1; ebadf_errors_allowed > 0; fd++) { 313 | int *s; 314 | 315 | for (s = skip_fds; *s != -1; s++) { 316 | if (fd == *s) { 317 | goto again; 318 | } 319 | } 320 | socklen_t len = sizeof(*domain); 321 | errno = 0; 322 | int r = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, domain, &len); 323 | if (r) { 324 | if (errno == EBADF) { 325 | ebadf_errors_allowed--; 326 | } 327 | continue; 328 | } 329 | len = sizeof(*sock_type); 330 | r = getsockopt(fd, SOL_SOCKET, SO_TYPE, sock_type, &len); 331 | if (r) { 332 | continue; 333 | } 334 | len = sizeof(*protocol); 335 | r = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, protocol, &len); 336 | if (r) { 337 | continue; 338 | } 339 | return fd; 340 | again:; 341 | } 342 | return -1; 343 | } 344 | 345 | static void clear_net_bottombits(uint8_t *dst, int dst_sz, int bottombits) 346 | { 347 | size_t bytes = bottombits / 8; 348 | 349 | if (dst_sz * 8 <= bottombits) { 350 | memset(dst, 0, dst_sz); 351 | return; 352 | } 353 | 354 | memset(dst + dst_sz - bytes, 0, bytes); 355 | if (bottombits & 7) 356 | dst[dst_sz - bytes - 1] &= 0xff << (bottombits & 7); 357 | } 358 | 359 | int get_bottombits(struct sockaddr_storage *ss, int subnet) 360 | { 361 | int bottombits = 0; 362 | if (subnet >= 0) { 363 | if (ss->ss_family == AF_INET) { 364 | bottombits = 32 - subnet; 365 | } 366 | if (ss->ss_family == AF_INET6) { 367 | bottombits = 128 - subnet; 368 | } 369 | } 370 | return bottombits; 371 | } 372 | 373 | void parse_inet_addr(struct inet_addr *addr, char *protocol, char *host) 374 | { 375 | addr->protocol = atoi(protocol); 376 | 377 | int subnet = -1; 378 | net_parse_sockaddr(&addr->ss, host, &subnet); 379 | if (subnet < 0) { 380 | if (addr->ss.ss_family == AF_INET) { 381 | subnet = 32; 382 | } 383 | if (addr->ss.ss_family == AF_INET6) { 384 | subnet = 128; 385 | } 386 | } 387 | addr->subnet = subnet; 388 | 389 | int bottombits = get_bottombits(&addr->ss, subnet); 390 | if (addr->ss.ss_family == AF_INET) { 391 | struct sockaddr_in *sin = (struct sockaddr_in *)&addr->ss; 392 | clear_net_bottombits((uint8_t *)&sin->sin_addr, 393 | sizeof(sin->sin_addr), bottombits); 394 | } 395 | if (addr->ss.ss_family == AF_INET6) { 396 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr->ss; 397 | clear_net_bottombits((uint8_t *)&sin6->sin6_addr, 398 | sizeof(sin6->sin6_addr), bottombits); 399 | } 400 | } 401 | 402 | void bump_memlimit() 403 | { 404 | /* [*] SOCKMAP requires more than 16MiB of locked mem */ 405 | 406 | struct rlimit rlim_inf = {RLIM_INFINITY, RLIM_INFINITY}; 407 | int r = setrlimit(RLIMIT_MEMLOCK, &rlim_inf); 408 | if (r != 0) { 409 | struct rlimit rlim = { 410 | .rlim_cur = 128 * 1024 * 1024, 411 | .rlim_max = 128 * 1024 * 1024, 412 | }; 413 | /* ignore error */ 414 | setrlimit(RLIMIT_MEMLOCK, &rlim); 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /tests/test_basic.py: -------------------------------------------------------------------------------- 1 | from . import base 2 | import errno 3 | 4 | 5 | class BasicTest(base.TestCase): 6 | def test_no_param(self): 7 | ''' Verify message on no CLI parameter ''' 8 | p = base.inet_tool() 9 | self.assertIn("Select a command", p.stderr_line()) 10 | rc = p.close() 11 | self.assertEqual(rc, 255) 12 | 13 | def test_wrong_command(self): 14 | ''' Verify message on wrong CLI command ''' 15 | p = base.inet_tool("sregister") 16 | self.assertIn('[!] Unknown operation "sregister"', p.stderr_line()) 17 | rc = p.close() 18 | self.assertEqual(rc, 3) 19 | 20 | def test_help(self): 21 | ''' Verify message on --help ''' 22 | p = base.inet_tool('--help') 23 | self.assertIn("Usage: inet-tool", p.stdout_line()) 24 | rc = p.close() 25 | self.assertEqual(rc, 0) 26 | 27 | def test_basic_lifecycle(self): 28 | ''' Verify 'info' before and after 'load', verify 'unload' ''' 29 | p = base.inet_tool('info') 30 | self.assertIn("SK_LOOKUP program absent", p.stdout_line()) 31 | rc = p.close() 32 | self.assertEqual(rc, 1) 33 | 34 | p = base.inet_tool('load') 35 | self.assertIn("SK_LOOKUP program loaded", p.stdout_line()) 36 | rc = p.close() 37 | self.assertEqual(rc, 0) 38 | 39 | p = base.inet_tool('load') 40 | self.assertIn("SK_LOOKUP program loaded", p.stdout_line()) 41 | rc = p.close() 42 | self.assertEqual(rc, 0) 43 | 44 | p = base.inet_tool('info') 45 | self.assertIn("SK_LOOKUP program present", p.stdout_line()) 46 | rc = p.close() 47 | self.assertEqual(rc, 0) 48 | 49 | p = base.inet_tool('unload') 50 | self.assertIn("Unpinned SK_LOOKUP link", p.stdout_line()) 51 | rc = p.close() 52 | self.assertEqual(rc, 0) 53 | 54 | p = base.inet_tool('info') 55 | self.assertIn("SK_LOOKUP program absent", p.stdout_line()) 56 | rc = p.close() 57 | self.assertEqual(rc, 1) 58 | 59 | p = base.inet_tool('unload') 60 | self.assertIn("Failed to unpin SK_LOOKUP link", p.stdout_line()) 61 | rc = p.close() 62 | self.assertEqual(rc, errno.ENOENT) 63 | 64 | 65 | def test_basic_tcp_bind(self): 66 | ''' Veify creation and removal of simple tcp binding ''' 67 | p = base.inet_tool('load') 68 | self.assertIn("SK_LOOKUP program loaded", p.stdout_line()) 69 | 70 | svcs, bdgs = self.inet_tool_list() 71 | self.assertFalse(svcs) 72 | self.assertFalse(bdgs) 73 | 74 | p = base.inet_tool('bind 6 0.0.0.0:1234 x') 75 | self.assertIn("6 0.0.0.0:1234 -> x", p.stdout_line()) 76 | rc = p.close() 77 | self.assertEqual(rc, 0) 78 | 79 | svcs, bdgs = self.inet_tool_list() 80 | self.assertFalse(svcs) 81 | self.assertEqual(bdgs, ['6 0.0.0.0:1234 -> x']) 82 | 83 | p = base.inet_tool('unbind 6 0.0.0.0:1234 x') 84 | self.assertIn("6 0.0.0.0:1234", p.stdout_line()) 85 | rc = p.close() 86 | self.assertEqual(rc, 0) 87 | 88 | svcs, bdgs = self.inet_tool_list() 89 | self.assertFalse(svcs) 90 | self.assertFalse(bdgs) 91 | 92 | 93 | def test_basic_tcp_echo_2_tuple(self): 94 | ''' Verify if basic binding actually work ''' 95 | base.inet_tool('load').close() 96 | base.inet_tool('bind 6 127.0.0.1:1234 x').close() 97 | sd, srv_port = base.bind_tcp(cloexec=False) 98 | self.assertTcpConnSuccess('127.0.0.1', srv_port) 99 | self.assertTcpConnRefused('127.0.0.1', 1234) 100 | 101 | p = base.inet_tool('register x', close_fds=False) 102 | self.assertIn("[+] x -> #0", p.stderr_line()) 103 | svcs, bdgs = self.inet_tool_list() 104 | self.assertIn('x\t= #0 sk:', svcs[0]) 105 | self.assertEqual(len(bdgs), 1) 106 | 107 | self.assertTcpConnSuccess('127.0.0.1', srv_port) 108 | self.assertTcpConnSuccess('127.0.0.1', 1234) 109 | 110 | # Socket shall be closed - SOCKARRAY shall not hold a reference. 111 | sd.close() 112 | 113 | self.assertTcpConnRefused('127.0.0.1', srv_port) 114 | self.assertTcpConnRefused('127.0.0.1', 1234) 115 | 116 | svcs, bdgs = self.inet_tool_list() 117 | self.assertEqual(svcs, ['x\t= #0 sk:(nil)']) 118 | self.assertEqual(bdgs, ['6 127.0.0.1:1234 -> x']) 119 | 120 | 121 | def test_basic_tcp_echo_1_tuple(self): 122 | ''' Verify if basic binding actually work ''' 123 | base.inet_tool('load').close() 124 | base.inet_tool('bind 6 127.0.0.1:0 x').close() 125 | sd, srv_port = base.bind_tcp(cloexec=False) 126 | self.assertTcpConnSuccess('127.0.0.1', srv_port) 127 | self.assertTcpConnRefused('127.0.0.1', 1) 128 | 129 | p = base.inet_tool('register x', close_fds=False) 130 | self.assertIn("[+] x -> #0", p.stderr_line()) 131 | svcs, bdgs = self.inet_tool_list() 132 | self.assertIn('x\t= #0 sk:', svcs[0]) 133 | self.assertEqual(len(bdgs), 1) 134 | 135 | self.assertTcpConnSuccess('127.0.0.1', 1) 136 | self.assertTcpConnSuccess('127.0.0.1', 2) 137 | self.assertTcpConnRefused('127.0.0.2', 1) 138 | self.assertTcpConnRefused('127.0.0.2', 2) 139 | 140 | # Socket shall be closed - SOCKARRAY shall not hold a reference. 141 | sd.close() 142 | 143 | self.assertTcpConnRefused('127.0.0.1', 1) 144 | 145 | 146 | def test_basic_register(self): 147 | ''' Verify 'register' and 'unregister' ''' 148 | base.inet_tool('load').close() 149 | 150 | p = base.inet_tool('register a') 151 | self.assertIn("[+] a -> #0 (sk:nil)", p.stderr_line()) 152 | rc = p.close() 153 | self.assertEqual(rc, 0) 154 | 155 | svcs, bdgs = self.inet_tool_list() 156 | self.assertEqual(svcs, ['a\t= #0 sk:(nil)']) 157 | self.assertFalse(bdgs) 158 | 159 | p = base.inet_tool('unregister a') 160 | self.assertIn("[-] a ->", p.stderr_line()) 161 | rc = p.close() 162 | self.assertEqual(rc, 0) 163 | 164 | svcs, bdgs = self.inet_tool_list() 165 | self.assertFalse(svcs) 166 | self.assertFalse(bdgs) 167 | 168 | 169 | def test_basic_tcp_echo_subnet(self): 170 | ''' Verify if binding subnets actually work ''' 171 | base.inet_tool('load').close() 172 | base.inet_tool('bind 6 127.0.0.0/24:0 x').close() 173 | sd, srv_port = base.bind_tcp(cloexec=False) 174 | p = base.inet_tool('register x', close_fds=False) 175 | self.assertIn("[+] x -> #0", p.stderr_line()) 176 | 177 | self.assertTcpConnSuccess('127.0.0.1', 1) 178 | self.assertTcpConnSuccess('127.0.0.2', 2) 179 | self.assertTcpConnRefused('127.0.1.1', 1) 180 | self.assertTcpConnRefused('127.0.2.2', 2) 181 | 182 | 183 | def test_basic_tcp_clear_subnet(self): 184 | ''' Verify if bottom of IP is masked on adding subnets ''' 185 | base.inet_tool('load').close() 186 | 187 | base.inet_tool('bind 6 255.255.255.255/24:0 x').close() 188 | svcs, bdgs = self.inet_tool_list() 189 | self.assertFalse(svcs) 190 | self.assertEqual(bdgs, ['6 255.255.255.0/24:0 -> x']) 191 | base.inet_tool('unbind 6 255.255.255.255/24:0').close() 192 | 193 | base.inet_tool('bind 6 255.255.255.255/1:0 x').close() 194 | svcs, bdgs = self.inet_tool_list() 195 | self.assertFalse(svcs) 196 | self.assertEqual(bdgs, ['6 128.0.0.0/1:0 -> x']) 197 | base.inet_tool('unbind 6 128.0.0.0/1:0').close() 198 | 199 | base.inet_tool('bind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/127:0 x').close() 200 | svcs, bdgs = self.inet_tool_list() 201 | self.assertFalse(svcs) 202 | self.assertEqual(bdgs, ['6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/127:0 -> x']) 203 | base.inet_tool('unbind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/127:0').close() 204 | 205 | base.inet_tool('bind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/126:0 x').close() 206 | svcs, bdgs = self.inet_tool_list() 207 | self.assertFalse(svcs) 208 | self.assertEqual(bdgs, ['6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc]/126:0 -> x']) 209 | base.inet_tool('unbind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc]/126:0').close() 210 | 211 | base.inet_tool('bind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/119:0 x').close() 212 | svcs, bdgs = self.inet_tool_list() 213 | self.assertFalse(svcs) 214 | self.assertEqual(bdgs, ['6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00]/119:0 -> x']) 215 | base.inet_tool('unbind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00]/119:0').close() 216 | 217 | base.inet_tool('bind 6 [ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/1:0 x').close() 218 | svcs, bdgs = self.inet_tool_list() 219 | self.assertFalse(svcs) 220 | self.assertEqual(bdgs, ['6 [8000::]/1:0 -> x']) 221 | base.inet_tool('unbind 6 [8000:]/1:0').close() 222 | 223 | 224 | def test_basic_tcp_register(self): 225 | ''' Verify if register and command work ''' 226 | base.inet_tool('load').close() 227 | base.inet_tool('bind 6 127.0.0.1:1234 x').close() 228 | sd, srv_port = base.bind_tcp(cloexec=False) 229 | 230 | p = base.inet_tool('register x -- %s' % base.HELLO_WORLD_SERVER, close_fds=False) 231 | self.assertIn("[+] x -> #0", p.stderr_line()) 232 | self.assertIn("running: ", p.stderr_line()) 233 | 234 | self.assertTcpHelloWorld(port=srv_port) 235 | self.assertTcpHelloWorld(port=1234) 236 | p.close(kill=True) 237 | 238 | 239 | def test_basic_tcp_echo_1_tuple_inet6(self): 240 | ''' Verify if basic binding actually work on ipv6 ''' 241 | base.inet_tool('load').close() 242 | base.inet_tool('bind 6 [::1]:0 x').close() 243 | sd, srv_port = base.bind_tcp(ip='::1',cloexec=False) 244 | self.assertTcpConnSuccess('::1', srv_port) 245 | self.assertTcpConnRefused('::1', 1) 246 | 247 | p = base.inet_tool('register x', close_fds=False) 248 | self.assertIn("[+] x -> #0", p.stderr_line()) 249 | svcs, bdgs = self.inet_tool_list() 250 | self.assertIn('x\t= #0 sk:', svcs[0]) 251 | self.assertEqual(len(bdgs), 1) 252 | 253 | self.assertTcpConnSuccess('::1', 1) 254 | self.assertTcpConnSuccess('::1', 2) 255 | 256 | # Socket shall be closed - SOCKARRAY shall not hold a reference. 257 | sd.close() 258 | 259 | self.assertTcpConnRefused('::1', 1) 260 | 261 | 262 | def test_basic_scm_register(self): 263 | ''' Verify if scm register works ''' 264 | base.inet_tool('load').close() 265 | scm_serve = base.inet_tool('scm_serve --unix=@test') 266 | self.assertIn("Waiting for SCM_RIGHTS", scm_serve.stdout_line()) 267 | 268 | sd, srv_port = base.bind_tcp(cloexec=False) 269 | p = base.inet_tool('scm_register --unix=@test x -- /bin/echo xxx', close_fds=False) 270 | self.assertIn("Registering service \"x\"", p.stderr_line()) 271 | self.assertIn("fd=", scm_serve.stdout_line()) 272 | 273 | svcs, bdgs = self.inet_tool_list() 274 | self.assertNotIn('nil', svcs[0]) 275 | self.assertIn('x\t= #0 sk:', svcs[0]) 276 | self.assertFalse(bdgs) 277 | -------------------------------------------------------------------------------- /src/inet-commands.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "../ebpf/inet-kern-shared.h" 19 | #include "inet.h" 20 | #include "tbpf.h" 21 | 22 | #define SEC(a) 23 | #include "../ebpf/inet-kern-shared.c" 24 | 25 | struct bpf_map_def *ebpf_maps[] = { 26 | &redir_map, 27 | &bind_map, 28 | &srvname_map, 29 | }; 30 | int ebpf_maps_sz = ARRAY_SIZE(ebpf_maps); 31 | 32 | char *ebpf_maps_names[] = { 33 | "redir_map", 34 | "bind_map", 35 | "srvname_map", 36 | }; 37 | 38 | const char *ebpf_link_name = "sk_lookup_link"; 39 | 40 | extern size_t bpf_insn_sk_lookup_cnt; 41 | extern struct bpf_insn bpf_insn_sk_lookup[]; 42 | extern struct tbpf_reloc bpf_reloc_sk_lookup[]; 43 | 44 | struct { 45 | char *name; 46 | struct bpf_insn *insn; 47 | size_t *insn_cnt; 48 | struct tbpf_reloc *reloc; 49 | } ebpf_program = { 50 | INET_PROGRAM_VERSION, 51 | bpf_insn_sk_lookup, 52 | &bpf_insn_sk_lookup_cnt, 53 | bpf_reloc_sk_lookup, 54 | }; 55 | 56 | void inet_load(struct state *state) 57 | { 58 | int map_pos; 59 | for (map_pos = 0; map_pos < ebpf_maps_sz; map_pos++) { 60 | struct bpf_map_def *def = ebpf_maps[map_pos]; 61 | char map_name[PATH_MAX]; 62 | snprintf(map_name, sizeof(map_name), "%s%s", 63 | state->sys_fs_obj_prefix, ebpf_maps_names[map_pos]); 64 | 65 | int map_fd = state->map_fds[map_pos]; 66 | if (map_fd == 0) { 67 | map_fd = bpf_create_map( 68 | def->type, def->key_size, def->value_size, 69 | def->max_entries, def->map_flags); 70 | if (map_fd < 0) { 71 | if (errno == EPERM) { 72 | fprintf(stderr, 73 | "[!] Are you root? Do you have " 74 | "enough memlock " 75 | "resources?\n"); 76 | fprintf(stderr, 77 | "[!] Try running \"ulimit -l " 78 | "unlimited\" before\n"); 79 | } 80 | PFATAL("bpf(BPF_MAP_CREATE, %d)", def->type); 81 | } 82 | 83 | int r = bpf_obj_pin(map_fd, map_name); 84 | if (r < 0) { 85 | PFATAL("BPF_OBJ_PIN(%s)", map_name); 86 | } 87 | state->map_fds[map_pos] = map_fd; 88 | fprintf(stderr, "[+] Created map %s\n", map_name); 89 | } else { 90 | fprintf(stderr, "[+] Reused map %s\n", map_name); 91 | } 92 | 93 | char *obj_name = ebpf_maps_names[map_pos]; 94 | 95 | tbpf_fill_symbol(ebpf_program.insn, ebpf_program.reloc, 96 | obj_name, map_fd); 97 | } 98 | 99 | /* prog load */ 100 | char log_buf[16 * 1024]; 101 | struct bpf_load_program_attr load_attr = { 102 | .prog_type = BPF_PROG_TYPE_SK_LOOKUP, 103 | .expected_attach_type = BPF_SK_LOOKUP, 104 | .insns = ebpf_program.insn, 105 | .insns_cnt = *ebpf_program.insn_cnt, 106 | .license = "Dual BSD/GPL", 107 | .name = ebpf_program.name, 108 | .kern_version = KERNEL_VERSION(5, 2, 0), 109 | }; 110 | 111 | int bpf_prog = 112 | bpf_load_program_xattr(&load_attr, log_buf, sizeof(log_buf)); 113 | 114 | if (bpf_prog < 0) { 115 | if (errno == EPERM) { 116 | fprintf(stderr, 117 | "[!] Are you root? Do you have enough memlock " 118 | "resources?\n"); 119 | fprintf(stderr, 120 | "[!] Try running \"ulimit -l unlimited\" " 121 | "before\n"); 122 | } 123 | PFATAL("Bpf Log:\n%s\n bpf(BPF_PROG_LOAD)", log_buf); 124 | } 125 | 126 | /* link create/update */ 127 | char link_name[PATH_MAX]; 128 | snprintf(link_name, sizeof(link_name), "%s%s", 129 | state->sys_fs_obj_prefix, ebpf_link_name); 130 | 131 | if (state->link_fd < 0) { 132 | /* link doesn't exist, create it */ 133 | int netns_fd = open("/proc/self/ns/net", O_RDONLY); 134 | if (netns_fd < 0) { 135 | PFATAL("open(/proc/self/ns/net)"); 136 | } 137 | 138 | DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts); 139 | int link_fd = bpf_link_create(bpf_prog, netns_fd, BPF_SK_LOOKUP, &opts); 140 | if (link_fd < 0) { 141 | PFATAL("bpf_link_create(BPF_SK_LOOKUP)"); 142 | } 143 | 144 | int r = bpf_obj_pin(link_fd, link_name); 145 | if (r) { 146 | PFATAL("bpf_obj_pin(%s)", link_name); 147 | } 148 | 149 | state->link_fd = link_fd; 150 | close(netns_fd); 151 | 152 | fprintf(stderr, "[+] Created link %s\n", link_name); 153 | } else { 154 | /* link already exists, update it */ 155 | DECLARE_LIBBPF_OPTS(bpf_link_update_opts, opts); 156 | int r = bpf_link_update(state->link_fd, bpf_prog, &opts); 157 | if (r) { 158 | PFATAL("bpf_link_update"); 159 | } 160 | 161 | fprintf(stderr, "[+] Updated link %s\n", link_name); 162 | } 163 | printf("SK_LOOKUP program loaded\n"); 164 | } 165 | 166 | void inet_open_verify_maps(struct state *state, int all_needed) 167 | { 168 | int map_pos; 169 | for (map_pos = 0; map_pos < ebpf_maps_sz; map_pos++) { 170 | struct bpf_map_def *def = ebpf_maps[map_pos]; 171 | char map_name[PATH_MAX]; 172 | snprintf(map_name, sizeof(map_name), "%s%s", 173 | state->sys_fs_obj_prefix, ebpf_maps_names[map_pos]); 174 | 175 | /* 1. try to reuse already opened maps */ 176 | int map_fd = state->map_fds[map_pos]; 177 | 178 | if (map_fd == 0) { 179 | /* 2. otherwise try to open the map */ 180 | map_fd = bpf_obj_get(map_name); 181 | 182 | /* 3. any error other than ENOTFOUND is fatal */ 183 | if (map_fd < 0 && errno != ENOENT) { 184 | PFATAL("bpf_obj_get(%s)", map_name); 185 | } 186 | 187 | /* 4. Got ENOTFOUND? Two options. Either 188 | * ignore it (we will create it during program 189 | * "load" anyway). */ 190 | if (map_fd < 0 && all_needed == 0) { 191 | continue; 192 | } 193 | /* 5. Or fatal. We need all maps for other actions. */ 194 | if (map_fd < 0 && all_needed == 1) { 195 | PFATAL("Failed to open map %s", map_name); 196 | } 197 | } 198 | 199 | { 200 | /* 6. Verify map parameters */ 201 | struct bpf_map_info info = {}; 202 | uint32_t info_sz = sizeof(struct bpf_map_info); 203 | int r = bpf_obj_get_info_by_fd(map_fd, &info, &info_sz); 204 | if (r) { 205 | PFATAL("bpf_obj_get_info_by_fd"); 206 | } 207 | if (info.type != def->type || 208 | info.key_size != def->key_size || 209 | info.value_size != def->value_size || 210 | info.max_entries != def->max_entries) { 211 | fprintf(stderr, 212 | "[!] Map %s parameters don't match. " 213 | "Run " 214 | "\"inet_tool unload\" first\n", 215 | map_name); 216 | exit(-2); 217 | } 218 | } 219 | state->map_fds[map_pos] = map_fd; 220 | } 221 | } 222 | 223 | void inet_open_verify_link(struct state *state) 224 | { 225 | char link_name[PATH_MAX]; 226 | snprintf(link_name, sizeof(link_name), "%s%s", 227 | state->sys_fs_obj_prefix, ebpf_link_name); 228 | 229 | /* 1. try to open pinned link */ 230 | int link_fd = bpf_obj_get(link_name); 231 | 232 | /* 2. ignore if link doesn't exist, will create it on "load", 233 | * fail otherwise */ 234 | if (link_fd < 0) { 235 | if (errno == ENOENT) { 236 | link_fd = -1; 237 | goto out; 238 | } 239 | PFATAL("bpf_obj_get(%s)", link_name); 240 | } 241 | 242 | struct stat st; 243 | int r = stat("/proc/self/ns/net", &st); 244 | if (r < 0) { 245 | PFATAL("stat(/proc/self/ns/net)"); 246 | } 247 | 248 | /* 3. verify existing link info */ 249 | struct bpf_link_info info = {}; 250 | uint32_t info_sz = sizeof(struct bpf_link_info); 251 | r = bpf_obj_get_info_by_fd(link_fd, &info, &info_sz); 252 | 253 | if (info.type != BPF_LINK_TYPE_NETNS || 254 | info.netns.attach_type != BPF_SK_LOOKUP || 255 | info.netns.netns_ino != st.st_ino || 256 | !info.id || !info.prog_id) { 257 | fprintf(stderr, 258 | "[!] Link %s info is not as expected." 259 | "Remove the link file first\n", 260 | link_name); 261 | exit(-1); 262 | } 263 | out: 264 | state->link_fd = link_fd; 265 | } 266 | 267 | struct prog_info { 268 | uint32_t map_ids[128]; 269 | struct bpf_prog_info bpi; 270 | }; 271 | 272 | static int get_prog_info(struct prog_info *prog_info) 273 | { 274 | int ns_fd = open("/proc/self/ns/net", O_RDONLY); 275 | if (ns_fd < 0) { 276 | PFATAL("open(/proc/self/ns/net)"); 277 | } 278 | 279 | uint32_t attach_flags = 0; 280 | uint32_t prog_ids[1] = {0}; 281 | uint32_t prog_cnt = 1; 282 | 283 | int r = bpf_prog_query(ns_fd, BPF_SK_LOOKUP, 0, &attach_flags, 284 | prog_ids, &prog_cnt); 285 | if (r) { 286 | PFATAL("bpf(PROG_QUERY, BPF_SK_LOOKUP)"); 287 | } 288 | close(ns_fd); 289 | 290 | int i; 291 | for (i = 0; i < (int)prog_cnt; i++) { 292 | int bpf_fd = bpf_prog_get_fd_by_id(prog_ids[i]); 293 | if (bpf_fd < 0) { 294 | PFATAL("bpf_prog_get_fd_by_id()"); 295 | } 296 | 297 | prog_info->bpi = (struct bpf_prog_info){ 298 | .nr_map_ids = 128, 299 | .map_ids = (uint64_t)&prog_info->map_ids, 300 | }; 301 | uint32_t bpi_sz = sizeof(struct bpf_prog_info); 302 | r = bpf_obj_get_info_by_fd(bpf_fd, &prog_info->bpi, &bpi_sz); 303 | if (r) { 304 | PFATAL("bpf_obj_get_info_by_fd()"); 305 | } 306 | close(bpf_fd); 307 | } 308 | return i; 309 | } 310 | 311 | int inet_prog_info(struct state *state) 312 | { 313 | struct prog_info prog_info; 314 | int i = get_prog_info(&prog_info); 315 | 316 | if (i == 1) { 317 | printf("[+] SK_LOOKUP program present\n"); 318 | int recognized = 319 | strcmp(prog_info.bpi.name, ebpf_program.name) == 0; 320 | printf("[+] name: %s (%s)\n", prog_info.bpi.name, 321 | recognized ? "recognized" : "unknown"); 322 | uint8_t *tag = prog_info.bpi.tag; 323 | printf("[+] tag: %02x%02x%02x%02x%02x%02x%02x%02x\n", tag[0], 324 | tag[1], tag[2], tag[3], tag[4], tag[5], tag[6], tag[7]); 325 | printf("[+] prog maps: "); 326 | 327 | int j; 328 | for (j = 0; j < (int)prog_info.bpi.nr_map_ids; j++) { 329 | printf("%s%u", j > 0 ? "," : "", prog_info.map_ids[j]); 330 | } 331 | printf("\n"); 332 | printf("[+] /sys maps: "); 333 | for (j = 0; j < ebpf_maps_sz; j++) { 334 | int map_fd = state->map_fds[j]; 335 | struct bpf_map_info info = {}; 336 | uint32_t info_sz = sizeof(struct bpf_map_info); 337 | int r = bpf_obj_get_info_by_fd(map_fd, &info, &info_sz); 338 | if (r) { 339 | PFATAL("bpf_obj_get_info_by_fd"); 340 | } 341 | printf("%s%u", j > 0 ? "," : "", info.id); 342 | } 343 | printf("\n"); 344 | printf("[+] run_cnt=%llu run_time_ns=%llu\n", 345 | prog_info.bpi.run_cnt, prog_info.bpi.run_time_ns); 346 | } 347 | if (i == 0) { 348 | printf("SK_LOOKUP program absent\n"); 349 | return 1; 350 | } 351 | return 0; 352 | } 353 | 354 | int inet_prog_verify() 355 | { 356 | struct prog_info prog_info; 357 | int i = get_prog_info(&prog_info); 358 | if (i == 1) { 359 | int recognized = 360 | strcmp(prog_info.bpi.name, ebpf_program.name) == 0; 361 | return recognized; 362 | } 363 | return -1; 364 | } 365 | 366 | int inet_unload(struct state *state) 367 | { 368 | int return_code = 0; 369 | 370 | char link_name[PATH_MAX]; 371 | snprintf(link_name, sizeof(link_name), "%s%s", 372 | state->sys_fs_obj_prefix, ebpf_link_name); 373 | 374 | int r = unlink(link_name); 375 | if (r == 0) { 376 | printf("[+] Unpinned SK_LOOKUP link %s\n", link_name); 377 | } else { 378 | printf("[-] Failed to unpin SK_LOOKUP link %s: %s\n", link_name, 379 | strerror(errno)); 380 | return_code = errno; 381 | } 382 | 383 | int map_pos; 384 | for (map_pos = 0; map_pos < ebpf_maps_sz; map_pos++) { 385 | char map_name[PATH_MAX]; 386 | snprintf(map_name, sizeof(map_name), "%s%s", 387 | state->sys_fs_obj_prefix, ebpf_maps_names[map_pos]); 388 | 389 | r = unlink(map_name); 390 | if (r == 0) { 391 | printf("[+] Unpinned map %s\n", map_name); 392 | } else { 393 | printf("[-] Failed to unpin map %s: %s\n", map_name, 394 | strerror(errno)); 395 | } 396 | } 397 | return return_code; 398 | } 399 | 400 | void inet_list(struct state *state) 401 | { 402 | { 403 | printf("List of services:\n"); 404 | struct srvname k = {}; 405 | uint32_t v; 406 | while (1) { 407 | int r = bpf_map_get_next_key( 408 | state->map_fds[SRVNAME_MAP], &k, &k); 409 | if (r) { 410 | if (errno == ENOENT) { 411 | break; 412 | } 413 | PFATAL("get_next_key"); 414 | } 415 | r = bpf_map_lookup_elem(state->map_fds[SRVNAME_MAP], &k, 416 | &v); 417 | if (r) { 418 | PFATAL("map_lookup_elem"); 419 | } 420 | 421 | uint32_t redir_k = v; 422 | char sk[32] = "sk:(nil)"; 423 | uint64_t redir_v; 424 | 425 | r = bpf_map_lookup_elem(state->map_fds[REDIR_MAP], 426 | &redir_k, &redir_v); 427 | if (r) { 428 | if (errno != ENOENT) { 429 | PFATAL("map_lookup_elem"); 430 | } 431 | } else { 432 | snprintf(sk, sizeof(sk), "sk:%lx", redir_v); 433 | } 434 | printf("\t%.*s\t= #%d %s\n", 32, k.name, v, sk); 435 | } 436 | } 437 | 438 | { 439 | printf("List of bindings:\n"); 440 | struct addr k = {}; 441 | struct srvname v; 442 | while (1) { 443 | int r = bpf_map_get_next_key(state->map_fds[BIND_MAP], 444 | &k, &k); 445 | if (r) { 446 | if (errno == ENOENT) { 447 | break; 448 | } 449 | PFATAL("get_next_key"); 450 | } 451 | r = bpf_map_lookup_elem(state->map_fds[BIND_MAP], &k, 452 | &v); 453 | if (r) { 454 | PFATAL("map_lookup_elem"); 455 | } 456 | printf("\t%s -> %.*s\n", sprint_addr(&k), 32, v.name); 457 | } 458 | } 459 | } 460 | 461 | void inet_register(struct state *state, char **fdnames, char **srvnames) 462 | { 463 | int fd = -1; 464 | int skip_fds[] = {0, 1, 2, -1}; 465 | while (1) { 466 | int domain, sock_type, protocol; 467 | fd = find_inherited_fd(fd, skip_fds, &domain, &sock_type, 468 | &protocol); 469 | if (fd < 0) { 470 | break; 471 | } 472 | 473 | if (domain == AF_INET || domain == AF_INET6) { 474 | char *name = NULL; 475 | if (fdnames && fdnames[0]) { 476 | name = fdnames[0]; 477 | fdnames++; 478 | } else if (srvnames && srvnames[0]) { 479 | name = srvnames[0]; 480 | srvnames++; 481 | } else { 482 | name = "@"; 483 | } 484 | 485 | int r = inet_register_socket(state, fd, name); 486 | if (r == -1) { 487 | fprintf(stderr, "[!] redir_map full!\n"); 488 | } 489 | 490 | uint64_t sk = 0; 491 | socklen_t l = sizeof(sk); 492 | getsockopt(fd, SOL_SOCKET, SO_COOKIE, &sk, &l); 493 | 494 | fprintf(stderr, "[+] %.*s -> #%d (sk:%lx)\n", 495 | (int)sizeof(struct srvname), name, r, sk); 496 | } 497 | } 498 | 499 | /* Sockets absent but labels are still present. */ 500 | 501 | while (srvnames && srvnames[0]) { 502 | char *name = srvnames[0]; 503 | srvnames++; 504 | 505 | int r = inet_register_socket(state, -1, name); 506 | fprintf(stderr, "[+] %.*s -> #%d (sk:nil)\n", 507 | (int)sizeof(struct srvname), name, r); 508 | } 509 | } 510 | 511 | void inet_unregister(struct state *state, char *service) 512 | { 513 | struct srvname srvname; 514 | strncpy(srvname.name, service, sizeof(srvname.name)); 515 | 516 | int r = bpf_map_delete_elem(state->map_fds[SRVNAME_MAP], &srvname); 517 | if (r) { 518 | PFATAL("map_delete(srvname_map)"); 519 | } 520 | fprintf(stderr, "[-] %.*s -> \n", (int)sizeof(srvname.name), 521 | srvname.name); 522 | } 523 | 524 | int inet_register_socket(struct state *state, int fd, char *fdname) 525 | { 526 | struct srvname srvname = {}; 527 | strncpy(srvname.name, fdname, sizeof(srvname.name)); 528 | 529 | /* lookup already present service */ 530 | uint32_t redir_index = UINT_MAX; 531 | { 532 | uint32_t v; 533 | int r = bpf_map_lookup_elem(state->map_fds[SRVNAME_MAP], 534 | &srvname, &v); 535 | if (r) { 536 | if (errno != ENOENT) { 537 | PFATAL("map_lookup_elem(srvname_map)"); 538 | } 539 | } else { 540 | redir_index = v; 541 | } 542 | } 543 | 544 | if (redir_index == UINT_MAX) { 545 | char redir_index_used[32] = {}; 546 | struct srvname k = {}; 547 | uint32_t v; 548 | while (1) { 549 | int r = bpf_map_get_next_key( 550 | state->map_fds[SRVNAME_MAP], &k, &k); 551 | if (r) { 552 | if (errno == ENOENT) { 553 | break; 554 | } 555 | PFATAL("get_next_key"); 556 | } 557 | r = bpf_map_lookup_elem(state->map_fds[SRVNAME_MAP], &k, 558 | &v); 559 | if (r) { 560 | PFATAL("map_lookup_elem"); 561 | } 562 | if (v > 32) { 563 | PFATAL(""); 564 | } 565 | redir_index_used[v] = 1; 566 | } 567 | int i; 568 | for (i = 0; i < 32; i++) { 569 | if (redir_index_used[i] == 0) { 570 | redir_index = i; 571 | break; 572 | } 573 | } 574 | } 575 | 576 | if (redir_index == UINT_MAX) { 577 | return -1; 578 | } 579 | 580 | if (fd >= 0) { 581 | uint64_t val = fd; 582 | int r = bpf_map_update_elem(state->map_fds[REDIR_MAP], 583 | &redir_index, &val, 0); 584 | if (r) { 585 | if (errno != EEXIST) { 586 | PFATAL("map_update"); 587 | } 588 | } 589 | } 590 | 591 | /* Insert srvname int */ 592 | { 593 | struct srvname srvname = {}; 594 | strncpy(srvname.name, fdname, sizeof(srvname.name)); 595 | int r = bpf_map_update_elem(state->map_fds[SRVNAME_MAP], 596 | &srvname, &redir_index, 0); 597 | if (r) { 598 | PFATAL("map_update(srvname_map)"); 599 | } 600 | } 601 | return redir_index; 602 | } 603 | 604 | struct addr addr_from_inetaddr(struct inet_addr *ia) 605 | { 606 | int prefixlen = (sizeof(struct addr) - 4) * 8; 607 | int bottombits = get_bottombits(&ia->ss, ia->subnet); 608 | 609 | struct addr ad = { 610 | .prefixlen = prefixlen - bottombits, 611 | .protocol = ia->protocol, 612 | }; 613 | 614 | { 615 | struct sockaddr_in *sin = (struct sockaddr_in *)&ia->ss; 616 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ia->ss; 617 | if (ia->ss.ss_family == AF_INET) { 618 | ad.addr.ip_as_w[2] = htonl(0x0000ffff); 619 | memcpy(&ad.addr.ip_as_w[3], &sin->sin_addr, 4); 620 | ad.port = htons(sin->sin_port); 621 | } 622 | if (ia->ss.ss_family == AF_INET6) { 623 | memcpy(&ad.addr, &sin6->sin6_addr, 16); 624 | ad.port = htons(sin6->sin6_port); 625 | } 626 | } 627 | return ad; 628 | } 629 | 630 | void inet_bind(struct state *state, struct inet_addr *iaddr, char *service) 631 | { 632 | struct addr localaddr = addr_from_inetaddr(iaddr); 633 | 634 | struct srvname srvname; 635 | strncpy(srvname.name, service, sizeof(srvname.name)); 636 | 637 | int r = bpf_map_update_elem(state->map_fds[BIND_MAP], &localaddr, 638 | &srvname, 0); 639 | if (r) { 640 | PFATAL("map_update(bind_map)"); 641 | } 642 | printf("[+] %s -> %.*s\n", sprint_addr(&localaddr), 643 | (int)sizeof(srvname.name), srvname.name); 644 | } 645 | 646 | void inet_unbind(struct state *state, struct inet_addr *iaddr) 647 | { 648 | struct addr localaddr = addr_from_inetaddr(iaddr); 649 | 650 | int r = bpf_map_delete_elem(state->map_fds[BIND_MAP], &localaddr); 651 | if (r) { 652 | PFATAL("map_update(bind_map)"); 653 | } 654 | printf("[-] %s\n", sprint_addr(&localaddr)); 655 | } 656 | --------------------------------------------------------------------------------