Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/net: Add test for timing a bind request to a port with a populated bhash entry

This test populates the bhash table for a given port with
MAX_THREADS * MAX_CONNECTIONS sockets, and then times how long
a bind request on the port takes.

When populating the bhash table, we create the sockets and then bind
the sockets to the same address and port (SO_REUSEADDR and SO_REUSEPORT
are set). When timing how long a bind on the port takes, we bind on a
different address without SO_REUSEPORT set. We do not set SO_REUSEPORT
because we are interested in the case where the bind request does not
go through the tb->fastreuseport path, which is fragile (eg
tb->fastreuseport path does not work if binding with a different uid).

To run the script:
Usage: ./bind_bhash.sh [-6 | -4] [-p port] [-a address]
6: use ipv6
4: use ipv4
port: Port number
address: ip address

Without any arguments, ./bind_bhash.sh defaults to ipv6 using ip address
"2001:0db8:0:f101::1" on port 443.

On my local machine, I see:
ipv4:
before - 0.002317 seconds
with bhash2 - 0.000020 seconds

ipv6:
before - 0.002431 seconds
with bhash2 - 0.000021 seconds

Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Joanne Koong and committed by
Jakub Kicinski
c35ecb95 28044fc1

+214
+1
tools/testing/selftests/net/.gitignore
··· 40 40 cmsg_sender 41 41 unix_connect 42 42 tap 43 + bind_bhash
+3
tools/testing/selftests/net/Makefile
··· 43 43 TEST_PROGS += arp_ndisc_untracked_subnets.sh 44 44 TEST_PROGS += stress_reuseport_listen.sh 45 45 TEST_PROGS := l2_tos_ttl_inherit.sh 46 + TEST_PROGS += bind_bhash.sh 46 47 TEST_PROGS_EXTENDED := in_netns.sh setup_loopback.sh setup_veth.sh 47 48 TEST_PROGS_EXTENDED += toeplitz_client.sh toeplitz.sh 48 49 TEST_GEN_FILES = socket nettest ··· 65 64 TEST_GEN_FILES += stress_reuseport_listen 66 65 TEST_PROGS += test_vxlan_vnifiltering.sh 67 66 TEST_GEN_FILES += io_uring_zerocopy_tx 67 + TEST_GEN_FILES += bind_bhash 68 68 69 69 TEST_FILES := settings 70 70 ··· 76 74 $(OUTPUT)/reuseport_bpf_numa: LDLIBS += -lnuma 77 75 $(OUTPUT)/tcp_mmap: LDLIBS += -lpthread 78 76 $(OUTPUT)/tcp_inq: LDLIBS += -lpthread 77 + $(OUTPUT)/bind_bhash: LDLIBS += -lpthread
+144
tools/testing/selftests/net/bind_bhash.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * This times how long it takes to bind to a port when the port already 4 + * has multiple sockets in its bhash table. 5 + * 6 + * In the setup(), we populate the port's bhash table with 7 + * MAX_THREADS * MAX_CONNECTIONS number of entries. 8 + */ 9 + 10 + #include <unistd.h> 11 + #include <stdio.h> 12 + #include <netdb.h> 13 + #include <pthread.h> 14 + #include <string.h> 15 + #include <stdbool.h> 16 + 17 + #define MAX_THREADS 600 18 + #define MAX_CONNECTIONS 40 19 + 20 + static const char *setup_addr_v6 = "::1"; 21 + static const char *setup_addr_v4 = "127.0.0.1"; 22 + static const char *setup_addr; 23 + static const char *bind_addr; 24 + static const char *port; 25 + bool use_v6; 26 + int ret; 27 + 28 + static int fd_array[MAX_THREADS][MAX_CONNECTIONS]; 29 + 30 + static int bind_socket(int opt, const char *addr) 31 + { 32 + struct addrinfo *res, hint = {}; 33 + int sock_fd, reuse = 1, err; 34 + int domain = use_v6 ? AF_INET6 : AF_INET; 35 + 36 + sock_fd = socket(domain, SOCK_STREAM, 0); 37 + if (sock_fd < 0) { 38 + perror("socket fd err"); 39 + return sock_fd; 40 + } 41 + 42 + hint.ai_family = domain; 43 + hint.ai_socktype = SOCK_STREAM; 44 + 45 + err = getaddrinfo(addr, port, &hint, &res); 46 + if (err) { 47 + perror("getaddrinfo failed"); 48 + goto cleanup; 49 + } 50 + 51 + if (opt) { 52 + err = setsockopt(sock_fd, SOL_SOCKET, opt, &reuse, sizeof(reuse)); 53 + if (err) { 54 + perror("setsockopt failed"); 55 + goto cleanup; 56 + } 57 + } 58 + 59 + err = bind(sock_fd, res->ai_addr, res->ai_addrlen); 60 + if (err) { 61 + perror("failed to bind to port"); 62 + goto cleanup; 63 + } 64 + 65 + return sock_fd; 66 + 67 + cleanup: 68 + close(sock_fd); 69 + return err; 70 + } 71 + 72 + static void *setup(void *arg) 73 + { 74 + int sock_fd, i; 75 + int *array = (int *)arg; 76 + 77 + for (i = 0; i < MAX_CONNECTIONS; i++) { 78 + sock_fd = bind_socket(SO_REUSEADDR | SO_REUSEPORT, setup_addr); 79 + if (sock_fd < 0) { 80 + ret = sock_fd; 81 + pthread_exit(&ret); 82 + } 83 + array[i] = sock_fd; 84 + } 85 + 86 + return NULL; 87 + } 88 + 89 + int main(int argc, const char *argv[]) 90 + { 91 + int listener_fd, sock_fd, i, j; 92 + pthread_t tid[MAX_THREADS]; 93 + clock_t begin, end; 94 + 95 + if (argc != 4) { 96 + printf("Usage: listener <port> <ipv6 | ipv4> <bind-addr>\n"); 97 + return -1; 98 + } 99 + 100 + port = argv[1]; 101 + use_v6 = strcmp(argv[2], "ipv6") == 0; 102 + bind_addr = argv[3]; 103 + 104 + setup_addr = use_v6 ? setup_addr_v6 : setup_addr_v4; 105 + 106 + listener_fd = bind_socket(SO_REUSEADDR | SO_REUSEPORT, setup_addr); 107 + if (listen(listener_fd, 100) < 0) { 108 + perror("listen failed"); 109 + return -1; 110 + } 111 + 112 + /* Set up threads to populate the bhash table entry for the port */ 113 + for (i = 0; i < MAX_THREADS; i++) 114 + pthread_create(&tid[i], NULL, setup, fd_array[i]); 115 + 116 + for (i = 0; i < MAX_THREADS; i++) 117 + pthread_join(tid[i], NULL); 118 + 119 + if (ret) 120 + goto done; 121 + 122 + begin = clock(); 123 + 124 + /* Bind to the same port on a different address */ 125 + sock_fd = bind_socket(0, bind_addr); 126 + if (sock_fd < 0) 127 + goto done; 128 + 129 + end = clock(); 130 + 131 + printf("time spent = %f\n", (double)(end - begin) / CLOCKS_PER_SEC); 132 + 133 + /* clean up */ 134 + close(sock_fd); 135 + 136 + done: 137 + close(listener_fd); 138 + for (i = 0; i < MAX_THREADS; i++) { 139 + for (j = 0; i < MAX_THREADS; i++) 140 + close(fd_array[i][j]); 141 + } 142 + 143 + return 0; 144 + }
+66
tools/testing/selftests/net/bind_bhash.sh
··· 1 + #!/bin/bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + NR_FILES=32768 5 + SAVED_NR_FILES=$(ulimit -n) 6 + 7 + # default values 8 + port=443 9 + addr_v6="2001:0db8:0:f101::1" 10 + addr_v4="10.8.8.8" 11 + use_v6=true 12 + addr="" 13 + 14 + usage() { 15 + echo "Usage: $0 [-6 | -4] [-p port] [-a address]" 16 + echo -e "\t6: use ipv6" 17 + echo -e "\t4: use ipv4" 18 + echo -e "\tport: Port number" 19 + echo -e "\taddress: ip address" 20 + } 21 + 22 + while getopts "ha:p:64" opt; do 23 + case ${opt} in 24 + h) 25 + usage $0 26 + exit 0 27 + ;; 28 + a) addr=$OPTARG;; 29 + p) 30 + port=$OPTARG;; 31 + 6) 32 + use_v6=true;; 33 + 4) 34 + use_v6=false;; 35 + esac 36 + done 37 + 38 + setup() { 39 + if [[ "$use_v6" == true ]]; then 40 + ip addr add $addr_v6 nodad dev eth0 41 + else 42 + ip addr add $addr_v4 dev lo 43 + fi 44 + ulimit -n $NR_FILES 45 + } 46 + 47 + cleanup() { 48 + if [[ "$use_v6" == true ]]; then 49 + ip addr del $addr_v6 dev eth0 50 + else 51 + ip addr del $addr_v4/32 dev lo 52 + fi 53 + ulimit -n $SAVED_NR_FILES 54 + } 55 + 56 + if [[ "$addr" != "" ]]; then 57 + addr_v4=$addr; 58 + addr_v6=$addr; 59 + fi 60 + setup 61 + if [[ "$use_v6" == true ]] ; then 62 + ./bind_bhash $port "ipv6" $addr_v6 63 + else 64 + ./bind_bhash $port "ipv4" $addr_v4 65 + fi 66 + cleanup