├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── bin └── .gitkeep ├── bpftrace ├── Makefile ├── biolatency.bt ├── runqlat.bt └── tcpdrop.bt ├── docker ├── Dockerfile ├── Dockerfile.alpine ├── Dockerfile.mvfst ├── docker-compose-load-clients.yaml ├── docker-compose-parallel-load-clients.yaml ├── docker-compose.yaml ├── entrypoint.sh └── load-test-entrypoint.sh ├── echo_server.c ├── experiments ├── Makefile ├── README.md ├── aggregate.py ├── compute.py ├── cpu.py ├── extract.py ├── graphs.plt ├── in │ └── .gitkeep ├── mem.py ├── out │ └── .gitkeep └── stats │ └── .gitkeep ├── http_server.c ├── logs └── .gitkeep ├── src ├── equic.c ├── equic_kern.c ├── equic_user.c └── equic_user.h └── ssl ├── Readme.md ├── cert.pem ├── generate-key.sh └── private.key /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # All files resulted of compilation 55 | bin/* 56 | # except for .gitkeep file 57 | !bin/.gitkeep 58 | 59 | # temporary commands reminder file 60 | .command-reminder 61 | 62 | # Logs files except gitkeep file 63 | logs/* 64 | !logs/.gitkeep 65 | 66 | # Traces files except gitkeep file 67 | logs/traces/* 68 | !logs/traces/.gitkeep 69 | 70 | # Experiments should keep directories but not files 71 | experiments/in/*.dat 72 | experiments/stats/*.csv 73 | experiments/out/*.png 74 | !experiments/in/.gitkeep 75 | !experiments/stats/.gitkeep 76 | !experiments/out/.gitkeep 77 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libbpf"] 2 | path = libbpf 3 | url = https://github.com/libbpf/libbpf.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 2 | LIST(APPEND LIBS ${EVENT_LIB}) 3 | 4 | IF(MSVC) 5 | FIND_LIBRARY(PCRE_LIB pcre) 6 | IF(PCRE_LIB) 7 | MESSAGE(STATUS "Found pcre: ${PCRE_LIB}") 8 | LIST(APPEND LIBS ${PCRE_LIB}) 9 | ELSE() 10 | MESSAGE(STATUS "pcre not found: http_server won't work") 11 | ENDIF() 12 | FIND_LIBRARY(PCREPOSIX_LIB pcreposix) 13 | IF(PCREPOSIX_LIB) 14 | MESSAGE(STATUS "Found pcreposix: ${PCREPOSIX_LIB}") 15 | LIST(APPEND LIBS ${PCREPOSIX_LIB}) 16 | ELSE() 17 | MESSAGE(STATUS "pcreposix not found: http_server won't work") 18 | ENDIF() 19 | LIST(APPEND LIBS ws2_32) 20 | LIST(APPEND LIBS iphlpapi) 21 | LIST(APPEND LIBS ${GETOPT_LIB}) 22 | ENDIF() 23 | 24 | # eQUIC 25 | add_executable(http_server /src/equic/bin/equic_user.o http_server.c prog.c test_common.c test_cert.c) 26 | 27 | IF(NOT MSVC) # TODO: port MD5 server and client to Windows 28 | add_executable(md5_server md5_server.c prog.c test_common.c test_cert.c) 29 | add_executable(md5_client md5_client.c prog.c test_common.c test_cert.c) 30 | ENDIF() 31 | 32 | # eQUIC 33 | add_executable(echo_server /src/equic/bin/equic_user.o echo_server.c prog.c test_common.c test_cert.c) 34 | add_executable(echo_client echo_client.c prog.c test_common.c test_cert.c) 35 | add_executable(duck_server duck_server.c prog.c test_common.c test_cert.c) 36 | add_executable(duck_client duck_client.c prog.c test_common.c test_cert.c) 37 | add_executable(perf_client perf_client.c prog.c test_common.c test_cert.c) 38 | add_executable(perf_server perf_server.c prog.c test_common.c test_cert.c) 39 | 40 | 41 | IF (NOT MSVC) 42 | 43 | add_executable(http_client 44 | http_client.c 45 | prog.c 46 | test_common.c 47 | test_cert.c 48 | ) 49 | 50 | #MSVC 51 | ELSE() 52 | 53 | add_executable(http_client 54 | http_client.c 55 | prog.c 56 | test_common.c 57 | test_cert.c 58 | ) 59 | 60 | ENDIF() 61 | 62 | TARGET_LINK_LIBRARIES(http_client ${LIBS}) 63 | # eQUIC 64 | TARGET_LINK_LIBRARIES(http_server ${LIBS} bpf elf /usr/lib/x86_64-linux-gnu/libz.a) 65 | IF(NOT MSVC) 66 | TARGET_LINK_LIBRARIES(md5_server ${LIBS}) 67 | TARGET_LINK_LIBRARIES(md5_client ${LIBS}) 68 | ENDIF() 69 | # eQUIC 70 | TARGET_LINK_LIBRARIES(echo_server ${LIBS} bpf elf /usr/lib/x86_64-linux-gnu/libz.a) 71 | TARGET_LINK_LIBRARIES(echo_client ${LIBS}) 72 | TARGET_LINK_LIBRARIES(duck_server ${LIBS}) 73 | TARGET_LINK_LIBRARIES(duck_client ${LIBS}) 74 | TARGET_LINK_LIBRARIES(perf_client ${LIBS}) 75 | TARGET_LINK_LIBRARIES(perf_server ${LIBS}) 76 | 77 | 78 | INCLUDE(CheckFunctionExists) 79 | CHECK_FUNCTION_EXISTS(sendmmsg HAVE_SENDMMSG) 80 | CHECK_FUNCTION_EXISTS(recvmmsg HAVE_RECVMMSG) 81 | CHECK_FUNCTION_EXISTS(open_memstream HAVE_OPEN_MEMSTREAM) 82 | 83 | 84 | INCLUDE(CheckSymbolExists) 85 | 86 | CHECK_SYMBOL_EXISTS( 87 | IP_MTU_DISCOVER 88 | "netinet/in.h" 89 | HAVE_IP_MTU_DISCOVER 90 | ) 91 | 92 | CHECK_SYMBOL_EXISTS( 93 | IPV6_MTU_DISCOVER 94 | "netinet/in.h" 95 | HAVE_IPV6_MTU_DISCOVER 96 | ) 97 | 98 | CHECK_SYMBOL_EXISTS( 99 | IP_DONTFRAG 100 | "netinet/in.h" 101 | HAVE_IP_DONTFRAG 102 | ) 103 | 104 | CHECK_SYMBOL_EXISTS( 105 | preadv 106 | "sys/uio.h" 107 | HAVE_PREADV 108 | ) 109 | 110 | INCLUDE(CheckIncludeFiles) 111 | 112 | IF (MSVC AND PCRE_LIB) 113 | FIND_PATH(EVENT_INCLUDE_DIR NAMES pcreposix.h) 114 | IF (EVENT_INCLUDE_DIR) 115 | MESSAGE(STATUS "found pcreposix.h") 116 | SET(HAVE_REGEX 1) 117 | ELSE() 118 | MESSAGE(FATAL_ERROR "event2/event.h was not found") 119 | ENDIF() 120 | ELSE() 121 | CHECK_INCLUDE_FILES(regex.h HAVE_REGEX) 122 | ENDIF() 123 | 124 | CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/test_config.h) 125 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Variables block 3 | # 4 | 5 | DOCKERFILES_DIR := docker 6 | 7 | DOCKER_TAG ?= local 8 | 9 | DOCKER_CMD := $(shell which docker) 10 | 11 | DOCKER_COMPOSE_CMD := $(shell which docker-compose) 12 | 13 | 14 | # Compiler optimization options 15 | OPTMIZATIONS := -O2 16 | 17 | # C compilation flags 18 | CFLAGS := -DDEBUG -D__BPF_TRACING__ -D__KERNEL__ 19 | CFLAGS += $(OPTMIZATIONS) 20 | 21 | # Headers include path for Kernel module inside the container 22 | INCLUDE_PATH := /usr/include/x86_64-linux-gnu/ 23 | 24 | # C lang compiler 25 | CC := $(shell which clang) 26 | 27 | # Source code directory 28 | SRC := src 29 | 30 | # Binary directory 31 | BIN := bin 32 | 33 | # Logs directory 34 | LOGS := logs 35 | 36 | # Log suffixes used to name files 37 | LOG_SUFFIX ?= baseline 38 | KERNEL_LOG_SUFFIX ?= kernel 39 | 40 | # Kernel trace file which eBPF prints debug messages 41 | TRACE_PIPE := /sys/kernel/debug/tracing/trace_pipe 42 | 43 | # Light Speed QUIC library path 44 | LSQUIC_PATH := /src/lsquic 45 | 46 | # Network interface to attach eBPF program 47 | IFACE ?= eth0 48 | 49 | # Current timestamp 50 | NOW := $(shell date +%s) 51 | 52 | # Load test request size 53 | REQ_SIZE ?= 64k 54 | 55 | # 56 | # Checks if trace file exist. If not, mount it 57 | # 58 | define check_debugfs 59 | 60 | @stat $(TRACE_PIPE) > /dev/null \ 61 | && echo "Trace found" \ 62 | || (echo "Mounting debugfs.." \ 63 | && mount -t debugfs none /sys/kernel/debug) 64 | endef 65 | 66 | 67 | # 68 | # Target rules block 69 | # 70 | .DEFAULT_GOAL: help 71 | 72 | 73 | .PHONY: help 74 | help: greetings 75 | @echo "eQUIC available target rules" 76 | @echo 77 | @echo "-- Host machine --" 78 | @echo " build Builds docker image" 79 | @echo " start Starts docker compose" 80 | @echo " stop Stops docker compose" 81 | @echo " restart Restarts docker containers" 82 | @echo " client_shell Runs a shell on the client 1 container" 83 | @echo " server_shell Runs a shell on the server container" 84 | @echo " logs Tails the server application logs" 85 | @echo " load_test Runs load test experiment spamming clients" 86 | @echo " parallel_load_test Runs load test experiment spamming clients in parallel" 87 | @echo " stop_load_test Stops load test experiment" 88 | @echo 89 | @echo "-- Inside Container --" 90 | @echo " compile Compiles the eQUIC for kernel and userspace" 91 | @echo " kernel Compiles the eBPF program for kernel" 92 | @echo " userspace Compiles the eQUIC user space program" 93 | @echo " library Builds eQUIC as a shared library" 94 | @echo " echo Builds lsquic echo server with eQUIC" 95 | @echo " http Builds lsquic http server with eQUIC" 96 | @echo " clean Remove all files resulted by the compilations" 97 | @echo " clean_logs Remove all log files from logging directory" 98 | @echo " load Loads the eBPF program into interface" 99 | @echo " unload Unloads the eBPF program into interface" 100 | @echo " debug Tails the eBPF program logs (trace_pipe)" 101 | @echo " show Shows the network interface link state" 102 | @echo " bpf_dev Runs all targets to build load and debug eBPF" 103 | @echo " bpf Runs all targets to build load eBPF program" 104 | @echo " run_server Runs the lsquic echo binary as a server" 105 | @echo " run_client Runs the lsquic echo binary as a client" 106 | @echo " http_server Runs the lsquic http binary as a server" 107 | @echo " http_client Runs the lsquic http binary as a client" 108 | @echo " equic_http_server Runs the lsquic http binary as a server with -e option (eQUIC)" 109 | @echo 110 | 111 | 112 | .PHONY: greetings 113 | greetings: 114 | @echo " ___ _ _ ___ ____ " 115 | @echo " ___ / _ \| | | |_ _/ ___|" 116 | @echo " / _ \ | | | | | || | | " 117 | @echo " | __/ |_| | |_| || | |___ " 118 | @echo " \___|\__\_\\\\\___/|___\____|" 119 | @echo 120 | 121 | 122 | # Builds docker images locally 123 | .PHONY: build 124 | build: greetings $(DOCKERFILES_DIR)/Dockerfile 125 | $(info Building eQUIC docker image..) 126 | @$(DOCKER_CMD) build --tag equic:$(DOCKER_TAG) --file $(word 2, $^) --memory=8g --cpuset-cpus=0,1,2,3 . 127 | 128 | 129 | .PHONY: start 130 | start: $(DOCKERFILES_DIR)/docker-compose.yaml 131 | $(info Running eQUIC client and server..) 132 | @$(DOCKER_COMPOSE_CMD) --file $< up --detach 133 | 134 | 135 | .PHONY: stop 136 | stop: $(DOCKERFILES_DIR)/docker-compose.yaml 137 | $(info Stopping eQUIC client and server..) 138 | @$(DOCKER_COMPOSE_CMD) --file $< down --remove-orphans 139 | 140 | 141 | .PHONY: restart 142 | restart: stop start 143 | 144 | 145 | .PHONY: client_shell 146 | client_shell: 147 | @docker exec -it client0 bash 148 | 149 | 150 | .PHONY: server_shell 151 | server_shell: 152 | @docker exec -it equic-server bash 153 | 154 | 155 | .PHONY: logs 156 | logs: 157 | @$(DOCKER_CMD) logs --tail 100 --follow equic-server 158 | 159 | 160 | # 161 | # Targets to run XDP related tasks 162 | # 163 | .PHONY: compile 164 | compile: greetings kernel userspace 165 | 166 | 167 | .PHONY: kernel 168 | kernel: $(SRC)/equic_kern.c 169 | $(info Compiling eBPF kernel program) 170 | $(CC) -target bpf -c $< -o $(BIN)/equic_kern.o -I $(INCLUDE_PATH) $(CFLAGS) 171 | 172 | 173 | .PHONY: userspace 174 | userspace: library 175 | $(info Compiling eQUIC userspace program standalone program) 176 | $(CC) $(SRC)/equic.c $(BIN)/equic_user.o -O2 -lbpf -lelf -lz -o $(BIN)/equic 177 | 178 | 179 | .PHONY: library 180 | library: $(SRC)/equic_user.c 181 | $(info Compiling eQUIC userspace as static library) 182 | gcc -c $< -fPIE -O2 -o $(BIN)/equic_user.o 183 | 184 | 185 | .PHONY: echo 186 | echo: 187 | $(info Compiling lsquic echo server with eQUIC offload) 188 | @cd $(LSQUIC_PATH) && make echo_server 189 | 190 | 191 | .PHONY: http 192 | http: 193 | $(info Compiling lsquic HTTP server with eQUIC offload) 194 | @cd $(LSQUIC_PATH) && make http_server 195 | 196 | 197 | .PHONY: clean 198 | clean: 199 | @rm -rvf $(BIN)/equic $(wildcard $(BIN)/*.o) 200 | 201 | 202 | .PHONY: clean_logs 203 | clean_logs: 204 | @rm -rvf $(LOGS)/*.log 205 | 206 | 207 | .PHONY: load 208 | load: 209 | $(info Loading eBPF program on interface $(IFACE)) 210 | ip link set dev $(IFACE) xdp obj $(BIN)/equic_kern.o sec equic 211 | 212 | 213 | .PHONY: unload 214 | unload: 215 | $(info Unloading eBPF program from interface $(IFACE)) 216 | ip link set dev $(IFACE) xdp off 217 | 218 | 219 | .PHONY: debug 220 | debug: 221 | $(info Entering debug mode) 222 | $(call check_debugfs) 223 | @cat $(TRACE_PIPE) | tee $(LOGS)/$(shell date +%s)-$(KERNEL_LOG_SUFFIX).log 224 | 225 | 226 | .PHONY: show 227 | show: 228 | @ip link show dev $(IFACE) 229 | 230 | 231 | .PHONY: bpf_dev 232 | bpf_dev: greetings unload compile load debug 233 | 234 | 235 | .PHONY: bpf 236 | bpf: greetings unload compile load 237 | 238 | 239 | .PHONY: run_server 240 | run_server: $(LSQUIC_PATH)/bin/echo_server 241 | $(eval SSL_DIR=/src/equic/ssl) 242 | $< -c localhost,$(SSL_DIR)/cert.pem,$(SSL_DIR)/private.key 243 | 244 | 245 | .PHONY: run_client 246 | run_client: $(LSQUIC_PATH)/bin/echo_client 247 | $(eval SRV_ADDR=$(shell host equic-server | awk '{print $$4}')) 248 | $< -H localhost -s $(SRV_ADDR):12345 249 | 250 | 251 | .PHONY: http_server 252 | http_server: $(LSQUIC_PATH)/bin/http_server 253 | $(eval SSL_DIR=/src/equic/ssl) 254 | $< -c localhost,$(SSL_DIR)/cert.pem,$(SSL_DIR)/private.key > $(LOGS)/$(NOW)-$(LOG_SUFFIX).log 255 | 256 | 257 | .PHONY: equic_http_server 258 | equic_http_server: $(LSQUIC_PATH)/bin/http_server 259 | $(eval SSL_DIR=/src/equic/ssl) 260 | $< -e -c localhost,$(SSL_DIR)/cert.pem,$(SSL_DIR)/private.key > $(LOGS)/$(NOW)-$(LOG_SUFFIX).log 261 | 262 | 263 | .PHONY: http_client 264 | http_client: $(LSQUIC_PATH)/bin/http_client 265 | $(eval SRV_ADDR=$(shell host equic-server | awk '{print $$4}')) 266 | $< -H localhost -s $(SRV_ADDR):12345 -p /$(REQ_SIZE) 267 | 268 | 269 | .PHONY: load_test 270 | load_test: $(DOCKERFILES_DIR)/docker-compose-load-clients.yaml 271 | $(info Running eQUIC load test..) 272 | REQ_SIZE=$(REQ_SIZE) $(DOCKER_COMPOSE_CMD) --file $< up 273 | $(DOCKER_COMPOSE_CMD) --file $< down 274 | 275 | 276 | .PHONY: parallel_load_test 277 | parallel_load_test: $(DOCKERFILES_DIR)/docker-compose-parallel-load-clients.yaml 278 | $(info Running eQUIC parallel load test..) 279 | REQ_SIZE=$(REQ_SIZE) $(DOCKER_COMPOSE_CMD) --file $< up 280 | $(DOCKER_COMPOSE_CMD) --file $< down 281 | 282 | 283 | .PHONY: stop_load_test 284 | stop_load_test: 285 | $(info Stopping eQUIC load test..) 286 | $(DOCKER_COMPOSE_CMD) --file $(DOCKERFILES_DIR)/docker-compose-load-clients.yaml down 287 | $(DOCKER_COMPOSE_CMD) --file $(DOCKERFILES_DIR)/docker-compose-parallel-load-clients.yaml down 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eQUIC 2 | 3 | QUIC protocol connection quota control offload to eBPF + XDP 4 | 5 | 6 | ### Dependencies 7 | 8 | We use docker to run a QUIC server and two container clients to trigger 9 | requests. 10 | 11 | ### Getting started 12 | 13 | This project uses `Makefile` to automate small tasks. Try running 14 | `make help` at the project root directory to see all available target rules. 15 | 16 | 17 | First of all, you should build the docker images to run the applications. 18 | To do it, run as follows: 19 | 20 | ```bash 21 | $> make build 22 | ``` 23 | 24 | To run the containers we hava a `docker/docker-compose.yaml` file to that. 25 | In order to run one server and two client containers do as follows: 26 | 27 | ```bash 28 | $> make start 29 | ``` 30 | 31 | Once the containers are ready and runnning you are able to get a shell inside 32 | each of the containers. When you get a shell, you'll be placed at the eQUIC 33 | project directory. Thus, you can use the Makefile again. Run `make help` and 34 | check all target rules available to be runned from inside a container. 35 | 36 | ```bash 37 | # Get a shell on the server 38 | $> make server_shell 39 | # Get a shell on the client 40 | $> make client_shell 41 | ``` 42 | 43 | From inside the server container, compile the eQUIC library and kernel module. 44 | 45 | ```bash 46 | $server> make clean compile 47 | ``` 48 | 49 | Once you have the kernel module and library ready, you can compile the server 50 | program: 51 | 52 | ```bash 53 | $server> make echo 54 | ``` 55 | 56 | Then run the server binary that will load the kernel module at runtime: 57 | ```bash 58 | $server> make run_server 59 | /src/lsquic/echo_server -c localhost,/src/equic/ssl/cert.pem,/src/equic/ssl/private.key 60 | [eQUIC] Action=Read, Type=OS, Interface=182 61 | [eQUIC] Action=Load, Type=BPF, Object=/src/equic/bin/equic_kern.o 62 | [eQUIC] Action=Load, Type=BPF, Map=counters 63 | [eQUIC] Action=Setup, Type=BPF, Hook=XDP 64 | ``` 65 | 66 | All right! To try connecting to the server with a client, open a shell on one 67 | of the client containers using `make client1_shell` and then run as follows 68 | from inside the container: 69 | 70 | ```bash 71 | $client1> make run_client 72 | ``` 73 | 74 | Then you can type anything and once you hit Return, the packet will be sent 75 | to the server and ECHOED back to you using QUIC protocol. Also, if you reach 76 | the Quota limit defined inside the eQUIC library your packets are going to be 77 | droped directly on the kernel. 78 | 79 | 80 | ### Running HTTP client and server 81 | 82 | On the server container run: 83 | 84 | ```bash 85 | $> make http # compiles http code 86 | $> make http_server # Runs the web server 87 | ``` 88 | 89 | On client container run: 90 | 91 | ```bash 92 | $> make http_client # Runs the client application that triggers requests to server 93 | ``` 94 | 95 | 96 | ### Running experiments 97 | 98 | Make sure you have the http server running inside the server container: 99 | 100 | ```bash 101 | $> make http_server 102 | ``` 103 | 104 | On the host machine run: 105 | 106 | ```bash 107 | $> make load_test REQ_SIZE=4k 108 | ``` 109 | 110 | It will spam clients sending requests to the server. 111 | If you want to run experiments in parallel, do as follows: 112 | 113 | ```bash 114 | make parallel_load_test REQ_SIZE=1k 115 | ``` 116 | It will also spam clients sending requests to the server, but now in parallel 117 | 118 | > Other experiments can be executed following [experiments/README.md](experiments/README.md) 119 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantuza/equic/a64166b49780851a6db3b163e608d6058c722387/bin/.gitkeep -------------------------------------------------------------------------------- /bpftrace/Makefile: -------------------------------------------------------------------------------- 1 | 2 | APP_NAME := eQUIC BPFTrace scripts 3 | 4 | TRACES_DIR := ../logs/traces 5 | 6 | 7 | .PHONY: help 8 | help: ## Displays help message 9 | @echo "Makefile to control development tasks for $(APP_NAME)" 10 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 11 | 12 | 13 | .PHONY: page_fault 14 | page_fault: ## Count page faults by process 15 | bpftrace -e 'software:faults:1 { @[comm] = count(); }' > $(TRACES_DIR)/$@-$(shell date +%s).trace 16 | 17 | 18 | .PHONY: syscall_rate 19 | syscall_rate: ## Show per-second syscall rates 20 | bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @ = count(); } interval:s:1 { print(@); clear(@); }' > $(TRACES_DIR)/$@-$(shell date +%s).trace 21 | 22 | 23 | .PHONY: read_distribution 24 | read_distribution: ## Read size distribution by proces 25 | bpftrace -e 'tracepoint:syscalls:sys_exit_read { @[comm] = hist(args->ret); }' > $(TRACES_DIR)/$@-$(shell date +%s).trace 26 | 27 | 28 | .PHONY: cpu_sched_latency 29 | cpu_sched_latency: ## CPU scheduler run queue latency as a histogram 30 | ./runqlat.bt > $(TRACES_DIR)/$@-$(shell date +%s).trace 31 | 32 | 33 | .PHONY: packet_drops 34 | packet_drops: ## Trace kernel-based TCP packet drops with details 35 | ./tcpdrop.bt > $(TRACES_DIR)/$@-$(shell date +%s).trace 36 | 37 | 38 | .PHONY: block_io_latency 39 | block_io_latency: ## Block I/O latency as a histogram 40 | ./biolatency.bt > $(TRACES_DIR)/$@-$(shell date +%s).trace 41 | 42 | 43 | .PHONY: clean_traces 44 | clean_traces: ## Cleans all traces logs 45 | rm -rf $(TRACES_DIR)/*.trace 46 | -------------------------------------------------------------------------------- /bpftrace/biolatency.bt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bpftrace 2 | /* 3 | * biolatency.bt Block I/O latency as a histogram. 4 | * For Linux, uses bpftrace, eBPF. 5 | * 6 | * This is a bpftrace version of the bcc tool of the same name. 7 | * 8 | * Copyright 2018 Netflix, Inc. 9 | * Licensed under the Apache License, Version 2.0 (the "License") 10 | * 11 | * 13-Sep-2018 Brendan Gregg Created this. 12 | */ 13 | 14 | BEGIN 15 | { 16 | printf("Tracing block device I/O... Hit Ctrl-C to end.\n"); 17 | } 18 | 19 | kprobe:blk_account_io_start, 20 | kprobe:__blk_account_io_start 21 | { 22 | @start[arg0] = nsecs; 23 | } 24 | 25 | kprobe:blk_account_io_done, 26 | kprobe:__blk_account_io_done 27 | /@start[arg0]/ 28 | { 29 | @usecs = hist((nsecs - @start[arg0]) / 1000); 30 | delete(@start[arg0]); 31 | } 32 | 33 | END 34 | { 35 | clear(@start); 36 | } 37 | -------------------------------------------------------------------------------- /bpftrace/runqlat.bt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bpftrace 2 | /* 3 | * runqlat.bt CPU scheduler run queue latency as a histogram. 4 | * For Linux, uses bpftrace, eBPF. 5 | * 6 | * This is a bpftrace version of the bcc tool of the same name. 7 | * 8 | * Copyright 2018 Netflix, Inc. 9 | * Licensed under the Apache License, Version 2.0 (the "License") 10 | * 11 | * 17-Sep-2018 Brendan Gregg Created this. 12 | */ 13 | 14 | #include 15 | 16 | BEGIN 17 | { 18 | printf("Tracing CPU scheduler... Hit Ctrl-C to end.\n"); 19 | } 20 | 21 | tracepoint:sched:sched_wakeup, 22 | tracepoint:sched:sched_wakeup_new 23 | { 24 | @qtime[args->pid] = nsecs; 25 | } 26 | 27 | tracepoint:sched:sched_switch 28 | { 29 | if (args->prev_state == TASK_RUNNING) { 30 | @qtime[args->prev_pid] = nsecs; 31 | } 32 | 33 | $ns = @qtime[args->next_pid]; 34 | if ($ns) { 35 | @usecs = hist((nsecs - $ns) / 1000); 36 | } 37 | delete(@qtime[args->next_pid]); 38 | } 39 | 40 | END 41 | { 42 | clear(@qtime); 43 | } 44 | -------------------------------------------------------------------------------- /bpftrace/tcpdrop.bt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bpftrace 2 | /* 3 | * tcpdrop.bt Trace TCP kernel-dropped packets/segments. 4 | * For Linux, uses bpftrace and eBPF. 5 | * 6 | * USAGE: tcpdrop.bt 7 | * 8 | * This is a bpftrace version of the bcc tool of the same name. 9 | * It is limited to ipv4 addresses, and cannot show tcp flags. 10 | * 11 | * This provides information such as packet details, socket state, and kernel 12 | * stack trace for packets/segments that were dropped via tcp_drop(). 13 | 14 | * WARNING: this script attaches to the tcp_drop kprobe which is likely inlined 15 | * on newer kernels and not replaced by anything else, therefore 16 | * the script will stop working 17 | 18 | * Copyright (c) 2018 Dale Hamel. 19 | * Licensed under the Apache License, Version 2.0 (the "License") 20 | 21 | * 23-Nov-2018 Dale Hamel created this. 22 | */ 23 | 24 | #ifndef BPFTRACE_HAVE_BTF 25 | #include 26 | #include 27 | #else 28 | #include 29 | #endif 30 | 31 | BEGIN 32 | { 33 | printf("Tracing tcp drops. Hit Ctrl-C to end.\n"); 34 | printf("%-8s %-8s %-16s %-21s %-21s %-8s\n", "TIME", "PID", "COMM", "SADDR:SPORT", "DADDR:DPORT", "STATE"); 35 | 36 | // See https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h 37 | @tcp_states[1] = "ESTABLISHED"; 38 | @tcp_states[2] = "SYN_SENT"; 39 | @tcp_states[3] = "SYN_RECV"; 40 | @tcp_states[4] = "FIN_WAIT1"; 41 | @tcp_states[5] = "FIN_WAIT2"; 42 | @tcp_states[6] = "TIME_WAIT"; 43 | @tcp_states[7] = "CLOSE"; 44 | @tcp_states[8] = "CLOSE_WAIT"; 45 | @tcp_states[9] = "LAST_ACK"; 46 | @tcp_states[10] = "LISTEN"; 47 | @tcp_states[11] = "CLOSING"; 48 | @tcp_states[12] = "NEW_SYN_RECV"; 49 | } 50 | 51 | kprobe:tcp_drop 52 | { 53 | $sk = ((struct sock *) arg0); 54 | $inet_family = $sk->__sk_common.skc_family; 55 | 56 | if ($inet_family == AF_INET || $inet_family == AF_INET6) { 57 | if ($inet_family == AF_INET) { 58 | $daddr = ntop($sk->__sk_common.skc_daddr); 59 | $saddr = ntop($sk->__sk_common.skc_rcv_saddr); 60 | } else { 61 | $daddr = ntop($sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); 62 | $saddr = ntop($sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); 63 | } 64 | $lport = $sk->__sk_common.skc_num; 65 | $dport = $sk->__sk_common.skc_dport; 66 | 67 | // Destination port is big endian, it must be flipped 68 | $dport = bswap($dport); 69 | 70 | $state = $sk->__sk_common.skc_state; 71 | $statestr = @tcp_states[$state]; 72 | 73 | time("%H:%M:%S "); 74 | printf("%-8d %-16s ", pid, comm); 75 | printf("%39s:%-6d %39s:%-6d %-10s\n", $saddr, $lport, $daddr, $dport, $statestr); 76 | printf("%s\n", kstack); 77 | } 78 | } 79 | 80 | END 81 | { 82 | clear(@tcp_states); 83 | } 84 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | ENV TZ=America/Sao_Paulo 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y build-essential git cmake \ 8 | software-properties-common \ 9 | zlib1g-dev libevent-dev golang \ 10 | llvm vim libelf-dev iputils-ping \ 11 | clang netcat tcpdump dnsutils iproute2 \ 12 | libc6-dev-i386 bpftrace 13 | 14 | 15 | RUN mkdir /src 16 | WORKDIR /src 17 | 18 | 19 | RUN git clone https://boringssl.googlesource.com/boringssl && \ 20 | cd boringssl && \ 21 | cmake . && \ 22 | make 23 | 24 | RUN git clone https://github.com/litespeedtech/lsquic.git && \ 25 | cd lsquic && \ 26 | git submodule update --init --recursive 27 | 28 | RUN cd /src/lsquic && \ 29 | cmake -DBORINGSSL_DIR=/src/boringssl . && \ 30 | make 31 | 32 | RUN cd lsquic && make test && cp bin/http_client /usr/bin/ && cp bin/http_server /usr/bin 33 | 34 | # 35 | # Configures eQUIC 36 | # 37 | COPY . /src/equic 38 | 39 | WORKDIR /src/equic 40 | 41 | # TODO: Check how we can do it cause docker build does not accept 42 | # Mounts kernel tracing debug filesystem for debugging eBPF 43 | # mount -t debugfs none /sys/kernel/debug && \ 44 | 45 | # Needed to link asm/byteorder.h into eBPF 46 | RUN ln -s /usr/include/x86_64-linux-gnu/asm/ /usr/include/asm 47 | 48 | 49 | # Clone and build libbpf as staticlibrary 50 | RUN git submodule update --init && \ 51 | cd libbpf/src && \ 52 | make BUILD_STATIC_ONLY=y && \ 53 | make install BUILD_STATIC_ONLY=y LIBDIR=/usr/lib/x86_64-linux-gnu/ 54 | 55 | # Clones the master branch of linux kernel 56 | RUN git clone --depth 1 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git && \ 57 | # Overrides all bpf headers with the latest ones from linux kernel source 58 | cp linux/include/uapi/linux/bpf* /usr/include/linux/ 59 | # TODO: Remove linux folder after copying bpf files to release space 60 | 61 | CMD /src/equic/docker/entrypoint.sh 62 | -------------------------------------------------------------------------------- /docker/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | # Don't forget to run this container with --privileged option 2 | 3 | FROM alpine:latest 4 | 5 | RUN apk add --update iproute2 tcpdump clang linux-headers 6 | 7 | COPY . /server 8 | 9 | WORKDIR /server 10 | 11 | RUN clang -target bpf -c drop_world.c -o drop_world.o -O2 12 | 13 | CMD ip link set dev eth0 xdpdrv obj drop_world.o sec .text \ 14 | && tcpdump -tn -i eth0 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.mvfst: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | ENV TZ=America/Sao_Paulo 5 | 6 | RUN apt-get update -y && \ 7 | # Project dependencies 8 | apt-get install -y iproute2 tcpdump git sudo dnsutils strace \ 9 | vim clang llvm libelf-dev && \ 10 | # mvfst dependencies 11 | apt-get install -y g++ cmake libboost-all-dev libevent-dev \ 12 | libdouble-conversion-dev libgoogle-glog-dev libgflags-dev \ 13 | libiberty-dev liblz4-dev liblzma-dev libsnappy-dev make \ 14 | zlib1g-dev binutils-dev libjemalloc-dev libssl-dev \ 15 | pkg-config libsodium-dev 16 | 17 | 18 | # Builds Facebook mvfst project 19 | RUN git clone https://github.com/facebookincubator/mvfst.git && \ 20 | cd mvfst && ./build_helper.sh 21 | 22 | 23 | # 24 | # Configures eQUIC 25 | # 26 | COPY . /equic 27 | 28 | WORKDIR /equic 29 | 30 | # TODO: Check how we can do it cause docker build does not accept 31 | # Mounts kernel tracing debug filesystem for debugging eBPF 32 | # mount -t debugfs none /sys/kernel/debug && \ 33 | 34 | # Needed to link asm/byteorder.h into eBPF 35 | RUN ln -s /usr/include/x86_64-linux-gnu/asm/ /usr/include/asm 36 | 37 | # Clone and build libbpf as staticlibrary 38 | RUN git submodule update --init && \ 39 | cd libbpf/src && \ 40 | make && \ 41 | make install 42 | 43 | # Clones the master branch of linux kernel 44 | RUN git clone --depth 1 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git && \ 45 | # Overrides all bpf headers with the latest ones from linux kernel source 46 | cp linux/include/uapi/linux/bpf* /usr/include/linux/ 47 | # TODO: Remove linux folder after copying bpf files to release space 48 | -------------------------------------------------------------------------------- /docker/docker-compose-load-clients.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | client1: 6 | container_name: load0 7 | image: equic:local 8 | command: /src/equic/docker/load-test-entrypoint.sh 9 | environment: 10 | - REQ_SIZE=$REQ_SIZE 11 | volumes: 12 | - ..:/src/equic 13 | 14 | client2: 15 | container_name: load1 16 | image: equic:local 17 | command: /src/equic/docker/load-test-entrypoint.sh 18 | environment: 19 | - REQ_SIZE=$REQ_SIZE 20 | volumes: 21 | - ..:/src/equic 22 | 23 | client3: 24 | container_name: load2 25 | image: equic:local 26 | command: /src/equic/docker/load-test-entrypoint.sh 27 | environment: 28 | - REQ_SIZE=$REQ_SIZE 29 | volumes: 30 | - ..:/src/equic 31 | 32 | client4: 33 | container_name: load3 34 | image: equic:local 35 | command: /src/equic/docker/load-test-entrypoint.sh 36 | environment: 37 | - REQ_SIZE=$REQ_SIZE 38 | volumes: 39 | - ..:/src/equic 40 | 41 | networks: 42 | default: 43 | external: 44 | name: docker_equic 45 | -------------------------------------------------------------------------------- /docker/docker-compose-parallel-load-clients.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | client5: 6 | container_name: parallel0 7 | image: equic:local 8 | command: /src/equic/docker/load-test-entrypoint.sh 9 | environment: 10 | - REQ_SIZE=$REQ_SIZE 11 | - PARALLEL=1 12 | volumes: 13 | - ..:/src/equic 14 | 15 | client6: 16 | container_name: parallel1 17 | image: equic:local 18 | command: /src/equic/docker/load-test-entrypoint.sh 19 | environment: 20 | - REQ_SIZE=$REQ_SIZE 21 | - PARALLEL=1 22 | volumes: 23 | - ..:/src/equic 24 | 25 | client7: 26 | container_name: parallel2 27 | image: equic:local 28 | command: /src/equic/docker/load-test-entrypoint.sh 29 | environment: 30 | - REQ_SIZE=$REQ_SIZE 31 | - PARALLEL=1 32 | volumes: 33 | - ..:/src/equic 34 | 35 | client8: 36 | container_name: parallel3 37 | image: equic:local 38 | command: /src/equic/docker/load-test-entrypoint.sh 39 | environment: 40 | - REQ_SIZE=$REQ_SIZE 41 | - PARALLEL=1 42 | volumes: 43 | - ..:/src/equic 44 | 45 | networks: 46 | default: 47 | external: 48 | name: docker_equic 49 | -------------------------------------------------------------------------------- /docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | server: 6 | container_name: equic-server 7 | image: equic:local 8 | volumes: 9 | - ..:/src/equic 10 | - ../CMakeLists.txt:/src/lsquic/bin/CMakeLists.txt 11 | - ../echo_server.c:/src/lsquic/bin/echo_server.c 12 | - ../http_server.c:/src/lsquic/bin/http_server.c 13 | cap_add: # Necessary to modify network interface 14 | - NET_ADMIN 15 | - SYS_ADMIN 16 | networks: 17 | - equic 18 | restart: always 19 | ulimits: 20 | memlock: 21 | soft: -1 22 | hard: -1 23 | 24 | client0: 25 | container_name: client0 26 | image: equic:local 27 | command: tail -f 28 | volumes: 29 | - ..:/src/equic 30 | networks: 31 | - equic 32 | 33 | networks: 34 | equic: 35 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Inspired by https://github.com/mmisono/try-bpftrace-in-mac 3 | 4 | mount -t debugfs none /sys/kernel/debug/ 5 | 6 | sysctl -w kernel.kptr_restrict=0 >/dev/null 2>&1 7 | sysctl -w kernel.perf_event_paranoid=2 >/dev/null 2>&1 8 | 9 | tail -f 10 | -------------------------------------------------------------------------------- /docker/load-test-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Number of request to be done to the remote server 4 | N_REQS=1000; 5 | 6 | 7 | # Default size of the payload expected in the response 8 | if [ -z "${REQ_SIZE}" ]; then 9 | REQ_SIZE="64k"; 10 | fi; 11 | echo "Load test will ask for responses with with ${REQ_SIZE}" 12 | 13 | 14 | # Current container name 15 | HOSTNAME=$(hostname) 16 | 17 | # Time to wait before triggering parallel load 18 | SLEEP_TIME=30 19 | 20 | # 21 | # Checks for parallel execution. Clients that triggers parallel 22 | # requests are the ones that reach the quota limit. In other words, 23 | # they are the attacker containers 24 | # 25 | if [[ ${PARALLEL} -eq 1 ]]; then 26 | 27 | echo "Going to run in parallel in ${SLEEP_TIME} seconds.." 28 | 29 | # Attacker containers waits 30 seconds before starting 30 | # sending load to the server 31 | sleep ${SLEEP_TIME}; 32 | fi; 33 | 34 | # 35 | # Load test execution loop 36 | # 37 | for i in $(seq ${N_REQS}); do 38 | 39 | 40 | if [[ ${PARALLEL} -eq 1 ]]; then 41 | echo "Parallel request $i from $HOSTNAME with response size $REQ_SIZE" 42 | make http_client REQ_SIZE=${REQ_SIZE} > /dev/null & 43 | 44 | else 45 | echo "Request $i from $HOSTNAME with response size $REQ_SIZE" 46 | make http_client REQ_SIZE=${REQ_SIZE} > /dev/null 47 | fi; 48 | done; 49 | -------------------------------------------------------------------------------- /echo_server.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2 | /* 3 | * echo_server.c -- QUIC server that echoes back input line by line 4 | */ 5 | 6 | 7 | /* 8 | * This code is a modified version of the original echo_server.c 9 | * from LiteSpeed QUIC library for research purpose. 10 | * 11 | * This implementation adds eQUIC library to load a eBPF program on 12 | * the XDP kernel hook. On every new QUIC connection setup and QUIC 13 | * connection teardown this program updates a eBPF kernel Map. 14 | * 15 | * Take a look at the code in the following functions: 16 | * . debug_connection 17 | * . echo_server_on_new_conn 18 | * . echo_server_on_conn_closed 19 | * . main 20 | * 21 | * Author: Gustavo Pantuza 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #ifndef WIN32 33 | #include 34 | #include 35 | #else 36 | #include "vc_compat.h" 37 | #include "getopt.h" 38 | #endif 39 | 40 | #include "lsquic.h" 41 | #include "test_common.h" 42 | #include "prog.h" 43 | 44 | #include "../src/liblsquic/lsquic_logger.h" 45 | 46 | #include "/src/equic/src/equic_user.h" 47 | 48 | struct lsquic_conn_ctx; 49 | 50 | struct echo_server_ctx { 51 | TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs; 52 | unsigned max_reqs; 53 | int n_conn; 54 | struct sport_head sports; 55 | struct prog *prog; 56 | 57 | equic_t *equic; 58 | }; 59 | 60 | struct lsquic_conn_ctx { 61 | TAILQ_ENTRY(lsquic_conn_ctx) next_connh; 62 | lsquic_conn_t *conn; 63 | struct echo_server_ctx *server_ctx; 64 | }; 65 | 66 | /** 67 | * Prints a 4-tuple with Source IPv4 address and port and Destination 68 | * IPv4 address and port 69 | */ 70 | static void 71 | debug_connection (const struct sockaddr *local, const struct sockaddr *peer, char *action) 72 | { 73 | /* Gets server ip address and port */ 74 | struct sockaddr_in *local_addr_in = (struct sockaddr_in *)local; 75 | char *local_addr_str = inet_ntoa(local_addr_in->sin_addr); 76 | uint16_t local_port; 77 | local_port = htons(local_addr_in->sin_port); 78 | 79 | /* Gets client ip address and port */ 80 | struct sockaddr_in *peer_addr_in = (struct sockaddr_in *)peer; 81 | char *peer_addr_str = inet_ntoa(peer_addr_in->sin_addr); 82 | uint16_t peer_port; 83 | peer_port = htons(peer_addr_in->sin_port); 84 | 85 | printf( 86 | "[eQUIC] Action=%s, Client=%s, SourcePort=%d, Server=%s, DestinationPort=%d\n", 87 | action, peer_addr_str, peer_port, local_addr_str, local_port 88 | ); 89 | } 90 | 91 | 92 | static lsquic_conn_ctx_t * 93 | echo_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 94 | { 95 | struct echo_server_ctx *server_ctx = stream_if_ctx; 96 | lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); 97 | conn_h->conn = conn; 98 | conn_h->server_ctx = server_ctx; 99 | TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh); 100 | LSQ_NOTICE("New connection!"); 101 | print_conn_info(conn); 102 | 103 | /* Read sockaddr from connection for client and server */ 104 | const struct sockaddr *local; 105 | const struct sockaddr *peer; 106 | lsquic_conn_get_sockaddr(conn, &local, &peer); 107 | 108 | debug_connection(local, peer, "ConnectionSetup"); 109 | 110 | /* 111 | * Increment eQUIC Kernel counters Map key for the give peer (client) 112 | */ 113 | equic_inc_counter(server_ctx->equic, peer); 114 | 115 | return conn_h; 116 | } 117 | 118 | 119 | static void 120 | echo_server_on_conn_closed (lsquic_conn_t *conn) 121 | { 122 | /* Read sockaddr from connection for client and server */ 123 | const struct sockaddr *local; 124 | const struct sockaddr *peer; 125 | lsquic_conn_get_sockaddr(conn, &local, &peer); 126 | 127 | lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 128 | if (conn_h->server_ctx->n_conn) 129 | { 130 | --conn_h->server_ctx->n_conn; 131 | LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn); 132 | if (0 == conn_h->server_ctx->n_conn) 133 | prog_stop(conn_h->server_ctx->prog); 134 | } 135 | else 136 | LSQ_NOTICE("Connection closed"); 137 | TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh); 138 | free(conn_h); 139 | 140 | debug_connection(local, peer, "ConnectionClose"); 141 | 142 | /* 143 | * Decrement eQUIC Kernel counters Map key for the give peer (client) 144 | */ 145 | equic_dec_counter(conn_h->server_ctx->equic, peer); 146 | } 147 | 148 | 149 | struct lsquic_stream_ctx { 150 | lsquic_stream_t *stream; 151 | struct echo_server_ctx *server_ctx; 152 | char buf[0x100]; 153 | size_t buf_off; 154 | }; 155 | 156 | 157 | static lsquic_stream_ctx_t * 158 | echo_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 159 | { 160 | lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h)); 161 | st_h->stream = stream; 162 | st_h->server_ctx = stream_if_ctx; 163 | st_h->buf_off = 0; 164 | lsquic_stream_wantread(stream, 1); 165 | return st_h; 166 | } 167 | 168 | 169 | static struct lsquic_conn_ctx * 170 | find_conn_h (const struct echo_server_ctx *server_ctx, lsquic_stream_t *stream) 171 | { 172 | struct lsquic_conn_ctx *conn_h; 173 | lsquic_conn_t *conn; 174 | 175 | conn = lsquic_stream_conn(stream); 176 | TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh) 177 | if (conn_h->conn == conn) 178 | return conn_h; 179 | return NULL; 180 | } 181 | 182 | 183 | static void 184 | echo_server_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 185 | { 186 | struct lsquic_conn_ctx *conn_h; 187 | size_t nr; 188 | 189 | nr = lsquic_stream_read(stream, st_h->buf + st_h->buf_off++, 1); 190 | if (0 == nr) 191 | { 192 | LSQ_NOTICE("EOF: closing connection"); 193 | lsquic_stream_shutdown(stream, 2); 194 | conn_h = find_conn_h(st_h->server_ctx, stream); 195 | lsquic_conn_close(conn_h->conn); 196 | } 197 | else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) 198 | { 199 | /* Found end of line: echo it back */ 200 | lsquic_stream_wantwrite(stream, 1); 201 | lsquic_stream_wantread(stream, 0); 202 | } 203 | else if (st_h->buf_off == sizeof(st_h->buf)) 204 | { 205 | /* Out of buffer space: line too long */ 206 | LSQ_NOTICE("run out of buffer space"); 207 | lsquic_stream_shutdown(stream, 2); 208 | } 209 | else 210 | { 211 | /* Keep reading */; 212 | } 213 | } 214 | 215 | 216 | static void 217 | echo_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 218 | { 219 | lsquic_stream_write(stream, st_h->buf, st_h->buf_off); 220 | st_h->buf_off = 0; 221 | lsquic_stream_flush(stream); 222 | lsquic_stream_wantwrite(stream, 0); 223 | lsquic_stream_wantread(stream, 1); 224 | } 225 | 226 | 227 | static void 228 | echo_server_on_stream_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 229 | { 230 | struct lsquic_conn_ctx *conn_h; 231 | LSQ_NOTICE("%s called", __func__); 232 | conn_h = find_conn_h(st_h->server_ctx, stream); 233 | LSQ_WARN("%s: TODO: free connection handler %p", __func__, conn_h); 234 | free(st_h); 235 | } 236 | 237 | 238 | const struct lsquic_stream_if server_echo_stream_if = { 239 | .on_new_conn = echo_server_on_new_conn, 240 | .on_conn_closed = echo_server_on_conn_closed, 241 | .on_new_stream = echo_server_on_new_stream, 242 | .on_read = echo_server_on_read, 243 | .on_write = echo_server_on_write, 244 | .on_close = echo_server_on_stream_close, 245 | }; 246 | 247 | 248 | static void 249 | usage (const char *prog) 250 | { 251 | const char *const slash = strrchr(prog, '/'); 252 | if (slash) 253 | prog = slash + 1; 254 | printf( 255 | "Usage: %s [opts]\n" 256 | "\n" 257 | "Options:\n" 258 | , prog); 259 | } 260 | 261 | 262 | int 263 | main (int argc, char **argv) 264 | { 265 | int opt, s; 266 | struct prog prog; 267 | struct echo_server_ctx server_ctx; 268 | 269 | memset(&server_ctx, 0, sizeof(server_ctx)); 270 | server_ctx.prog = &prog; 271 | TAILQ_INIT(&server_ctx.sports); 272 | TAILQ_INIT(&server_ctx.conn_ctxs); 273 | 274 | /* eQUIC initialization and setup */ 275 | equic_t equic; 276 | equic_get_interface(&equic, "eth0"); 277 | equic_read_object(&equic, "/src/equic/bin/equic_kern.o"); 278 | 279 | signal(SIGINT, equic_sigint_callback); 280 | signal(SIGTERM, equic_sigterm_callback); 281 | 282 | equic_load(&equic); 283 | 284 | server_ctx.equic = &equic; 285 | 286 | prog_init(&prog, LSENG_SERVER, &server_ctx.sports, 287 | &server_echo_stream_if, &server_ctx); 288 | 289 | while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hn:"))) 290 | { 291 | switch (opt) { 292 | case 'n': 293 | server_ctx.n_conn = atoi(optarg); 294 | break; 295 | case 'h': 296 | usage(argv[0]); 297 | prog_print_common_options(&prog, stdout); 298 | exit(0); 299 | default: 300 | if (0 != prog_set_opt(&prog, opt, optarg)) 301 | exit(1); 302 | } 303 | } 304 | 305 | if (0 != prog_prep(&prog)) 306 | { 307 | LSQ_ERROR("could not prep"); 308 | exit(EXIT_FAILURE); 309 | } 310 | 311 | LSQ_DEBUG("entering event loop"); 312 | 313 | s = prog_run(&prog); 314 | prog_cleanup(&prog); 315 | 316 | exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 317 | } 318 | -------------------------------------------------------------------------------- /experiments/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This makefile is responsible to rule all data analysis tasks 3 | # 4 | 5 | 6 | # 7 | # VARIABLES 8 | # 9 | LOGS_DIR := ../logs 10 | 11 | IN_DIR := in 12 | OUT_DIR := out 13 | STATS_DIR := stats 14 | 15 | # Python 3 binary path 16 | PYTHON3 := $(shell which python3) 17 | 18 | # Gnu Plot binary path 19 | GNUPLOT := $(shell which gnuplot) 20 | 21 | # 22 | # TARGET RULES 23 | # 24 | 25 | .DEFAULT_GOAL: help 26 | 27 | .PHONY: help 28 | help: 29 | @echo "===== eQUIC analysis =====" 30 | @echo 31 | @echo "Available target rules:" 32 | @echo 33 | @echo "extract Extracts data from log files" 34 | @echo "compute Compute and summarize data into statistics" 35 | @echo "aggregate Aggregate statistics into single files" 36 | @echo "plot Plots results using gnu plot" 37 | @echo "show Shows the graph results" 38 | @echo "clean Cleans experiments resulted files" 39 | @echo 40 | @echo "--" 41 | @echo 42 | 43 | 44 | .PHONY: extract 45 | extract: 46 | $(info === Extracting data from logs ===) 47 | @$(PYTHON3) extract.py $(LOGS_DIR)/*-baseline-256k.log 48 | @$(PYTHON3) extract.py $(LOGS_DIR)/*-userspace-256k.log 49 | @$(PYTHON3) extract.py $(LOGS_DIR)/*-kernel-256k.log 50 | @$(PYTHON3) extract.py $(LOGS_DIR)/*-parallel_cpu_userspace-256k.log 51 | @$(PYTHON3) extract.py $(LOGS_DIR)/*-parallel_cpu_kernel-256k.log 52 | @echo 53 | 54 | 55 | .PHONY: compute 56 | compute: 57 | $(info === Summarizing statistics from data ===) 58 | @$(PYTHON3) compute.py $(IN_DIR)/duration-baseline-256k.dat 59 | @$(PYTHON3) compute.py $(IN_DIR)/duration-userspace-256k.dat 60 | @$(PYTHON3) compute.py $(IN_DIR)/duration-kernel-256k.dat 61 | @$(PYTHON3) compute.py $(IN_DIR)/reqs_per_second-baseline-256k.dat 62 | @$(PYTHON3) compute.py $(IN_DIR)/reqs_per_second-userspace-256k.dat 63 | @$(PYTHON3) compute.py $(IN_DIR)/reqs_per_second-kernel-256k.dat 64 | @$(PYTHON3) compute.py $(IN_DIR)/block_duration-parallel_cpu_kernel-256k.dat 65 | @$(PYTHON3) compute.py $(IN_DIR)/block_duration-parallel_cpu_userspace-256k.dat 66 | @$(PYTHON3) cpu.py 67 | @$(PYTHON3) mem.py 68 | @echo 69 | 70 | 71 | .PHONY: aggregate 72 | aggregate: 73 | $(info === Aggregating statistics ===) 74 | @$(PYTHON3) aggregate.py 75 | @echo 76 | 77 | 78 | .PHONY: plot 79 | plot: 80 | $(info === Plotting graphs from statistics ===) 81 | @$(GNUPLOT) graphs.plt 82 | @echo 83 | 84 | 85 | .PHONY: show 86 | show: 87 | $(info === Showing results graphs ===) 88 | qlmanage -p $(OUT_DIR)/duration.png 89 | qlmanage -p $(OUT_DIR)/duration-en.png 90 | qlmanage -p $(OUT_DIR)/requests.png 91 | qlmanage -p $(OUT_DIR)/requests-en.png 92 | qlmanage -p $(OUT_DIR)/block-duration.png 93 | qlmanage -p $(OUT_DIR)/block-duration-en.png 94 | qlmanage -p $(OUT_DIR)/requests-per-second.png 95 | qlmanage -p $(OUT_DIR)/requests-per-second-en.png 96 | qlmanage -p $(OUT_DIR)/cpu-cdf.png 97 | qlmanage -p $(OUT_DIR)/cpu-cdf-en.png 98 | qlmanage -p $(OUT_DIR)/mem-cdf.png 99 | qlmanage -p $(OUT_DIR)/mem-cdf-en.png 100 | 101 | 102 | .PHONY: clean 103 | clean: 104 | $(info === Clean experiments files ===) 105 | @rm -rvf $(IN_DIR)/*.dat 106 | @rm -rvf $(STATS_DIR)/*.csv 107 | @rm -rvf $(OUT_DIR)/*.png 108 | @echo 109 | -------------------------------------------------------------------------------- /experiments/README.md: -------------------------------------------------------------------------------- 1 | # How to run experiments 2 | 3 | 4 | -------------------------------------------------------------------------------- /experiments/aggregate.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from sys import exit 3 | 4 | 5 | def get_and_write_reqs(): 6 | 7 | reqs_userspace = 0.0 8 | with open("stats/reqs_per_second-userspace.csv", "r") as infile: 9 | data = infile.readline().split(",") 10 | reqs_userspace = float(data[1]) 11 | 12 | reqs_kernel = 0.0 13 | with open("stats/reqs_per_second-kernel.csv", "r") as infile: 14 | data = infile.readline().split(",") 15 | reqs_kernel = float(data[1]) 16 | 17 | with open("stats/reqs_and_cpu.csv", "a") as outfile: 18 | outfile.write("Vazão média, {0}, {1}\n".format( 19 | reqs_userspace, reqs_kernel 20 | ) 21 | ) 22 | print("[Aggregated] Type=requests") 23 | 24 | def get_and_write_cpu_times(): 25 | 26 | cpu_userspace = 0.0 27 | with open("stats/block_duration-parallel_cpu_userspace.csv", "r") as infile: 28 | data = infile.readline().split(",") 29 | cpu_userspace = float(data[1]) 30 | 31 | cpu_kernel = 0.0 32 | with open("stats/block_duration-parallel_cpu_kernel.csv", "r") as infile: 33 | data = infile.readline().split(",") 34 | cpu_kernel = float(data[1]) 35 | 36 | with open("stats/reqs_and_cpu.csv", "a") as outfile: 37 | outfile.write("Bloqueio, {0}, {1}\n".format( 38 | cpu_userspace, cpu_kernel 39 | ) 40 | ) 41 | print("[Aggregated] Type=cpu") 42 | 43 | 44 | def aggregate_reqs_and_time(): 45 | 46 | get_and_write_reqs() 47 | get_and_write_cpu_times() 48 | 49 | 50 | def main(): 51 | 52 | aggregate_reqs_and_time() 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /experiments/compute.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from sys import exit 3 | 4 | from statistics import harmonic_mean 5 | 6 | 7 | OUT_DIR = "stats/" 8 | 9 | 10 | def compute_avg_duration(dat): 11 | 12 | durations = [] 13 | 14 | with open(dat) as ifile: 15 | 16 | for duration in ifile: 17 | durations.append(float(duration)) 18 | 19 | return harmonic_mean(durations) 20 | 21 | def compute_avg_reqs_per_second(dat): 22 | 23 | reqs = [] 24 | 25 | with open(dat) as ifile: 26 | 27 | for nreqs in ifile: 28 | reqs.append(int(nreqs)) 29 | 30 | return harmonic_mean(reqs) 31 | 32 | def compute_avg_block_duration(dat): 33 | 34 | durations = [] 35 | 36 | with open(dat) as ifile: 37 | 38 | for duration in ifile: 39 | durations.append(float(duration)) 40 | 41 | return harmonic_mean(durations) 42 | 43 | def compute(dat, meta): 44 | 45 | ofilename = OUT_DIR + meta["metric"] + "-" + meta["type"] + ".csv" 46 | 47 | avg_value = 0.0 48 | 49 | if meta["metric"] == "duration": 50 | avg_value = compute_avg_duration(dat) 51 | 52 | elif meta["metric"] == "reqs_per_second": 53 | avg_value = compute_avg_reqs_per_second(dat) 54 | 55 | elif meta["metric"] == "block_duration": 56 | avg_value = compute_avg_block_duration(dat) 57 | 58 | with open(ofilename, "a") as ofile: 59 | ofile.write("{0}, {1}\n".format(meta["size"], avg_value)) 60 | 61 | print("[Computed] Type={0}, Size={1}, To={2}".format( 62 | meta["type"], meta["size"], OUT_DIR) 63 | ) 64 | 65 | 66 | def main(): 67 | 68 | if not len(argv) > 1: 69 | print("[eQUIC Analysis] Missing log file as input") 70 | exit(1) 71 | 72 | dat = argv[1] 73 | 74 | splitted = dat.split("-") 75 | 76 | meta = { 77 | "metric": splitted[0].split("/")[-1], # For example: duration 78 | "type": splitted[1], # For example: baseline 79 | "size": splitted[2][:-4], # For example: 32k 80 | } 81 | 82 | compute(dat, meta) 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /experiments/cpu.py: -------------------------------------------------------------------------------- 1 | from statistics import NormalDist 2 | from statistics import mean 3 | from statistics import stdev 4 | 5 | 6 | 7 | def extract(logfile, output): 8 | """ Extracts CPU data from log file and outputs it to input directory """ 9 | 10 | ofile = open(output, "w") 11 | 12 | with open(logfile) as infile: 13 | 14 | # Skip first two lines 15 | infile.readline() 16 | infile.readline() 17 | infile.readline() 18 | 19 | for line in infile: 20 | 21 | data = line.split() 22 | 23 | ofile.write("{0}\n".format(data[7])) 24 | 25 | # skips title and blank line 26 | infile.readline() 27 | infile.readline() 28 | 29 | ofile.close() 30 | 31 | 32 | def compute(infile, outfile): 33 | 34 | ofile = open(outfile, "w") 35 | 36 | cpu = [] 37 | with open(infile) as indata: 38 | 39 | for line in indata: 40 | cpu.append(float(line)) 41 | 42 | mu = mean(cpu) 43 | sigma = stdev(cpu) 44 | 45 | dist = NormalDist(mu=mu, sigma=sigma) 46 | 47 | for x in sorted(cpu): 48 | ofile.write("{0}, {1}\n".format(x, dist.cdf(x))) 49 | 50 | ofile.close() 51 | 52 | def main(): 53 | 54 | extract("../logs/pid-stats-no-equic-parallel.log", "in/cpu-no-equic.dat") 55 | extract("../logs/pid-stats-equic-parallel.log", "in/cpu-equic.dat") 56 | 57 | compute("in/cpu-no-equic.dat", "stats/cpu-cdf-no-equic.csv") 58 | compute("in/cpu-equic.dat", "stats/cpu-cdf-equic.csv") 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | -------------------------------------------------------------------------------- /experiments/extract.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from sys import exit 3 | 4 | 5 | # The output here is the input for the statistics computation 6 | OUT_DIR = "in/" 7 | 8 | 9 | def extract_request_time(data, base_name): 10 | 11 | splitted = data.split("=") 12 | 13 | key = splitted[0] 14 | 15 | if key != "RequestDuration": 16 | return 17 | 18 | value = splitted[1][:-1] 19 | 20 | ofilename = OUT_DIR + "duration-" + base_name + ".dat" 21 | 22 | with open(ofilename, "a") as ofile: 23 | ofile.write("{0}\n".format(value)) 24 | 25 | 26 | def extract_requests_per_second(data, base_name): 27 | 28 | splitted = data.split("=") 29 | 30 | key = splitted[0] 31 | 32 | if key != "RequestsPerSecond": 33 | return 34 | 35 | value = splitted[1] 36 | 37 | if int(value) > 0: 38 | ofilename = OUT_DIR + "reqs_per_second-" + base_name + ".dat" 39 | 40 | with open(ofilename, "a") as ofile: 41 | ofile.write("{0}\n".format(value)) 42 | 43 | 44 | def extract_cpu_time(data, base_name): 45 | 46 | splitted = data.split("=") 47 | 48 | key = splitted[0] 49 | 50 | if key != "BlockDuration": 51 | return 52 | 53 | value = splitted[1][:-1] 54 | 55 | if float(value) > 0.0: 56 | ofilename = OUT_DIR + "block_duration-" + base_name + ".dat" 57 | 58 | with open(ofilename, "a") as ofile: 59 | ofile.write("{0}\n".format(float(value))) 60 | 61 | 62 | def extract(log): 63 | 64 | test_type = log.split("-")[-2] # for example: baseline 65 | test_size = log.split("-")[-1][:-4] # for example: 32k 66 | 67 | # Output file base name 68 | base_name = test_type + "-" + test_size 69 | 70 | with open(log) as in_file: 71 | for line in in_file: 72 | 73 | data = line.split() 74 | extract_request_time(data[1], base_name) 75 | extract_requests_per_second(data[1], base_name) 76 | extract_cpu_time(data[1], base_name) # Extracts for Userspace 77 | if len(data) > 6: 78 | extract_cpu_time(data[6], base_name) # Extracts for Kernelspace 79 | 80 | print("[Extracted] Type={0}, Size={1}, To={2}".format( 81 | test_type, test_size, OUT_DIR) 82 | ) 83 | 84 | 85 | def main(): 86 | if not len(argv) > 1: 87 | print("[eQUIC Analysis] Missing log file as input") 88 | exit(1) 89 | 90 | log = argv[1] 91 | 92 | extract(log) 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /experiments/graphs.plt: -------------------------------------------------------------------------------- 1 | # vim: set ft=gnuplot 2 | 3 | 4 | # 5 | # Plots the requests duration 6 | # 7 | set title "Duração de requisições em milissegundos" font ", 24" 8 | set datafile separator "," 9 | set grid y 10 | set key inside right top 11 | 12 | set style data histogram 13 | set style histogram cluster gap 3 14 | set style fill solid 15 | set boxwidth 1.5 16 | 17 | set ylabel "Duração em (ms)" offset 1.5,0 18 | set yrange [0:26] 19 | set xlabel "Tamanho do corpo da resposta" 20 | 21 | set terminal 'png' size 800,500 22 | set output 'out/duration.png' 23 | 24 | plot 'stats/duration-userspace.csv' using 2:xtic(1) title "Espaço de usuário", \ 25 | 'stats/duration-kernel.csv' using 2:xtic(1) title "Espaço de núcleo", \ 26 | 'stats/duration-baseline.csv' using 2:xtic(1) title "Sem controle de quota" 27 | 28 | set title "Requests duration in miliseconds" font ", 24" 29 | set datafile separator "," 30 | set grid y 31 | set key inside right top 32 | 33 | set style data histogram 34 | set style histogram cluster gap 3 35 | set style fill solid 36 | set boxwidth 1.5 37 | 38 | set ylabel "Duration (ms)" offset 1.5,0 39 | set yrange [0:26] 40 | set xlabel "Response body size" 41 | 42 | set terminal 'png' size 800,500 43 | set output 'out/duration-en.png' 44 | 45 | plot 'stats/duration-userspace.csv' using 2:xtic(1) title "User space", \ 46 | 'stats/duration-kernel.csv' using 2:xtic(1) title "Kernel space", \ 47 | 'stats/duration-baseline.csv' using 2:xtic(1) title "No quota control" 48 | 49 | 50 | 51 | 52 | # 53 | # Plots the Blocking duration 54 | # 55 | set title "Tempo de bloqueio em micro-segundos" font ", 24" 56 | 57 | set style data histogram 58 | set style fill solid 59 | set boxwidth 1.5 60 | 61 | set ylabel "Tempo (us)" offset 1.5,0 62 | set yrange [0:12] 63 | set xlabel "Tamanho do corpo da resposta" 64 | 65 | set terminal 'png' size 800,600 66 | set output 'out/block-duration.png' 67 | 68 | plot 'stats/block_duration-parallel_cpu_userspace.csv' using 2:xtic(1) linewidth 3 title "Espaço de usuário", \ 69 | 'stats/block_duration-parallel_cpu_kernel.csv' using 2:xtic(1) linewidth 3 title "Espaço de núcleo" 70 | 71 | set title "Blocking time in microseconds" font ", 24" 72 | 73 | set style data histogram 74 | set style fill solid 75 | set boxwidth 1.5 76 | 77 | set ylabel "Time (us)" offset 1.5,0 78 | set yrange [0:12] 79 | set xlabel "Response body size" 80 | 81 | set terminal 'png' size 800,500 82 | set output 'out/block-duration-en.png' 83 | 84 | plot 'stats/block_duration-parallel_cpu_userspace.csv' using 2:xtic(1) linewidth 3 title "User space", \ 85 | 'stats/block_duration-parallel_cpu_kernel.csv' using 2:xtic(1) linewidth 3 title "Kernel space" 86 | 87 | 88 | # 89 | # Plots requests per second 90 | # 91 | set title "Variação na vazão de pacotes durante um ataque" font ", 24" offset screen 0,-0.03 92 | 93 | set style data lines 94 | set key spacing 1 95 | 96 | set ylabel "Requisições por segundo" offset 1,0 97 | set yrange [0:40] 98 | set xlabel "Número da coleta (1 unidade equivale a 5 segundos)" 99 | 100 | set terminal 'png' size 800,400 101 | set output 'out/requests-per-second.png' 102 | 103 | plot 'in/reqs_per_second-kernel-256k.dat' using 1 linewidth 3 title "Espaço de núcleo" smooth cspline, \ 104 | 'in/reqs_per_second-userspace-256k.dat' using 1 linewidth 3 title "Espaço de usuário" smooth cspline 105 | 106 | set title "Throughput variation on a burst scenario" font ", 24" offset screen 0,-0.03 107 | 108 | set style data lines 109 | set key spacing 1 110 | 111 | set ylabel "Requests per second" font ", 16" offset 1,0 112 | set yrange [0:40] 113 | set xlabel "Sample number (1 unit means 5 seconds)" font ", 16" 114 | 115 | set terminal 'png' size 800,400 116 | set output 'out/requests-per-second-en.png' 117 | 118 | plot 'in/reqs_per_second-kernel-256k.dat' using 1 linewidth 3 title "Kernel space" smooth cspline, \ 119 | 'in/reqs_per_second-userspace-256k.dat' using 1 linewidth 3 title "User space" smooth cspline 120 | 121 | 122 | 123 | # 124 | # Plots the requests per second curve 125 | # 126 | clear 127 | reset 128 | set terminal 'png' size 800,400 129 | set output 'out/requests.png' 130 | set datafile separator "," 131 | 132 | set key inside left top 133 | set key spacing 1 134 | set tmargin 5 135 | 136 | set grid y 137 | set multiplot layout 1,2 138 | 139 | 140 | set style data histogram 141 | set style fill solid 142 | set boxwidth 1 143 | 144 | set ylabel "Requisições por segundo" 145 | set yrange [0:25] 146 | set xlabel " " 147 | 148 | 149 | plot 'stats/reqs_per_second-userspace.csv' using 2:xtic("Vazão Média") title "Espaço de usuário", \ 150 | 'stats/reqs_per_second-kernel.csv' using 2:xtic("Vazão Média") title "Espaço de núcleo" 151 | 152 | 153 | set style data histogram 154 | set style fill solid 155 | set boxwidth 1 156 | 157 | unset ylabel 158 | unset yrange 159 | set format y " " 160 | set y2label "Tempo (us)" offset 1.5,0 161 | set y2range [0:12] 162 | set y2tics 163 | 164 | set xlabel "Tamanho do corpo da resposta (256k)" offset screen -0.25,0 165 | set title "Vazão média de requisições e tempo médio\n de bloqueio por contexto" font ", 24" offset screen -0.25,-0.05 166 | 167 | plot 'stats/block_duration-parallel_cpu_userspace.csv' using 2:xtic("Bloqueio"), \ 168 | 'stats/block_duration-parallel_cpu_kernel.csv' using 2:xtic("Bloqueio") 169 | 170 | unset multiplot 171 | 172 | clear 173 | reset 174 | set terminal 'png' size 800,400 175 | set output 'out/requests-en.png' 176 | set datafile separator "," 177 | 178 | set key inside left top 179 | set key spacing 1 180 | set tmargin 5 181 | 182 | set grid y 183 | set multiplot layout 1,2 184 | 185 | 186 | set style data histogram 187 | set style fill solid 188 | set boxwidth 1 189 | 190 | set ylabel "Requests per second" font ", 16" 191 | set yrange [0:25] 192 | set xlabel " " 193 | 194 | 195 | plot 'stats/reqs_per_second-userspace.csv' using 2:xtic("Average") title "User space", \ 196 | 'stats/reqs_per_second-kernel.csv' using 2:xtic("throughput") title "Kernel space" 197 | 198 | 199 | set style data histogram 200 | set style fill solid 201 | set boxwidth 1 202 | 203 | unset ylabel 204 | unset yrange 205 | set format y " " 206 | set y2label "Time (us)" offset 1.5,0 207 | set y2range [0:12] 208 | set y2tics 209 | 210 | set xlabel "Response body size (256k)" offset screen -0.25,0 211 | set title "Averages of request throughput\nand blocking time" font ", 24" offset screen -0.25,-0.05 212 | 213 | plot 'stats/block_duration-parallel_cpu_userspace.csv' using 2:xtic("Blocking"), \ 214 | 'stats/block_duration-parallel_cpu_kernel.csv' using 2:xtic("Blocking") 215 | 216 | unset multiplot 217 | 218 | 219 | 220 | 221 | # 222 | # Plots CPU data 223 | # 224 | reset 225 | set datafile separator ',' 226 | 227 | set grid 228 | set key right bottom 229 | set xlabel "Uso de CPU (%)" 230 | set ylabel "Proporção (CDF)" 231 | set yrange [0:1] 232 | 233 | set terminal 'png' size 500,500 234 | set output "out/cpu-cdf.png" 235 | 236 | set title "Proporção de uso de CPU\ndurante experimento" font ", 24" 237 | 238 | plot "stats/cpu-cdf-no-equic.csv" using 1:2 title "Sem eQUIC" linewidth 3 with linespoints, \ 239 | "stats/cpu-cdf-equic.csv" using 1:2 title "Com eQUIC" linewidth 3 with linespoints 240 | 241 | reset 242 | set datafile separator ',' 243 | 244 | set grid 245 | set key right bottom 246 | set xlabel "CPU Usage (%)" font ", 16" 247 | set ylabel "Proportion (CDF)" font ", 16" 248 | set yrange [0:1] 249 | 250 | set terminal 'png' size 500,500 251 | set output "out/cpu-cdf-en.png" 252 | 253 | set title "CPU usage proportion \nduring experiment" font ", 24" 254 | 255 | plot "stats/cpu-cdf-no-equic.csv" using 1:2 title "No eQUIC Gateway" linewidth 3 with linespoints, \ 256 | "stats/cpu-cdf-equic.csv" using 1:2 title "With eQUIC Gateway" linewidth 3 with linespoints 257 | 258 | 259 | 260 | # 261 | # Plots Memory data 262 | # 263 | reset 264 | set datafile separator ',' 265 | 266 | set grid 267 | set key right bottom 268 | set xlabel "Uso de memória (%)" 269 | set ylabel "Proporção (CDF)" 270 | set yrange [0:1] 271 | 272 | set terminal 'png' size 500,500 273 | set output "out/mem-cdf.png" 274 | 275 | set title "Proporção de uso de Memória\ndurante experimento" font ", 24" 276 | 277 | plot "stats/mem-cdf-no-equic.csv" using 1:2 title "Sem eQUIC" linewidth 3 with linespoints, \ 278 | "stats/mem-cdf-equic.csv" using 1:2 title "Com eQUIC" linewidth 3 with linespoints 279 | 280 | reset 281 | set datafile separator ',' 282 | 283 | set grid 284 | set key right bottom 285 | set xlabel "Memory usage (%)" font ", 16" 286 | set ylabel "Proportion (CDF)" font ", 16" 287 | set yrange [0:1] 288 | 289 | set terminal 'png' size 500,500 290 | set output "out/mem-cdf-en.png" 291 | 292 | set title "Proportion of memory usage \nduring experiment" font ", 24" 293 | 294 | plot "stats/mem-cdf-no-equic.csv" using 1:2 title "No eQUIC Gateway" linewidth 3 with linespoints, \ 295 | "stats/mem-cdf-equic.csv" using 1:2 title "With eQUIC Gateway" linewidth 3 with linespoints 296 | -------------------------------------------------------------------------------- /experiments/in/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantuza/equic/a64166b49780851a6db3b163e608d6058c722387/experiments/in/.gitkeep -------------------------------------------------------------------------------- /experiments/mem.py: -------------------------------------------------------------------------------- 1 | from statistics import NormalDist 2 | from statistics import mean 3 | from statistics import stdev 4 | 5 | 6 | 7 | def extract(logfile, output): 8 | """ Extracts Memory data from log file and outputs it to input directory """ 9 | 10 | ofile = open(output, "w") 11 | 12 | with open(logfile) as infile: 13 | 14 | # Skip first two lines 15 | infile.readline() 16 | infile.readline() 17 | infile.readline() 18 | 19 | for line in infile: 20 | 21 | data = line.split() 22 | 23 | ofile.write("{0}\n".format(data[13])) 24 | 25 | # skips title and blank line 26 | infile.readline() 27 | infile.readline() 28 | 29 | ofile.close() 30 | 31 | 32 | def compute(infile, outfile): 33 | 34 | ofile = open(outfile, "w") 35 | 36 | mem = [] 37 | with open(infile) as indata: 38 | 39 | for line in indata: 40 | mem.append(float(line)) 41 | 42 | mu = mean(mem) 43 | sigma = stdev(mem) 44 | 45 | dist = NormalDist(mu=mu, sigma=sigma) 46 | 47 | for x in sorted(mem): 48 | ofile.write("{0}, {1}\n".format(x, dist.cdf(x))) 49 | 50 | ofile.close() 51 | 52 | def main(): 53 | 54 | extract("../logs/pid-stats-no-equic-parallel.log", "in/mem-no-equic.dat") 55 | extract("../logs/pid-stats-equic-parallel.log", "in/mem-equic.dat") 56 | 57 | compute("in/cpu-no-equic.dat", "stats/mem-cdf-no-equic.csv") 58 | compute("in/cpu-equic.dat", "stats/mem-cdf-equic.csv") 59 | 60 | 61 | if __name__ == "__main__": 62 | main() 63 | -------------------------------------------------------------------------------- /experiments/out/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantuza/equic/a64166b49780851a6db3b163e608d6058c722387/experiments/out/.gitkeep -------------------------------------------------------------------------------- /experiments/stats/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantuza/equic/a64166b49780851a6db3b163e608d6058c722387/experiments/stats/.gitkeep -------------------------------------------------------------------------------- /http_server.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */ 2 | /* 3 | * http_server.c -- A simple HTTP/QUIC server 4 | * 5 | * It serves up files from the filesystem. 6 | */ 7 | 8 | 9 | 10 | 11 | /* 12 | * This code is a modified version of the original http_server.c 13 | * from LiteSpeed QUIC library for research purpose. 14 | * 15 | * This implementation adds eQUIC library to load a eBPF program on 16 | * the XDP kernel hook. On every new QUIC connection setup and QUIC 17 | * connection teardown this program updates a eBPF kernel Map. 18 | * 19 | * Take a look at the code in the following functions: 20 | * . struct server_ctx 21 | * . debug_connection 22 | * . echo_server_on_new_conn 23 | * . echo_server_on_conn_closed 24 | * . main 25 | * 26 | * Author: Gustavo Pantuza 27 | */ 28 | 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #ifndef WIN32 41 | #include 42 | #include 43 | #include 44 | #else 45 | #include "vc_compat.h" 46 | #include "getopt.h" 47 | #endif 48 | 49 | #include 50 | 51 | #include 52 | 53 | #include "lsquic.h" 54 | #include "../src/liblsquic/lsquic_hash.h" 55 | #include "lsxpack_header.h" 56 | #include "test_config.h" 57 | #include "test_common.h" 58 | #include "test_cert.h" 59 | #include "prog.h" 60 | 61 | #if HAVE_REGEX 62 | #ifndef WIN32 63 | #include 64 | #else 65 | #include 66 | #endif 67 | #endif 68 | 69 | #include "../src/liblsquic/lsquic_logger.h" 70 | #include "../src/liblsquic/lsquic_int_types.h" 71 | #include "../src/liblsquic/lsquic_util.h" 72 | 73 | // eQUIC 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include "/src/equic/src/equic_user.h" 79 | 80 | #if HAVE_REGEX 81 | static const char on_being_idle[] = 82 | "ON BEING IDLE.\n" 83 | "\n" 84 | "Now, this is a subject on which I flatter myself I really am _au fait_.\n" 85 | "The gentleman who, when I was young, bathed me at wisdom's font for nine\n" 86 | "guineas a term--no extras--used to say he never knew a boy who could\n" 87 | "do less work in more time; and I remember my poor grandmother once\n" 88 | "incidentally observing, in the course of an instruction upon the use\n" 89 | "of the Prayer-book, that it was highly improbable that I should ever do\n" 90 | "much that I ought not to do, but that she felt convinced beyond a doubt\n" 91 | "that I should leave undone pretty well everything that I ought to do.\n" 92 | "\n" 93 | "I am afraid I have somewhat belied half the dear old lady's prophecy.\n" 94 | "Heaven help me! I have done a good many things that I ought not to have\n" 95 | "done, in spite of my laziness. But I have fully confirmed the accuracy\n" 96 | "of her judgment so far as neglecting much that I ought not to have\n" 97 | "neglected is concerned. Idling always has been my strong point. I take\n" 98 | "no credit to myself in the matter--it is a gift. Few possess it. There\n" 99 | "are plenty of lazy people and plenty of slow-coaches, but a genuine\n" 100 | "idler is a rarity. He is not a man who slouches about with his hands in\n" 101 | "his pockets. On the contrary, his most startling characteristic is that\n" 102 | "he is always intensely busy.\n" 103 | "\n" 104 | "It is impossible to enjoy idling thoroughly unless one has plenty of\n" 105 | "work to do. There is no fun in doing nothing when you have nothing to\n" 106 | "do. Wasting time is merely an occupation then, and a most exhausting\n" 107 | "one. Idleness, like kisses, to be sweet must be stolen.\n" 108 | "\n" 109 | "Many years ago, when I was a young man, I was taken very ill--I never\n" 110 | "could see myself that much was the matter with me, except that I had\n" 111 | "a beastly cold. But I suppose it was something very serious, for the\n" 112 | "doctor said that I ought to have come to him a month before, and that\n" 113 | "if it (whatever it was) had gone on for another week he would not have\n" 114 | "answered for the consequences. It is an extraordinary thing, but I\n" 115 | "never knew a doctor called into any case yet but what it transpired\n" 116 | "that another day's delay would have rendered cure hopeless. Our medical\n" 117 | "guide, philosopher, and friend is like the hero in a melodrama--he\n" 118 | "always comes upon the scene just, and only just, in the nick of time. It\n" 119 | "is Providence, that is what it is.\n" 120 | "\n" 121 | "Well, as I was saying, I was very ill and was ordered to Buxton for a\n" 122 | "month, with strict injunctions to do nothing whatever all the while\n" 123 | "that I was there. \"Rest is what you require,\" said the doctor, \"perfect\n" 124 | "rest.\"\n" 125 | "\n" 126 | "It seemed a delightful prospect. \"This man evidently understands my\n" 127 | "complaint,\" said I, and I pictured to myself a glorious time--a four\n" 128 | "weeks' _dolce far niente_ with a dash of illness in it. Not too much\n" 129 | "illness, but just illness enough--just sufficient to give it the flavor\n" 130 | "of suffering and make it poetical. I should get up late, sip chocolate,\n" 131 | "and have my breakfast in slippers and a dressing-gown. I should lie out\n" 132 | "in the garden in a hammock and read sentimental novels with a melancholy\n" 133 | "ending, until the books should fall from my listless hand, and I should\n" 134 | "recline there, dreamily gazing into the deep blue of the firmament,\n" 135 | "watching the fleecy clouds floating like white-sailed ships across\n" 136 | "its depths, and listening to the joyous song of the birds and the low\n" 137 | "rustling of the trees. Or, on becoming too weak to go out of doors,\n" 138 | "I should sit propped up with pillows at the open window of the\n" 139 | "ground-floor front, and look wasted and interesting, so that all the\n" 140 | "pretty girls would sigh as they passed by.\n" 141 | "\n" 142 | "And twice a day I should go down in a Bath chair to the Colonnade to\n" 143 | "drink the waters. Oh, those waters! I knew nothing about them then,\n" 144 | "and was rather taken with the idea. \"Drinking the waters\" sounded\n" 145 | "fashionable and Queen Anne-fied, and I thought I should like them. But,\n" 146 | "ugh! after the first three or four mornings! Sam Weller's description of\n" 147 | "them as \"having a taste of warm flat-irons\" conveys only a faint idea of\n" 148 | "their hideous nauseousness. If anything could make a sick man get well\n" 149 | "quickly, it would be the knowledge that he must drink a glassful of them\n" 150 | "every day until he was recovered. I drank them neat for six consecutive\n" 151 | "days, and they nearly killed me; but after then I adopted the plan of\n" 152 | "taking a stiff glass of brandy-and-water immediately on the top of them,\n" 153 | "and found much relief thereby. I have been informed since, by various\n" 154 | "eminent medical gentlemen, that the alcohol must have entirely\n" 155 | "counteracted the effects of the chalybeate properties contained in the\n" 156 | "water. I am glad I was lucky enough to hit upon the right thing.\n" 157 | "\n" 158 | "But \"drinking the waters\" was only a small portion of the torture I\n" 159 | "experienced during that memorable month--a month which was, without\n" 160 | "exception, the most miserable I have ever spent. During the best part of\n" 161 | "it I religiously followed the doctor's mandate and did nothing whatever,\n" 162 | "except moon about the house and garden and go out for two hours a day in\n" 163 | "a Bath chair. That did break the monotony to a certain extent. There is\n" 164 | "more excitement about Bath-chairing--especially if you are not used to\n" 165 | "the exhilarating exercise--than might appear to the casual observer. A\n" 166 | "sense of danger, such as a mere outsider might not understand, is ever\n" 167 | "present to the mind of the occupant. He feels convinced every minute\n" 168 | "that the whole concern is going over, a conviction which becomes\n" 169 | "especially lively whenever a ditch or a stretch of newly macadamized\n" 170 | "road comes in sight. Every vehicle that passes he expects is going to\n" 171 | "run into him; and he never finds himself ascending or descending a\n" 172 | "hill without immediately beginning to speculate upon his chances,\n" 173 | "supposing--as seems extremely probable--that the weak-kneed controller\n" 174 | "of his destiny should let go.\n" 175 | "\n" 176 | "But even this diversion failed to enliven after awhile, and the _ennui_\n" 177 | "became perfectly unbearable. I felt my mind giving way under it. It is\n" 178 | "not a strong mind, and I thought it would be unwise to tax it too far.\n" 179 | "So somewhere about the twentieth morning I got up early, had a good\n" 180 | "breakfast, and walked straight off to Hayfield, at the foot of the\n" 181 | "Kinder Scout--a pleasant, busy little town, reached through a lovely\n" 182 | "valley, and with two sweetly pretty women in it. At least they were\n" 183 | "sweetly pretty then; one passed me on the bridge and, I think, smiled;\n" 184 | "and the other was standing at an open door, making an unremunerative\n" 185 | "investment of kisses upon a red-faced baby. But it is years ago, and I\n" 186 | "dare say they have both grown stout and snappish since that time.\n" 187 | "Coming back, I saw an old man breaking stones, and it roused such strong\n" 188 | "longing in me to use my arms that I offered him a drink to let me take\n" 189 | "his place. He was a kindly old man and he humored me. I went for those\n" 190 | "stones with the accumulated energy of three weeks, and did more work in\n" 191 | "half an hour than he had done all day. But it did not make him jealous.\n" 192 | "\n" 193 | "Having taken the plunge, I went further and further into dissipation,\n" 194 | "going out for a long walk every morning and listening to the band in\n" 195 | "the pavilion every evening. But the days still passed slowly\n" 196 | "notwithstanding, and I was heartily glad when the last one came and I\n" 197 | "was being whirled away from gouty, consumptive Buxton to London with its\n" 198 | "stern work and life. I looked out of the carriage as we rushed through\n" 199 | "Hendon in the evening. The lurid glare overhanging the mighty city\n" 200 | "seemed to warm my heart, and when, later on, my cab rattled out of St.\n" 201 | "Pancras' station, the old familiar roar that came swelling up around me\n" 202 | "sounded the sweetest music I had heard for many a long day.\n" 203 | "\n" 204 | "I certainly did not enjoy that month's idling. I like idling when I\n" 205 | "ought not to be idling; not when it is the only thing I have to do. That\n" 206 | "is my pig-headed nature. The time when I like best to stand with my\n" 207 | "back to the fire, calculating how much I owe, is when my desk is heaped\n" 208 | "highest with letters that must be answered by the next post. When I like\n" 209 | "to dawdle longest over my dinner is when I have a heavy evening's work\n" 210 | "before me. And if, for some urgent reason, I ought to be up particularly\n" 211 | "early in the morning, it is then, more than at any other time, that I\n" 212 | "love to lie an extra half-hour in bed.\n" 213 | "\n" 214 | "Ah! how delicious it is to turn over and go to sleep again: \"just for\n" 215 | "five minutes.\" Is there any human being, I wonder, besides the hero of\n" 216 | "a Sunday-school \"tale for boys,\" who ever gets up willingly? There\n" 217 | "are some men to whom getting up at the proper time is an utter\n" 218 | "impossibility. If eight o'clock happens to be the time that they should\n" 219 | "turn out, then they lie till half-past. If circumstances change and\n" 220 | "half-past eight becomes early enough for them, then it is nine before\n" 221 | "they can rise. They are like the statesman of whom it was said that he\n" 222 | "was always punctually half an hour late. They try all manner of schemes.\n" 223 | "They buy alarm-clocks (artful contrivances that go off at the wrong time\n" 224 | "and alarm the wrong people). They tell Sarah Jane to knock at the door\n" 225 | "and call them, and Sarah Jane does knock at the door and does call them,\n" 226 | "and they grunt back \"awri\" and then go comfortably to sleep again. I\n" 227 | "knew one man who would actually get out and have a cold bath; and even\n" 228 | "that was of no use, for afterward he would jump into bed again to warm\n" 229 | "himself.\n" 230 | "\n" 231 | "I think myself that I could keep out of bed all right if I once got\n" 232 | "out. It is the wrenching away of the head from the pillow that I find so\n" 233 | "hard, and no amount of over-night determination makes it easier. I say\n" 234 | "to myself, after having wasted the whole evening, \"Well, I won't do\n" 235 | "any more work to-night; I'll get up early to-morrow morning;\" and I am\n" 236 | "thoroughly resolved to do so--then. In the morning, however, I feel less\n" 237 | "enthusiastic about the idea, and reflect that it would have been much\n" 238 | "better if I had stopped up last night. And then there is the trouble of\n" 239 | "dressing, and the more one thinks about that the more one wants to put\n" 240 | "it off.\n" 241 | "\n" 242 | "It is a strange thing this bed, this mimic grave, where we stretch our\n" 243 | "tired limbs and sink away so quietly into the silence and rest. \"O bed,\n" 244 | "O bed, delicious bed, that heaven on earth to the weary head,\" as sang\n" 245 | "poor Hood, you are a kind old nurse to us fretful boys and girls. Clever\n" 246 | "and foolish, naughty and good, you take us all in your motherly lap and\n" 247 | "hush our wayward crying. The strong man full of care--the sick man\n" 248 | "full of pain--the little maiden sobbing for her faithless lover--like\n" 249 | "children we lay our aching heads on your white bosom, and you gently\n" 250 | "soothe us off to by-by.\n" 251 | "\n" 252 | "Our trouble is sore indeed when you turn away and will not comfort us.\n" 253 | "How long the dawn seems coming when we cannot sleep! Oh! those hideous\n" 254 | "nights when we toss and turn in fever and pain, when we lie, like living\n" 255 | "men among the dead, staring out into the dark hours that drift so slowly\n" 256 | "between us and the light. And oh! those still more hideous nights when\n" 257 | "we sit by another in pain, when the low fire startles us every now and\n" 258 | "then with a falling cinder, and the tick of the clock seems a hammer\n" 259 | "beating out the life that we are watching.\n" 260 | "\n" 261 | "But enough of beds and bedrooms. I have kept to them too long, even for\n" 262 | "an idle fellow. Let us come out and have a smoke. That wastes time just\n" 263 | "as well and does not look so bad. Tobacco has been a blessing to us\n" 264 | "idlers. What the civil-service clerk before Sir Walter's time found\n" 265 | "to occupy their minds with it is hard to imagine. I attribute the\n" 266 | "quarrelsome nature of the Middle Ages young men entirely to the want of\n" 267 | "the soothing weed. They had no work to do and could not smoke, and\n" 268 | "the consequence was they were forever fighting and rowing. If, by any\n" 269 | "extraordinary chance, there was no war going, then they got up a deadly\n" 270 | "family feud with the next-door neighbor, and if, in spite of this, they\n" 271 | "still had a few spare moments on their hands, they occupied them with\n" 272 | "discussions as to whose sweetheart was the best looking, the arguments\n" 273 | "employed on both sides being battle-axes, clubs, etc. Questions of taste\n" 274 | "were soon decided in those days. When a twelfth-century youth fell in\n" 275 | "love he did not take three paces backward, gaze into her eyes, and tell\n" 276 | "her she was too beautiful to live. He said he would step outside and see\n" 277 | "about it. And if, when he got out, he met a man and broke his head--the\n" 278 | "other man's head, I mean--then that proved that his--the first\n" 279 | "fellow's--girl was a pretty girl. But if the other fellow broke _his_\n" 280 | "head--not his own, you know, but the other fellow's--the other fellow\n" 281 | "to the second fellow, that is, because of course the other fellow would\n" 282 | "only be the other fellow to him, not the first fellow who--well, if he\n" 283 | "broke his head, then _his_ girl--not the other fellow's, but the fellow\n" 284 | "who _was_ the--Look here, if A broke B's head, then A's girl was a\n" 285 | "pretty girl; but if B broke A's head, then A's girl wasn't a pretty\n" 286 | "girl, but B's girl was. That was their method of conducting art\n" 287 | "criticism.\n" 288 | "\n" 289 | "Nowadays we light a pipe and let the girls fight it out among\n" 290 | "themselves.\n" 291 | "\n" 292 | "They do it very well. They are getting to do all our work. They are\n" 293 | "doctors, and barristers, and artists. They manage theaters, and promote\n" 294 | "swindles, and edit newspapers. I am looking forward to the time when we\n" 295 | "men shall have nothing to do but lie in bed till twelve, read two novels\n" 296 | "a day, have nice little five-o'clock teas all to ourselves, and tax\n" 297 | "our brains with nothing more trying than discussions upon the latest\n" 298 | "patterns in trousers and arguments as to what Mr. Jones' coat was\n" 299 | "made of and whether it fitted him. It is a glorious prospect--for idle\n" 300 | "fellows.\n" 301 | "\n\n\n" 302 | ; 303 | static const size_t IDLE_SIZE = sizeof(on_being_idle) - 1; 304 | #endif 305 | 306 | /* This is the "LSWS" mode: first write is performed immediately, outside 307 | * of the on_write() callback. This makes it possible to play with buffered 308 | * packet queues. 309 | */ 310 | static int s_immediate_write; 311 | 312 | /* Use preadv(2) in conjuction with lsquic_stream_pwritev() to reduce 313 | * number of system calls required to read from disk. The actual value 314 | * specifies maximum write size. A negative value indicates always to use 315 | * the remaining file size. 316 | */ 317 | static ssize_t s_pwritev; 318 | 319 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 320 | #define V(v) (v), strlen(v) 321 | 322 | struct lsquic_conn_ctx; 323 | 324 | static void interop_server_hset_destroy (void *); 325 | 326 | 327 | struct server_ctx { 328 | struct lsquic_conn_ctx *conn_h; 329 | lsquic_engine_t *engine; 330 | const char *document_root; 331 | const char *push_path; 332 | struct sport_head sports; 333 | struct prog *prog; 334 | unsigned max_conn; 335 | unsigned n_conn; 336 | unsigned n_current_conns; 337 | unsigned delay_resp_sec; 338 | 339 | // eQUIC 340 | equic_t *equic; 341 | unsigned int has_eBPF; 342 | struct timespec begin_t; 343 | struct timespec end_t; 344 | }; 345 | 346 | struct lsquic_conn_ctx { 347 | lsquic_conn_t *conn; 348 | struct server_ctx *server_ctx; 349 | enum { 350 | RECEIVED_GOAWAY = 1 << 0, 351 | } flags; 352 | }; 353 | 354 | // ----------------- USER SPACE CONNECTION QUOTA IMPLEMENTATION --------------- 355 | 356 | /** 357 | * Prints a 4-tuple with Source IPv4 address and port and Destination 358 | * IPv4 address and port 359 | */ 360 | static void 361 | debug_connection (const struct sockaddr *local, const struct sockaddr *peer, char *action) 362 | { 363 | /* Gets server ip address and port */ 364 | struct sockaddr_in *local_addr_in = (struct sockaddr_in *)local; 365 | char *local_addr_str = inet_ntoa(local_addr_in->sin_addr); 366 | uint16_t local_port; 367 | local_port = htons(local_addr_in->sin_port); 368 | 369 | /* Gets client ip address and port */ 370 | struct sockaddr_in *peer_addr_in = (struct sockaddr_in *)peer; 371 | char *peer_addr_str = inet_ntoa(peer_addr_in->sin_addr); 372 | uint16_t peer_port; 373 | peer_port = htons(peer_addr_in->sin_port); 374 | 375 | printf( 376 | "[eQUIC] Action=%s, Client=%s, SourcePort=%d, Server=%s, DestinationPort=%d\n", 377 | action, peer_addr_str, peer_port, local_addr_str, local_port 378 | ); 379 | } 380 | 381 | 382 | /** 383 | * Calculates elapsed miliseconds between two given times. 384 | * Returns a value of type double 385 | */ 386 | static double 387 | elapsed_time_ms (struct timespec *begin, struct timespec *end) 388 | { 389 | // First convert all times to nanoseconds 390 | long begin_in_ns = begin->tv_sec * 1.0e9 + begin->tv_nsec; 391 | long end_in_ns = end->tv_sec * 1.0e9 + end->tv_nsec; 392 | 393 | // Calculate the elapsed time and convert to miliseconds 394 | double elapsed_ms = (end_in_ns - begin_in_ns) / 1.0e6; 395 | 396 | printf( 397 | "[eQUIC] RequestDuration=%.3f, Begin=%.3ld, End=%.3ld\n", 398 | elapsed_ms, begin_in_ns, end_in_ns 399 | ); 400 | 401 | return elapsed_ms; 402 | } 403 | 404 | 405 | pthread_t tid; 406 | pthread_mutex_t lock; 407 | int requests_per_second; 408 | #define COUNTER_INTERVAL 5 409 | 410 | void *request_counter (void *arg) 411 | { 412 | while (1) { 413 | 414 | sleep(COUNTER_INTERVAL); 415 | pthread_mutex_lock(&lock); 416 | int n_reqs = (int) requests_per_second / COUNTER_INTERVAL; 417 | printf("[eQUIC] RequestsPerSecond=%d\n", n_reqs); 418 | requests_per_second = 0; 419 | pthread_mutex_unlock(&lock); 420 | } 421 | } 422 | 423 | void inc_request_counter () 424 | { 425 | pthread_mutex_lock(&lock); 426 | requests_per_second += 1; 427 | pthread_mutex_unlock(&lock); 428 | } 429 | 430 | 431 | // Array with quota limits for a Type C local network address space 432 | // Keys are the last octet of a given IPv4 address 433 | int Quotas[256]; 434 | 435 | // Mutex to control Quotas modifications 436 | pthread_mutex_t QuotasLock; 437 | 438 | // Upper bound limit for clients connection quota 439 | #define QUOTA_LIMIT 5 440 | 441 | // Upper bound limit for rate limiting clients 442 | #define RATE_LIMIT 20 443 | 444 | int extract_last_octet(char *addr) 445 | { 446 | char octet[4]; 447 | int index = 0; 448 | size_t n = 0; 449 | 450 | // Find the last octet start index 451 | for (int i=strlen(addr); i > 0; i--) { 452 | 453 | if (addr[i] == '.') { 454 | index = i + 1; 455 | break; 456 | } 457 | n++; 458 | } 459 | 460 | strncpy(octet, addr + index, n); 461 | 462 | return atoi(octet); 463 | } 464 | 465 | void inc_quota(const struct sockaddr *peer) 466 | { 467 | struct sockaddr_in *client_addr_in = (struct sockaddr_in *)peer; 468 | char *addr = inet_ntoa(client_addr_in->sin_addr); 469 | int octet = extract_last_octet(addr); 470 | 471 | pthread_mutex_lock(&QuotasLock); 472 | Quotas[octet] += 1; 473 | printf("[eQUIC] Action=Increment, Type=UserSpace, Key=%s, Value=%d\n", addr, Quotas[octet]); 474 | pthread_mutex_unlock(&QuotasLock); 475 | } 476 | 477 | void dec_quota(const struct sockaddr *peer) 478 | { 479 | struct sockaddr_in *client_addr_in = (struct sockaddr_in *)peer; 480 | char *addr = inet_ntoa(client_addr_in->sin_addr); 481 | int octet = extract_last_octet(addr); 482 | 483 | pthread_mutex_lock(&QuotasLock); 484 | if (Quotas[octet] > 0) { 485 | Quotas[octet] -= 1; 486 | } 487 | printf("[eQUIC] Action=Decrement, Type=UserSpace, Key=%s, Value=%d\n", addr, Quotas[octet]); 488 | pthread_mutex_unlock(&QuotasLock); 489 | 490 | } 491 | 492 | int reached_quota_limit(const struct sockaddr *peer) 493 | { 494 | struct sockaddr_in *client_addr_in = (struct sockaddr_in *)peer; 495 | char *addr = inet_ntoa(client_addr_in->sin_addr); 496 | int octet = extract_last_octet(addr); 497 | 498 | pthread_mutex_lock(&QuotasLock); 499 | if (Quotas[octet] >= QUOTA_LIMIT) { 500 | printf("[eQUIC] Action=Exceeded, Type=UserSpace, Key=%s, Value=%d\n", addr, Quotas[octet]); 501 | pthread_mutex_unlock(&QuotasLock); 502 | 503 | return 1; 504 | } 505 | 506 | printf("[eQUIC] Action=NotExceeded, Type=UserSpace, Key=%s, Value=%d\n", addr, Quotas[octet]); 507 | pthread_mutex_unlock(&QuotasLock); 508 | 509 | return 0; 510 | } 511 | 512 | void calculate_quota_block_time(struct timespec *begin, struct timespec *end) 513 | { 514 | // First convert all times to nanoseconds 515 | long begin_in_ns = begin->tv_sec * 1.0e9 + begin->tv_nsec; 516 | long end_in_ns = end->tv_sec * 1.0e9 + end->tv_nsec; 517 | 518 | // Calculate the elapsed time and convert to microseconds 519 | double elapsed_us = (end_in_ns - begin_in_ns) / 1.0e3; 520 | 521 | printf( 522 | "[eQUIC] BlockDuration=%.3f, Begin=%.3ld, End=%.3ld\n", 523 | elapsed_us, begin_in_ns, end_in_ns 524 | ); 525 | } 526 | // ------------ END OF USER SPACE CONNECTION QUOTA IMPLEMENTATION ------------- 527 | 528 | 529 | static lsquic_conn_ctx_t * 530 | http_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 531 | { 532 | struct server_ctx *server_ctx = stream_if_ctx; 533 | const char *sni; 534 | 535 | sni = lsquic_conn_get_sni(conn); 536 | LSQ_DEBUG("new connection, SNI: %s", sni ? sni : ""); 537 | 538 | lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h)); 539 | conn_h->conn = conn; 540 | conn_h->server_ctx = server_ctx; 541 | server_ctx->conn_h = conn_h; 542 | ++server_ctx->n_current_conns; 543 | 544 | // eQUIC 545 | /* Read sockaddr from connection for client and server */ 546 | const struct sockaddr *local; 547 | const struct sockaddr *peer; 548 | lsquic_conn_get_sockaddr(conn, &local, &peer); 549 | 550 | if(server_ctx->has_eBPF) { 551 | /* 552 | * Increment eQUIC Kernel counters Map key for the given peer (client) 553 | */ 554 | equic_inc_counter(server_ctx->equic, peer); 555 | 556 | } else { 557 | if (reached_quota_limit(peer)) { 558 | lsquic_conn_close(conn); 559 | 560 | struct timespec quota_end_time; 561 | clock_gettime(CLOCK_MONOTONIC, "a_end_time); 562 | calculate_quota_block_time(&server_ctx->begin_t, "a_end_time); 563 | 564 | return conn_h; 565 | } 566 | 567 | inc_quota(peer); 568 | } 569 | 570 | inc_request_counter(); 571 | debug_connection(local, peer, "ConnectionSetup"); 572 | 573 | return conn_h; 574 | } 575 | 576 | 577 | static void 578 | http_server_on_goaway (lsquic_conn_t *conn) 579 | { 580 | lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 581 | conn_h->flags |= RECEIVED_GOAWAY; 582 | LSQ_INFO("received GOAWAY"); 583 | } 584 | 585 | 586 | static void 587 | http_server_on_conn_closed (lsquic_conn_t *conn) 588 | { 589 | static int stopped; 590 | lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 591 | LSQ_INFO("Connection closed"); 592 | --conn_h->server_ctx->n_current_conns; 593 | if ((conn_h->server_ctx->prog->prog_flags & PROG_FLAG_COOLDOWN) 594 | && 0 == conn_h->server_ctx->n_current_conns) 595 | { 596 | if (!stopped) 597 | { 598 | stopped = 1; 599 | prog_stop(conn_h->server_ctx->prog); 600 | } 601 | } 602 | if (conn_h->server_ctx->max_conn > 0) 603 | { 604 | ++conn_h->server_ctx->n_conn; 605 | LSQ_NOTICE("Connection closed, remaining: %d", 606 | conn_h->server_ctx->max_conn - conn_h->server_ctx->n_conn); 607 | if (conn_h->server_ctx->n_conn >= conn_h->server_ctx->max_conn) 608 | { 609 | if (!stopped) 610 | { 611 | stopped = 1; 612 | prog_stop(conn_h->server_ctx->prog); 613 | } 614 | } 615 | } 616 | 617 | // eQUIC 618 | /* Read sockaddr from connection for client and server */ 619 | const struct sockaddr *local; 620 | const struct sockaddr *peer; 621 | lsquic_conn_get_sockaddr(conn, &local, &peer); 622 | 623 | debug_connection(local, peer, "ConnectionClose"); 624 | 625 | if (conn_h->server_ctx->has_eBPF) { 626 | /* 627 | * Decrement eQUIC Kernel counters Map key for the give peer (client) 628 | */ 629 | equic_dec_counter(conn_h->server_ctx->equic, peer); 630 | 631 | } else { 632 | dec_quota(peer); 633 | } 634 | 635 | /* Reads cpu time at connection end */ 636 | clock_gettime(CLOCK_MONOTONIC, &conn_h->server_ctx->end_t); 637 | elapsed_time_ms(&conn_h->server_ctx->begin_t, &conn_h->server_ctx->end_t); 638 | 639 | 640 | /* No provision is made to stop HTTP server */ 641 | lsquic_conn_set_ctx(conn, NULL); 642 | free(conn_h); 643 | } 644 | 645 | 646 | struct resp 647 | { 648 | const char *buf; 649 | size_t sz; 650 | size_t off; 651 | }; 652 | 653 | 654 | struct index_html_ctx 655 | { 656 | struct resp resp; 657 | }; 658 | 659 | 660 | struct ver_head_ctx 661 | { 662 | struct resp resp; 663 | unsigned char *req_body; 664 | size_t req_sz; /* Expect it to be the same as qif_sz */ 665 | }; 666 | 667 | 668 | struct md5sum_ctx 669 | { 670 | char resp_buf[0x100]; 671 | MD5_CTX md5ctx; 672 | struct resp resp; 673 | int done; 674 | }; 675 | 676 | 677 | struct req 678 | { 679 | enum method { 680 | UNSET, GET, POST, UNSUPPORTED, 681 | } method; 682 | enum { 683 | HAVE_XHDR = 1 << 0, 684 | } flags; 685 | enum { 686 | PH_AUTHORITY = 1 << 0, 687 | PH_METHOD = 1 << 1, 688 | PH_PATH = 1 << 2, 689 | } pseudo_headers; 690 | char *path; 691 | char *method_str; 692 | char *authority_str; 693 | char *qif_str; 694 | size_t qif_sz; 695 | struct lsxpack_header 696 | xhdr; 697 | size_t decode_off; 698 | char decode_buf[MIN(LSXPACK_MAX_STRLEN + 1, 64 * 1024)]; 699 | }; 700 | 701 | 702 | struct interop_push_path 703 | { 704 | STAILQ_ENTRY(interop_push_path) next; 705 | char path[0]; 706 | }; 707 | 708 | 709 | struct gen_file_ctx 710 | { 711 | STAILQ_HEAD(, interop_push_path) push_paths; 712 | size_t remain; 713 | unsigned idle_off; 714 | }; 715 | 716 | 717 | struct lsquic_stream_ctx { 718 | lsquic_stream_t *stream; 719 | struct server_ctx *server_ctx; 720 | FILE *req_fh; 721 | char *req_buf; 722 | char *req_filename; 723 | char *req_path; 724 | size_t req_sz; 725 | enum { 726 | SH_HEADERS_SENT = (1 << 0), 727 | SH_DELAYED = (1 << 1), 728 | SH_HEADERS_READ = (1 << 2), 729 | } flags; 730 | struct lsquic_reader reader; 731 | int file_fd; /* Used by pwritev */ 732 | 733 | /* Fields below are used by interop callbacks: */ 734 | enum interop_handler { 735 | IOH_ERROR, 736 | IOH_INDEX_HTML, 737 | IOH_MD5SUM, 738 | IOH_VER_HEAD, 739 | IOH_GEN_FILE, 740 | IOH_ECHO, 741 | } interop_handler; 742 | struct req *req; 743 | const char *resp_status; 744 | union { 745 | struct index_html_ctx ihc; 746 | struct ver_head_ctx vhc; 747 | struct md5sum_ctx md5c; 748 | struct gen_file_ctx gfc; 749 | struct { 750 | char buf[0x100]; 751 | struct resp resp; 752 | } err; 753 | } interop_u; 754 | struct event *resume_resp; 755 | size_t written; 756 | size_t file_size; /* Used by pwritev */ 757 | }; 758 | 759 | 760 | static lsquic_stream_ctx_t * 761 | http_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 762 | { 763 | lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h)); 764 | st_h->stream = stream; 765 | st_h->server_ctx = stream_if_ctx; 766 | lsquic_stream_wantread(stream, 1); 767 | return st_h; 768 | } 769 | 770 | 771 | static int 772 | ends_with (const char *filename, const char *ext) 773 | { 774 | const char *where; 775 | 776 | where = strstr(filename, ext); 777 | return where 778 | && strlen(where) == strlen(ext); 779 | } 780 | 781 | 782 | static const char * 783 | select_content_type (lsquic_stream_ctx_t *st_h) 784 | { 785 | if ( ends_with(st_h->req_filename, ".html")) 786 | return "text/html"; 787 | else if (ends_with(st_h->req_filename, ".png")) 788 | return "image/png"; 789 | else if (ends_with(st_h->req_filename, ".css")) 790 | return "text/css"; 791 | else if (ends_with(st_h->req_filename, ".gif")) 792 | return "image/gif"; 793 | else if (ends_with(st_h->req_filename, ".txt")) 794 | return "text/plain"; 795 | else 796 | return "application/octet-stream"; 797 | } 798 | 799 | 800 | static int 801 | send_headers (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 802 | { 803 | const char *content_type; 804 | struct header_buf hbuf; 805 | 806 | content_type = select_content_type(st_h); 807 | struct lsxpack_header headers_arr[2]; 808 | 809 | hbuf.off = 0; 810 | header_set_ptr(&headers_arr[0], &hbuf, ":status", 7, "200", 3); 811 | header_set_ptr(&headers_arr[1], &hbuf, "content-type", 12, 812 | content_type, strlen(content_type)); 813 | lsquic_http_headers_t headers = { 814 | .count = sizeof(headers_arr) / sizeof(headers_arr[0]), 815 | .headers = headers_arr, 816 | }; 817 | if (0 != lsquic_stream_send_headers(stream, &headers, 0)) 818 | { 819 | LSQ_ERROR("cannot send headers: %s", strerror(errno)); 820 | return -1; 821 | } 822 | 823 | st_h->flags |= SH_HEADERS_SENT; 824 | return 0; 825 | } 826 | 827 | 828 | static void 829 | resume_response (evutil_socket_t fd, short what, void *arg) 830 | { 831 | struct lsquic_stream_ctx *const st_h = arg; 832 | 833 | lsquic_stream_wantwrite(st_h->stream, 1); 834 | event_del(st_h->resume_resp); 835 | event_free(st_h->resume_resp); 836 | st_h->resume_resp = NULL; 837 | 838 | LSQ_NOTICE("resume response to stream %"PRIu64, 839 | lsquic_stream_id(st_h->stream)); 840 | prog_process_conns(st_h->server_ctx->prog); 841 | } 842 | 843 | 844 | static size_t 845 | bytes_left (lsquic_stream_ctx_t *st_h) 846 | { 847 | if (s_pwritev) 848 | return st_h->file_size - st_h->written; 849 | else 850 | return test_reader_size(st_h->reader.lsqr_ctx); 851 | } 852 | 853 | 854 | static ssize_t 855 | my_preadv (void *user_data, const struct iovec *iov, int iovcnt) 856 | { 857 | #if HAVE_PREADV 858 | lsquic_stream_ctx_t *const st_h = user_data; 859 | ssize_t nread = preadv(st_h->file_fd, iov, iovcnt, st_h->written); 860 | LSQ_DEBUG("%s: wrote %zd bytes", __func__, (size_t) nread); 861 | return nread; 862 | #else 863 | return -1; 864 | #endif 865 | } 866 | 867 | 868 | static size_t 869 | pwritev_fallback_read (void *lsqr_ctx, void *buf, size_t count) 870 | { 871 | lsquic_stream_ctx_t *const st_h = lsqr_ctx; 872 | struct iovec iov; 873 | size_t ntoread; 874 | 875 | ntoread = st_h->file_size - st_h->written; 876 | if (ntoread > count) 877 | count = ntoread; 878 | iov.iov_base = buf; 879 | iov.iov_len = count; 880 | return my_preadv(lsqr_ctx, &iov, 1); 881 | } 882 | 883 | 884 | static size_t 885 | pwritev_fallback_size (void *lsqr_ctx) 886 | { 887 | lsquic_stream_ctx_t *const st_h = lsqr_ctx; 888 | return st_h->file_size - st_h->written; 889 | } 890 | 891 | 892 | static void 893 | http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 894 | { 895 | if (st_h->flags & SH_HEADERS_SENT) 896 | { 897 | ssize_t nw; 898 | if (bytes_left(st_h) > 0) 899 | { 900 | if (st_h->server_ctx->delay_resp_sec 901 | && !(st_h->flags & SH_DELAYED) 902 | && st_h->written > 10000000) 903 | { 904 | struct timeval delay = { 905 | .tv_sec = st_h->server_ctx->delay_resp_sec, }; 906 | st_h->resume_resp = event_new(st_h->server_ctx->prog->prog_eb, 907 | -1, EV_TIMEOUT, resume_response, st_h); 908 | if (st_h->resume_resp) 909 | { 910 | event_add(st_h->resume_resp, &delay); 911 | lsquic_stream_wantwrite(stream, 0); 912 | st_h->flags |= SH_DELAYED; 913 | LSQ_NOTICE("delay response of stream %"PRIu64" for %u seconds", 914 | lsquic_stream_id(stream), st_h->server_ctx->delay_resp_sec); 915 | return; 916 | } 917 | else 918 | LSQ_ERROR("cannot allocate event"); 919 | } 920 | if (s_pwritev) 921 | { 922 | size_t to_write = bytes_left(st_h); 923 | if (s_pwritev > 0 && (size_t) s_pwritev < to_write) 924 | to_write = s_pwritev; 925 | nw = lsquic_stream_pwritev(stream, my_preadv, st_h, to_write); 926 | if (nw == 0) 927 | { 928 | struct lsquic_reader reader = { 929 | .lsqr_read = pwritev_fallback_read, 930 | .lsqr_size = pwritev_fallback_size, 931 | .lsqr_ctx = st_h, 932 | }; 933 | nw = lsquic_stream_writef(stream, &reader); 934 | } 935 | } 936 | else 937 | { 938 | nw = lsquic_stream_writef(stream, &st_h->reader); 939 | } 940 | if (nw < 0) 941 | { 942 | struct lsquic_conn *conn = lsquic_stream_conn(stream); 943 | lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 944 | if (conn_h->flags & RECEIVED_GOAWAY) 945 | { 946 | LSQ_NOTICE("cannot write: goaway received"); 947 | lsquic_stream_close(stream); 948 | } 949 | else 950 | { 951 | LSQ_ERROR("write error: %s", strerror(errno)); 952 | exit(1); 953 | } 954 | } 955 | if (bytes_left(st_h) > 0) 956 | { 957 | st_h->written += (size_t) nw; 958 | lsquic_stream_wantwrite(stream, 1); 959 | } 960 | else 961 | { 962 | lsquic_stream_shutdown(stream, 1); 963 | lsquic_stream_wantread(stream, 1); 964 | } 965 | } 966 | else 967 | { 968 | lsquic_stream_shutdown(stream, 1); 969 | lsquic_stream_wantread(stream, 1); 970 | } 971 | } 972 | else 973 | { 974 | if (0 != send_headers(stream, st_h)) 975 | exit(1); 976 | } 977 | } 978 | 979 | 980 | struct capped_reader_ctx 981 | { 982 | struct lsquic_reader *inner_reader; 983 | size_t nread; 984 | }; 985 | 986 | 987 | static size_t 988 | capped_reader_size (void *void_ctx) 989 | { 990 | struct capped_reader_ctx *const capped_reader_ctx = void_ctx; 991 | struct lsquic_reader *const inner_reader = capped_reader_ctx->inner_reader; 992 | size_t size; 993 | 994 | size = inner_reader->lsqr_size(inner_reader->lsqr_ctx); 995 | return MIN((size_t) (s_immediate_write - capped_reader_ctx->nread), size); 996 | } 997 | 998 | 999 | static size_t 1000 | capped_reader_read (void *void_ctx, void *buf, size_t count) 1001 | { 1002 | struct capped_reader_ctx *const capped_reader_ctx = void_ctx; 1003 | struct lsquic_reader *const inner_reader = capped_reader_ctx->inner_reader; 1004 | size_t size; 1005 | 1006 | count = MIN(count, (size_t) (s_immediate_write - capped_reader_ctx->nread)); 1007 | size = inner_reader->lsqr_read(inner_reader->lsqr_ctx, buf, count); 1008 | capped_reader_ctx->nread += size; 1009 | return size; 1010 | } 1011 | 1012 | 1013 | #if HAVE_OPEN_MEMSTREAM 1014 | static void 1015 | parse_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 1016 | { 1017 | char *filename; 1018 | int s; 1019 | regex_t re; 1020 | regmatch_t matches[2]; 1021 | 1022 | s = regcomp(&re, "GET (.*) HTTP/1.[01]\r\n", REG_EXTENDED); 1023 | if (0 != s) 1024 | { 1025 | perror("regcomp"); 1026 | exit(3); 1027 | } 1028 | 1029 | s = regexec(&re, st_h->req_buf, 2, matches, 0); 1030 | if (0 != s) 1031 | { 1032 | LSQ_WARN("GET request could not be parsed: `%s'", st_h->req_buf); 1033 | regfree(&re); 1034 | return; 1035 | } 1036 | 1037 | regfree(&re); 1038 | 1039 | filename = malloc(strlen(st_h->server_ctx->document_root) + 1 + 1040 | matches[1].rm_eo - matches[1].rm_so + 1); 1041 | strcpy(filename, st_h->server_ctx->document_root); 1042 | strcat(filename, "/"); 1043 | strncat(filename, st_h->req_buf + matches[1].rm_so, 1044 | matches[1].rm_eo - matches[1].rm_so); 1045 | 1046 | LSQ_INFO("filename to fetch: %s", filename); 1047 | 1048 | st_h->req_filename = filename; 1049 | st_h->req_path = strdup(filename); 1050 | } 1051 | 1052 | 1053 | static void 1054 | process_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 1055 | { 1056 | struct stat st; 1057 | 1058 | if (s_pwritev) 1059 | { 1060 | st_h->file_fd = open(st_h->req_path, O_RDONLY); 1061 | if (st_h->file_fd < 0) 1062 | { 1063 | LSQ_ERROR("cannot open %s for reading: %s", st_h->req_path, 1064 | strerror(errno)); 1065 | exit(1); 1066 | } 1067 | if (fstat(st_h->file_fd, &st) < 0) 1068 | { 1069 | LSQ_ERROR("fstat: %s", strerror(errno)); 1070 | exit(1); 1071 | } 1072 | st_h->file_size = st.st_size; 1073 | } 1074 | else 1075 | { 1076 | st_h->reader.lsqr_read = test_reader_read; 1077 | st_h->reader.lsqr_size = test_reader_size; 1078 | st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path); 1079 | if (!st_h->reader.lsqr_ctx) 1080 | exit(1); 1081 | } 1082 | 1083 | if (s_immediate_write) 1084 | { 1085 | if (0 != send_headers(stream, st_h)) 1086 | exit(1); 1087 | 1088 | if (test_reader_size(st_h->reader.lsqr_ctx) > 0) 1089 | { 1090 | struct capped_reader_ctx capped_reader_ctx = 1091 | { 1092 | .inner_reader = &st_h->reader, 1093 | }; 1094 | struct lsquic_reader capped_reader = 1095 | { 1096 | .lsqr_read = capped_reader_read, 1097 | .lsqr_size = capped_reader_size, 1098 | .lsqr_ctx = &capped_reader_ctx, 1099 | }; 1100 | ssize_t nw; 1101 | nw = lsquic_stream_writef(stream, &capped_reader); 1102 | if (nw < 0) 1103 | { 1104 | LSQ_ERROR("write error: %s", strerror(errno)); 1105 | exit(1); 1106 | } 1107 | } 1108 | 1109 | if (test_reader_size(st_h->reader.lsqr_ctx) > 0) 1110 | { 1111 | lsquic_stream_flush(stream); 1112 | lsquic_stream_wantwrite(stream, 1); 1113 | } 1114 | else 1115 | { 1116 | lsquic_stream_shutdown(stream, 1); 1117 | lsquic_stream_wantread(stream, 1); 1118 | } 1119 | } 1120 | else 1121 | lsquic_stream_wantwrite(st_h->stream, 1); 1122 | } 1123 | 1124 | 1125 | static struct hset_fm /* FM stands for Filesystem Mode */ 1126 | { 1127 | unsigned id; 1128 | char *path; 1129 | } * 1130 | new_hset_fm (const char *path) 1131 | { 1132 | static unsigned hfm_id; 1133 | struct hset_fm *const hfm = malloc(sizeof(*hfm)); 1134 | char *const str = strdup(path); 1135 | if (hfm && path) 1136 | { 1137 | hfm->id = hfm_id++; 1138 | hfm->path = str; 1139 | return hfm; 1140 | } 1141 | else 1142 | { 1143 | free(str); 1144 | free(hfm); 1145 | return NULL; 1146 | } 1147 | } 1148 | 1149 | 1150 | static void 1151 | destroy_hset_fm (struct hset_fm *hfm) 1152 | { 1153 | free(hfm->path); 1154 | free(hfm); 1155 | } 1156 | 1157 | 1158 | static int 1159 | push_promise (lsquic_stream_ctx_t *st_h, lsquic_stream_t *stream) 1160 | { 1161 | lsquic_conn_t *conn; 1162 | int s; 1163 | regex_t re; 1164 | regmatch_t matches[2]; 1165 | struct hset_fm *hfm; 1166 | struct header_buf hbuf; 1167 | 1168 | s = regcomp(&re, "\r\nHost: *([[:alnum:].][[:alnum:].]*)\r\n", 1169 | REG_EXTENDED|REG_ICASE); 1170 | if (0 != s) 1171 | { 1172 | perror("regcomp"); 1173 | exit(3); 1174 | } 1175 | 1176 | s = regexec(&re, st_h->req_buf, 2, matches, 0); 1177 | if (0 != s) 1178 | { 1179 | LSQ_WARN("Could not find host header in request `%s'", st_h->req_buf); 1180 | regfree(&re); 1181 | return -1; 1182 | } 1183 | regfree(&re); 1184 | 1185 | hfm = new_hset_fm(st_h->server_ctx->push_path); 1186 | if (!hfm) 1187 | { 1188 | LSQ_WARN("Could not allocate hfm"); 1189 | return -1; 1190 | } 1191 | 1192 | #define V(v) (v), strlen(v) 1193 | hbuf.off = 0; 1194 | struct lsxpack_header headers_arr[6]; 1195 | header_set_ptr(&headers_arr[0], &hbuf, V(":method"), V("GET")); 1196 | header_set_ptr(&headers_arr[1], &hbuf, V(":path"), 1197 | V(st_h->server_ctx->push_path)); 1198 | header_set_ptr(&headers_arr[2], &hbuf, V(":authority"), 1199 | st_h->req_buf + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); 1200 | header_set_ptr(&headers_arr[3], &hbuf, V(":scheme"), V("https")); 1201 | header_set_ptr(&headers_arr[4], &hbuf, V("x-some-header"), 1202 | V("x-some-value")); 1203 | header_set_ptr(&headers_arr[5], &hbuf, V("x-kenny-status"), 1204 | V("Oh my God! They killed Kenny!!! You bastards!")); 1205 | lsquic_http_headers_t headers = { 1206 | .count = sizeof(headers_arr) / sizeof(headers_arr[0]), 1207 | .headers = headers_arr, 1208 | }; 1209 | 1210 | conn = lsquic_stream_conn(stream); 1211 | s = lsquic_conn_push_stream(conn, hfm, stream, &headers); 1212 | if (0 == s) 1213 | LSQ_NOTICE("pushed stream successfully"); 1214 | else 1215 | { 1216 | destroy_hset_fm(hfm); 1217 | LSQ_ERROR("could not push stream: %s", strerror(errno)); 1218 | } 1219 | 1220 | return 0; 1221 | } 1222 | 1223 | 1224 | static void 1225 | http_server_on_read_pushed (struct lsquic_stream *stream, 1226 | lsquic_stream_ctx_t *st_h) 1227 | { 1228 | struct hset_fm *hfm; 1229 | 1230 | hfm = lsquic_stream_get_hset(stream); 1231 | if (!hfm) 1232 | { 1233 | LSQ_ERROR("%s: error fetching hset: %s", __func__, strerror(errno)); 1234 | lsquic_stream_close(stream); 1235 | return; 1236 | } 1237 | 1238 | LSQ_INFO("got push request #%u for %s", hfm->id, hfm->path); 1239 | st_h->req_path = malloc(strlen(st_h->server_ctx->document_root) + 1 + 1240 | strlen(hfm->path) + 1); 1241 | strcpy(st_h->req_path, st_h->server_ctx->document_root); 1242 | strcat(st_h->req_path, "/"); 1243 | strcat(st_h->req_path, hfm->path); 1244 | st_h->req_filename = strdup(st_h->req_path); /* XXX Only used for ends_with: drop it? */ 1245 | 1246 | process_request(stream, st_h); 1247 | free(st_h->req_buf); 1248 | lsquic_stream_shutdown(stream, 0); 1249 | destroy_hset_fm(hfm); 1250 | } 1251 | 1252 | 1253 | static void 1254 | http_server_on_read_regular (struct lsquic_stream *stream, 1255 | lsquic_stream_ctx_t *st_h) 1256 | { 1257 | unsigned char buf[0x400]; 1258 | ssize_t nread; 1259 | int s; 1260 | 1261 | if (!st_h->req_fh) 1262 | st_h->req_fh = open_memstream(&st_h->req_buf, &st_h->req_sz); 1263 | 1264 | nread = lsquic_stream_read(stream, buf, sizeof(buf)); 1265 | if (nread > 0) 1266 | fwrite(buf, 1, nread, st_h->req_fh); 1267 | else if (0 == nread) 1268 | { 1269 | fwrite("", 1, 1, st_h->req_fh); /* NUL-terminate so that we can regex the string */ 1270 | fclose(st_h->req_fh); 1271 | LSQ_INFO("got request: `%.*s'", (int) st_h->req_sz, st_h->req_buf); 1272 | parse_request(stream, st_h); 1273 | if (st_h->server_ctx->push_path && 1274 | 0 != strcmp(st_h->req_path, st_h->server_ctx->push_path)) 1275 | { 1276 | s = push_promise(st_h, stream); 1277 | if (s != 0) 1278 | exit(1); 1279 | } 1280 | process_request(stream, st_h); 1281 | free(st_h->req_buf); 1282 | lsquic_stream_shutdown(stream, 0); 1283 | } 1284 | else 1285 | { 1286 | LSQ_ERROR("error reading: %s", strerror(errno)); 1287 | lsquic_stream_close(stream); 1288 | } 1289 | } 1290 | #endif 1291 | 1292 | 1293 | static void 1294 | http_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 1295 | { 1296 | #if HAVE_OPEN_MEMSTREAM 1297 | if (lsquic_stream_is_pushed(stream)) 1298 | http_server_on_read_pushed(stream, st_h); 1299 | else 1300 | http_server_on_read_regular(stream, st_h); 1301 | #else 1302 | LSQ_ERROR("%s: open_memstream not supported\n", __func__); 1303 | exit(1); 1304 | #endif 1305 | } 1306 | 1307 | 1308 | static void 1309 | http_server_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 1310 | { 1311 | free(st_h->req_filename); 1312 | free(st_h->req_path); 1313 | if (st_h->reader.lsqr_ctx) 1314 | destroy_lsquic_reader_ctx(st_h->reader.lsqr_ctx); 1315 | #if HAVE_PREADV 1316 | if (s_pwritev) 1317 | close(st_h->file_fd); 1318 | #endif 1319 | if (st_h->req) 1320 | interop_server_hset_destroy(st_h->req); 1321 | free(st_h); 1322 | LSQ_INFO("%s called, has unacked data: %d", __func__, 1323 | lsquic_stream_has_unacked_data(stream)); 1324 | } 1325 | 1326 | 1327 | const struct lsquic_stream_if http_server_if = { 1328 | .on_new_conn = http_server_on_new_conn, 1329 | .on_conn_closed = http_server_on_conn_closed, 1330 | .on_new_stream = http_server_on_new_stream, 1331 | .on_read = http_server_on_read, 1332 | .on_write = http_server_on_write, 1333 | .on_close = http_server_on_close, 1334 | .on_goaway_received = http_server_on_goaway, 1335 | }; 1336 | 1337 | 1338 | #if HAVE_OPEN_MEMSTREAM 1339 | static void 1340 | hq_server_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 1341 | { 1342 | char tbuf[0x100], *buf; 1343 | ssize_t nread; 1344 | char *path, *end, *filename; 1345 | 1346 | if (!st_h->req_fh) 1347 | st_h->req_fh = open_memstream(&st_h->req_buf, &st_h->req_sz); 1348 | 1349 | nread = lsquic_stream_read(stream, tbuf, sizeof(tbuf)); 1350 | if (nread > 0) 1351 | { 1352 | fwrite(tbuf, 1, nread, st_h->req_fh); 1353 | return; 1354 | } 1355 | 1356 | if (nread < 0) 1357 | { 1358 | LSQ_WARN("error reading request from stream: %s", strerror(errno)); 1359 | lsquic_stream_close(stream); 1360 | return; 1361 | } 1362 | 1363 | fwrite("", 1, 1, st_h->req_fh); 1364 | fclose(st_h->req_fh); 1365 | LSQ_INFO("got request: `%.*s'", (int) st_h->req_sz, st_h->req_buf); 1366 | 1367 | buf = st_h->req_buf; 1368 | path = strchr(buf, ' '); 1369 | if (!path) 1370 | { 1371 | LSQ_WARN("invalid request (no space character): `%s'", buf); 1372 | lsquic_stream_close(stream); 1373 | return; 1374 | } 1375 | if (!(path - buf == 3 && 0 == strncasecmp(buf, "GET", 3))) 1376 | { 1377 | LSQ_NOTICE("unsupported method `%.*s'", (int) (path - buf), buf); 1378 | lsquic_stream_close(stream); 1379 | return; 1380 | } 1381 | ++path; 1382 | for (end = buf + st_h->req_sz - 1; end > path 1383 | && (*end == '\0' || *end == '\r' || *end == '\n'); --end) 1384 | *end = '\0'; 1385 | LSQ_NOTICE("parsed out request path: %s", path); 1386 | 1387 | filename = malloc(strlen(st_h->server_ctx->document_root) + 1 + strlen(path) + 1); 1388 | strcpy(filename, st_h->server_ctx->document_root); 1389 | strcat(filename, "/"); 1390 | strcat(filename, path); 1391 | LSQ_NOTICE("file to fetch: %s", filename); 1392 | /* XXX This copy pasta is getting a bit annoying now: two mallocs of the 1393 | * same thing? 1394 | */ 1395 | st_h->req_filename = filename; 1396 | st_h->req_path = strdup(filename); 1397 | st_h->reader.lsqr_read = test_reader_read; 1398 | st_h->reader.lsqr_size = test_reader_size; 1399 | st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path); 1400 | if (!st_h->reader.lsqr_ctx) 1401 | { 1402 | lsquic_stream_close(stream); 1403 | return; 1404 | } 1405 | lsquic_stream_shutdown(stream, 0); 1406 | lsquic_stream_wantwrite(stream, 1); 1407 | } 1408 | 1409 | 1410 | static void 1411 | hq_server_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h) 1412 | { 1413 | ssize_t nw; 1414 | 1415 | nw = lsquic_stream_writef(stream, &st_h->reader); 1416 | if (nw < 0) 1417 | { 1418 | struct lsquic_conn *conn = lsquic_stream_conn(stream); 1419 | lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 1420 | if (conn_h->flags & RECEIVED_GOAWAY) 1421 | { 1422 | LSQ_NOTICE("cannot write: goaway received"); 1423 | lsquic_stream_close(stream); 1424 | } 1425 | else 1426 | { 1427 | LSQ_ERROR("write error: %s", strerror(errno)); 1428 | lsquic_stream_close(stream); 1429 | } 1430 | } 1431 | else if (bytes_left(st_h) > 0) 1432 | { 1433 | st_h->written += (size_t) nw; 1434 | lsquic_stream_wantwrite(stream, 1); 1435 | } 1436 | else 1437 | { 1438 | lsquic_stream_shutdown(stream, 1); 1439 | lsquic_stream_wantread(stream, 1); 1440 | } 1441 | } 1442 | 1443 | 1444 | const struct lsquic_stream_if hq_server_if = { 1445 | .on_new_conn = http_server_on_new_conn, 1446 | .on_conn_closed = http_server_on_conn_closed, 1447 | .on_new_stream = http_server_on_new_stream, 1448 | .on_read = hq_server_on_read, 1449 | .on_write = hq_server_on_write, 1450 | .on_close = http_server_on_close, 1451 | }; 1452 | #endif 1453 | 1454 | 1455 | #if HAVE_REGEX 1456 | struct req_map 1457 | { 1458 | enum method method; 1459 | const char *path; 1460 | enum interop_handler handler; 1461 | const char *status; 1462 | enum { 1463 | RM_WANTBODY = 1 << 0, 1464 | RM_REGEX = 1 << 1, 1465 | RM_COMPILED = 1 << 2, 1466 | } flags; 1467 | regex_t re; 1468 | }; 1469 | 1470 | 1471 | static struct req_map req_maps[] = 1472 | { 1473 | { .method = GET, .path = "/", .handler = IOH_INDEX_HTML, .status = "200", .flags = 0, }, 1474 | { .method = GET, .path = "/index.html", .handler = IOH_INDEX_HTML, .status = "200", .flags = 0, }, 1475 | { .method = POST, .path = "/cgi-bin/md5sum.cgi", .handler = IOH_MD5SUM, .status = "200", .flags = RM_WANTBODY, }, 1476 | { .method = POST, .path = "/cgi-bin/verify-headers.cgi", .handler = IOH_VER_HEAD, .status = "200", .flags = RM_WANTBODY, }, 1477 | { .method = GET, .path = "^/([0-9][0-9]*)([KMG]?)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1478 | { .method = GET, .path = "^/([0-9][0-9]*)([KMG]?)\\?push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1479 | { .method = GET, .path = "^/([0-9][0-9]*)([KMG]?)\\?push=([^&]*)&push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1480 | { .method = GET, .path = "^/([0-9][0-9]*)([KMG]?)\\?push=([^&]*)&push=([^&]*)&push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1481 | { .method = GET, .path = "^/file-([0-9][0-9]*)([KMG]?)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1482 | { .method = GET, .path = "^/file-([0-9][0-9]*)([KMG]?)\\?push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1483 | { .method = GET, .path = "^/file-([0-9][0-9]*)([KMG]?)\\?push=([^&]*)&push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1484 | { .method = GET, .path = "^/file-([0-9][0-9]*)([KMG]?)\\?push=([^&]*)&push=([^&]*)&push=([^&]*)$", .handler = IOH_GEN_FILE, .status = "200", .flags = RM_REGEX, }, 1485 | }; 1486 | 1487 | 1488 | #define MAX_MATCHES 5 1489 | 1490 | 1491 | static void 1492 | init_map_regexes (void) 1493 | { 1494 | struct req_map *map; 1495 | 1496 | for (map = req_maps; map < req_maps + sizeof(req_maps) 1497 | / sizeof(req_maps[0]); ++map) 1498 | if (map->flags & RM_REGEX) 1499 | { 1500 | #ifndef NDEBUG 1501 | int s; 1502 | s = 1503 | #endif 1504 | regcomp(&map->re, map->path, REG_EXTENDED|REG_ICASE); 1505 | assert(0 == s); 1506 | map->flags |= RM_COMPILED; 1507 | } 1508 | } 1509 | 1510 | 1511 | static void 1512 | free_map_regexes (void) 1513 | { 1514 | struct req_map *map; 1515 | 1516 | for (map = req_maps; map < req_maps + sizeof(req_maps) 1517 | / sizeof(req_maps[0]); ++map) 1518 | if (map->flags & RM_COMPILED) 1519 | { 1520 | regfree(&map->re); 1521 | map->flags &= ~RM_COMPILED; 1522 | } 1523 | } 1524 | 1525 | 1526 | static const struct req_map * 1527 | find_handler (enum method method, const char *path, regmatch_t *matches) 1528 | { 1529 | const struct req_map *map; 1530 | 1531 | for (map = req_maps; map < req_maps + sizeof(req_maps) 1532 | / sizeof(req_maps[0]); ++map) 1533 | if (map->flags & RM_COMPILED) 1534 | { 1535 | if (0 == regexec(&map->re, path, MAX_MATCHES + 1, matches, 0)) 1536 | return map; 1537 | } 1538 | else if (0 == strcasecmp(path, map->path)) 1539 | return map; 1540 | 1541 | return NULL; 1542 | } 1543 | 1544 | 1545 | static const char INDEX_HTML[] = 1546 | "\n" 1547 | " \n" 1548 | " LiteSpeed IETF QUIC Server Index Page\n" 1549 | " \n" 1550 | " \n" 1551 | "

LiteSpeed IETF QUIC Server Index Page

\n" 1552 | "

Hello! Welcome to the interop. Available services:\n" 1553 | "

    \n" 1554 | "
  • POST to /cgi-bin/md5sum.cgi. This will return\n" 1555 | " MD5 checksum of the request body.\n" 1556 | "
  • GET /123K or GET /file-123K. This will return\n" 1557 | " requested number of payload in the form of repeating text\n" 1558 | " by Jerome K. Jerome. The size specification must match\n" 1559 | " (\\d+)[KMG]? and the total size request must not exceed\n" 1560 | " 2 gigabytes. Then, you will get back that many bytes\n" 1561 | " of the beloved classic.\n" 1564 | "
\n" 1565 | " \n" 1566 | "\n" 1567 | ; 1568 | 1569 | 1570 | static size_t 1571 | read_md5 (void *ctx, const unsigned char *buf, size_t sz, int fin) 1572 | { 1573 | struct lsquic_stream_ctx *st_h = ctx; 1574 | 1575 | if (sz) 1576 | MD5_Update(&st_h->interop_u.md5c.md5ctx, buf, sz); 1577 | 1578 | if (fin) 1579 | st_h->interop_u.md5c.done = 1; 1580 | 1581 | return sz; 1582 | } 1583 | 1584 | 1585 | static void 1586 | http_server_interop_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 1587 | { 1588 | #define ERROR_RESP(code, ...) do { \ 1589 | LSQ_WARN(__VA_ARGS__); \ 1590 | st_h->interop_handler = IOH_ERROR; \ 1591 | st_h->resp_status = #code; \ 1592 | st_h->interop_u.err.resp.sz = snprintf(st_h->interop_u.err.buf, \ 1593 | sizeof(st_h->interop_u.err.buf), __VA_ARGS__); \ 1594 | if (st_h->interop_u.err.resp.sz >= sizeof(st_h->interop_u.err.buf)) \ 1595 | st_h->interop_u.err.resp.sz = sizeof(st_h->interop_u.err.buf) - 1; \ 1596 | st_h->interop_u.err.resp.buf = st_h->interop_u.err.buf; \ 1597 | st_h->interop_u.err.resp.off = 0; \ 1598 | goto err; \ 1599 | } while (0) 1600 | 1601 | const struct req_map *map; 1602 | ssize_t nw; 1603 | size_t need; 1604 | unsigned len, i; 1605 | struct interop_push_path *push_path; 1606 | regmatch_t matches[MAX_MATCHES + 1]; 1607 | unsigned char md5sum[MD5_DIGEST_LENGTH]; 1608 | char md5str[ sizeof(md5sum) * 2 + 1 ]; 1609 | char byte[1]; 1610 | 1611 | if (!(st_h->flags & SH_HEADERS_READ)) 1612 | { 1613 | st_h->flags |= SH_HEADERS_READ; 1614 | st_h->req = lsquic_stream_get_hset(stream); 1615 | if (!st_h->req) 1616 | ERROR_RESP(500, "Internal error: cannot fetch header set from stream"); 1617 | else if (st_h->req->method == UNSET) 1618 | ERROR_RESP(400, "Method is not specified"); 1619 | else if (!st_h->req->path) 1620 | ERROR_RESP(400, "Path is not specified"); 1621 | else if (st_h->req->method == UNSUPPORTED) 1622 | ERROR_RESP(501, "Method %s is not supported", st_h->req->method_str); 1623 | else if (!(map = find_handler(st_h->req->method, st_h->req->path, matches))) 1624 | ERROR_RESP(404, "No handler found for method: %s; path: %s", 1625 | st_h->req->method_str, st_h->req->path); 1626 | else 1627 | { 1628 | LSQ_INFO("found handler for %s %s", st_h->req->method_str, st_h->req->path); 1629 | st_h->resp_status = map->status; 1630 | st_h->interop_handler = map->handler; 1631 | switch (map->handler) 1632 | { 1633 | case IOH_INDEX_HTML: 1634 | st_h->interop_u.ihc.resp = (struct resp) { INDEX_HTML, sizeof(INDEX_HTML) - 1, 0, }; 1635 | break; 1636 | case IOH_VER_HEAD: 1637 | st_h->interop_u.vhc.resp = (struct resp) { 1638 | st_h->req->qif_str, st_h->req->qif_sz, 0, }; 1639 | break; 1640 | case IOH_MD5SUM: 1641 | MD5_Init(&st_h->interop_u.md5c.md5ctx); 1642 | st_h->interop_u.md5c.done = 0; 1643 | break; 1644 | case IOH_GEN_FILE: 1645 | STAILQ_INIT(&st_h->interop_u.gfc.push_paths); 1646 | st_h->interop_u.gfc.remain = strtol(st_h->req->path + matches[1].rm_so, NULL, 10); 1647 | if (matches[2].rm_so >= 0 1648 | && matches[2].rm_so < matches[2].rm_eo) 1649 | { 1650 | switch (st_h->req->path[ matches[2].rm_so ]) 1651 | { 1652 | case 'G': 1653 | case 'g': 1654 | st_h->interop_u.gfc.remain <<= 30; 1655 | break; 1656 | case 'M': 1657 | case 'm': 1658 | st_h->interop_u.gfc.remain <<= 20; 1659 | break; 1660 | case 'K': 1661 | case 'k': 1662 | st_h->interop_u.gfc.remain <<= 10; 1663 | break; 1664 | } 1665 | } 1666 | if (st_h->interop_u.gfc.remain > 2 * (1u << 30)) 1667 | ERROR_RESP(406, "Response of %zd bytes is too long to generate", 1668 | st_h->interop_u.gfc.remain); 1669 | st_h->interop_u.gfc.idle_off = 0; 1670 | for (i = 3; i <= MAX_MATCHES; ++i) 1671 | if (matches[i].rm_so >= 0) 1672 | { 1673 | len = matches[i].rm_eo - matches[i].rm_so; 1674 | push_path = malloc(sizeof(*push_path) + len + 1); 1675 | memcpy(push_path->path, st_h->req->path 1676 | + matches[i].rm_so, len); 1677 | push_path->path[len] ='\0'; 1678 | STAILQ_INSERT_TAIL(&st_h->interop_u.gfc.push_paths, 1679 | push_path, next); 1680 | } 1681 | else 1682 | break; 1683 | break; 1684 | default: 1685 | /* TODO: implement this */ 1686 | assert(0); 1687 | break; 1688 | } 1689 | } 1690 | 1691 | if (!(map->flags & RM_WANTBODY)) 1692 | { 1693 | err: 1694 | lsquic_stream_shutdown(stream, 0); 1695 | lsquic_stream_wantwrite(stream, 1); 1696 | } 1697 | } 1698 | else 1699 | { 1700 | switch (st_h->interop_handler) 1701 | { 1702 | case IOH_MD5SUM: 1703 | assert(!st_h->interop_u.md5c.done); 1704 | nw = lsquic_stream_readf(stream, read_md5, st_h); 1705 | if (nw < 0) 1706 | { 1707 | LSQ_ERROR("could not read from stream for MD5: %s", strerror(errno)); 1708 | exit(1); 1709 | } 1710 | if (nw == 0) 1711 | st_h->interop_u.md5c.done = 1; 1712 | if (st_h->interop_u.md5c.done) 1713 | { 1714 | MD5_Final(md5sum, &st_h->interop_u.md5c.md5ctx); 1715 | lsquic_hexstr(md5sum, sizeof(md5sum), md5str, sizeof(md5str)); 1716 | snprintf(st_h->interop_u.md5c.resp_buf, sizeof(st_h->interop_u.md5c.resp_buf), 1717 | "MD5 Checksum Result\n" 1718 | "

MD5 Checksum Result

\n

" 1719 | "MD5 Checksum: %s\n\n", 1720 | md5str); 1721 | st_h->interop_u.md5c.resp.buf = st_h->interop_u.md5c.resp_buf; 1722 | st_h->interop_u.md5c.resp.sz = strlen(st_h->interop_u.md5c.resp_buf); 1723 | st_h->interop_u.md5c.resp.off = 0; 1724 | lsquic_stream_shutdown(stream, 0); 1725 | lsquic_stream_wantwrite(stream, 1); 1726 | } 1727 | break; 1728 | case IOH_VER_HEAD: 1729 | if (!st_h->interop_u.vhc.req_body) 1730 | { 1731 | st_h->interop_u.vhc.req_body = malloc(st_h->req->qif_sz); 1732 | if (!st_h->interop_u.vhc.req_body) 1733 | { 1734 | perror("malloc"); 1735 | exit(1); 1736 | } 1737 | } 1738 | need = st_h->req->qif_sz - st_h->interop_u.vhc.req_sz; 1739 | if (need > 0) 1740 | { 1741 | nw = lsquic_stream_read(stream, 1742 | st_h->interop_u.vhc.req_body 1743 | + st_h->interop_u.vhc.req_sz, need); 1744 | if (nw > 0) 1745 | st_h->interop_u.vhc.req_sz += need; 1746 | else if (nw == 0) 1747 | { 1748 | LSQ_WARN("request body too short (does not match headers)"); 1749 | lsquic_stream_shutdown(stream, 0); 1750 | lsquic_stream_wantwrite(stream, 1); 1751 | } 1752 | else 1753 | { 1754 | LSQ_ERROR("error reading from stream"); 1755 | exit(1); 1756 | } 1757 | } 1758 | else 1759 | { 1760 | nw = lsquic_stream_read(stream, byte, sizeof(byte)); 1761 | if (nw == 0) 1762 | { 1763 | if (0 == memcmp(st_h->req->qif_str, 1764 | st_h->interop_u.vhc.req_body, st_h->req->qif_sz)) 1765 | LSQ_INFO("request headers and payload check out"); 1766 | else 1767 | LSQ_WARN("request headers and payload are different"); 1768 | } 1769 | else 1770 | LSQ_WARN("request body too long (does not match headers)"); 1771 | lsquic_stream_shutdown(stream, 0); 1772 | lsquic_stream_wantwrite(stream, 1); 1773 | } 1774 | break; 1775 | default: 1776 | assert(0); 1777 | } 1778 | } 1779 | } 1780 | 1781 | 1782 | static int 1783 | send_headers2 (struct lsquic_stream *stream, struct lsquic_stream_ctx *st_h, 1784 | size_t content_len) 1785 | { 1786 | char clbuf[0x20]; 1787 | struct header_buf hbuf; 1788 | 1789 | snprintf(clbuf, sizeof(clbuf), "%zd", content_len); 1790 | 1791 | hbuf.off = 0; 1792 | struct lsxpack_header headers_arr[4]; 1793 | header_set_ptr(&headers_arr[0], &hbuf, V(":status"), V(st_h->resp_status)); 1794 | header_set_ptr(&headers_arr[1], &hbuf, V("server"), V(LITESPEED_ID)); 1795 | header_set_ptr(&headers_arr[2], &hbuf, V("content-type"), V("text/html")); 1796 | header_set_ptr(&headers_arr[3], &hbuf, V("content-length"), V(clbuf)); 1797 | lsquic_http_headers_t headers = { 1798 | .count = sizeof(headers_arr) / sizeof(headers_arr[0]), 1799 | .headers = headers_arr, 1800 | }; 1801 | 1802 | return lsquic_stream_send_headers(st_h->stream, &headers, 0); 1803 | } 1804 | 1805 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 1806 | 1807 | static size_t 1808 | idle_read (void *lsqr_ctx, void *buf, size_t count) 1809 | { 1810 | struct gen_file_ctx *const gfc = lsqr_ctx; 1811 | unsigned char *p = buf; 1812 | unsigned char *const end = p + count; 1813 | size_t towrite; 1814 | 1815 | while (p < end && gfc->remain > 0) 1816 | { 1817 | towrite = MIN((unsigned) (end - p), IDLE_SIZE - gfc->idle_off); 1818 | if (towrite > gfc->remain) 1819 | towrite = gfc->remain; 1820 | memcpy(p, on_being_idle + gfc->idle_off, towrite); 1821 | gfc->idle_off += towrite; 1822 | if (gfc->idle_off == IDLE_SIZE) 1823 | gfc->idle_off = 0; 1824 | p += towrite; 1825 | gfc->remain -= towrite; 1826 | } 1827 | 1828 | return p - (unsigned char *) buf; 1829 | } 1830 | 1831 | 1832 | static size_t 1833 | idle_size (void *lsqr_ctx) 1834 | { 1835 | struct gen_file_ctx *const gfc = lsqr_ctx; 1836 | 1837 | return gfc->remain; 1838 | } 1839 | 1840 | 1841 | static struct req * 1842 | new_req (enum method method, const char *path, const char *authority) 1843 | { 1844 | struct req *req; 1845 | 1846 | req = malloc(sizeof(*req)); 1847 | if (!req) 1848 | return NULL; 1849 | 1850 | memset(req, 0, offsetof(struct req, decode_buf)); 1851 | req->method = method; 1852 | req->path = strdup(path); 1853 | req->authority_str = strdup(authority); 1854 | if (!(req->path && req->authority_str)) 1855 | { 1856 | free(req->path); 1857 | free(req->authority_str); 1858 | free(req); 1859 | return NULL; 1860 | } 1861 | 1862 | return req; 1863 | } 1864 | 1865 | 1866 | static ssize_t 1867 | my_interop_preadv (void *user_data, const struct iovec *iov, int iovcnt) 1868 | { 1869 | struct gen_file_ctx *const gfc = user_data; 1870 | size_t nread, nr; 1871 | int i; 1872 | 1873 | nread = 0; 1874 | for (i = 0; i < iovcnt; ++i) 1875 | { 1876 | nr = idle_read(gfc, iov[i].iov_base, iov[i].iov_len); 1877 | nread += nr; 1878 | } 1879 | 1880 | return (ssize_t) nread; 1881 | } 1882 | 1883 | 1884 | static void 1885 | idle_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 1886 | { 1887 | struct gen_file_ctx *const gfc = &st_h->interop_u.gfc; 1888 | struct interop_push_path *push_path; 1889 | struct lsxpack_header header_arr[4]; 1890 | struct lsquic_http_headers headers; 1891 | struct req *req; 1892 | ssize_t nw; 1893 | struct header_buf hbuf; 1894 | struct lsquic_reader reader; 1895 | 1896 | if (st_h->flags & SH_HEADERS_SENT) 1897 | { 1898 | if (s_pwritev) 1899 | { 1900 | nw = lsquic_stream_pwritev(stream, my_interop_preadv, gfc, 1901 | gfc->remain); 1902 | if (nw == 0) 1903 | goto with_reader; 1904 | } 1905 | else 1906 | { 1907 | with_reader: 1908 | reader.lsqr_read = idle_read, 1909 | reader.lsqr_size = idle_size, 1910 | reader.lsqr_ctx = gfc, 1911 | nw = lsquic_stream_writef(stream, &reader); 1912 | } 1913 | if (nw < 0) 1914 | { 1915 | LSQ_ERROR("error writing idle thoughts: %s", strerror(errno)); 1916 | exit(1); 1917 | } 1918 | if (gfc->remain == 0) 1919 | lsquic_stream_shutdown(stream, 1); 1920 | } 1921 | else 1922 | { 1923 | if (st_h->req->authority_str) 1924 | while ((push_path = STAILQ_FIRST(&gfc->push_paths))) 1925 | { 1926 | STAILQ_REMOVE_HEAD(&gfc->push_paths, next); 1927 | LSQ_DEBUG("pushing promise for %s", push_path->path); 1928 | hbuf.off = 0; 1929 | header_set_ptr(&header_arr[0], &hbuf, V(":method"), V("GET")); 1930 | header_set_ptr(&header_arr[1], &hbuf, V(":path"), V(push_path->path)); 1931 | header_set_ptr(&header_arr[2], &hbuf, V(":authority"), V(st_h->req->authority_str)); 1932 | header_set_ptr(&header_arr[3], &hbuf, V(":scheme"), V("https")); 1933 | headers.headers = header_arr; 1934 | headers.count = sizeof(header_arr) / sizeof(header_arr[0]); 1935 | req = new_req(GET, push_path->path, st_h->req->authority_str); 1936 | if (req) 1937 | { 1938 | if (0 != lsquic_conn_push_stream(lsquic_stream_conn(stream), 1939 | req, stream, &headers)) 1940 | { 1941 | LSQ_WARN("stream push failed"); 1942 | interop_server_hset_destroy(req); 1943 | } 1944 | } 1945 | else 1946 | LSQ_WARN("cannot allocate req for push"); 1947 | free(push_path); 1948 | } 1949 | if (0 == send_headers2(stream, st_h, gfc->remain)) 1950 | st_h->flags |= SH_HEADERS_SENT; 1951 | else 1952 | { 1953 | LSQ_ERROR("cannot send headers: %s", strerror(errno)); 1954 | lsquic_stream_close(stream); 1955 | } 1956 | } 1957 | } 1958 | 1959 | 1960 | static void 1961 | http_server_interop_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 1962 | { 1963 | struct resp *resp; 1964 | ssize_t nw; 1965 | 1966 | switch (st_h->interop_handler) 1967 | { 1968 | case IOH_ERROR: 1969 | resp = &st_h->interop_u.err.resp; 1970 | goto reply; 1971 | case IOH_INDEX_HTML: 1972 | resp = &st_h->interop_u.ihc.resp; 1973 | goto reply; 1974 | case IOH_VER_HEAD: 1975 | resp = &st_h->interop_u.vhc.resp; 1976 | goto reply; 1977 | case IOH_MD5SUM: 1978 | resp = &st_h->interop_u.md5c.resp; 1979 | goto reply; 1980 | case IOH_GEN_FILE: 1981 | idle_on_write(stream, st_h); 1982 | return; 1983 | default: 1984 | assert(0); 1985 | return; 1986 | } 1987 | 1988 | reply: 1989 | assert(resp->sz); /* We always need to send body */ 1990 | if (!(st_h->flags & SH_HEADERS_SENT)) 1991 | { 1992 | send_headers2(stream, st_h, resp->sz); 1993 | st_h->flags |= SH_HEADERS_SENT; 1994 | return; 1995 | } 1996 | 1997 | nw = lsquic_stream_write(stream, resp->buf + resp->off, resp->sz - resp->off); 1998 | if (nw < 0) 1999 | { 2000 | LSQ_ERROR("error writing to stream: %s", strerror(errno)); 2001 | lsquic_conn_abort(lsquic_stream_conn(stream)); 2002 | return; 2003 | } 2004 | 2005 | resp->off += nw; 2006 | lsquic_stream_flush(stream); 2007 | if (resp->off == resp->sz) 2008 | lsquic_stream_shutdown(stream, 1); 2009 | } 2010 | 2011 | 2012 | const struct lsquic_stream_if interop_http_server_if = { 2013 | .on_new_conn = http_server_on_new_conn, 2014 | .on_conn_closed = http_server_on_conn_closed, 2015 | .on_new_stream = http_server_on_new_stream, 2016 | .on_read = http_server_interop_on_read, 2017 | .on_write = http_server_interop_on_write, 2018 | .on_close = http_server_on_close, 2019 | }; 2020 | #endif /* HAVE_REGEX */ 2021 | 2022 | 2023 | static void 2024 | usage (const char *prog) 2025 | { 2026 | const char *const slash = strrchr(prog, '/'); 2027 | if (slash) 2028 | prog = slash + 1; 2029 | printf( 2030 | "Usage: %s [opts]\n" 2031 | "\n" 2032 | "Options:\n" 2033 | " -r ROOT Document root\n" 2034 | " -p FILE Push request with this path\n" 2035 | " -w SIZE Write immediately (LSWS mode). Argument specifies maximum\n" 2036 | " size of the immediate write.\n" 2037 | #if HAVE_PREADV 2038 | " -P SIZE Use preadv(2) to read from disk and lsquic_stream_pwritev() to\n" 2039 | " write to stream. Positive SIZE indicate maximum value per\n" 2040 | " write; negative means always use remaining file size.\n" 2041 | " Incompatible with -w.\n" 2042 | #endif 2043 | " -y DELAY Delay response for this many seconds -- use for debugging\n" 2044 | " -Q ALPN Use hq mode; ALPN could be \"hq-29\", for example.\n" 2045 | , prog); 2046 | } 2047 | 2048 | 2049 | static void * 2050 | interop_server_hset_create (void *hsi_ctx, lsquic_stream_t *stream, 2051 | int is_push_promise) 2052 | { 2053 | struct req *req; 2054 | 2055 | req = malloc(sizeof(struct req)); 2056 | memset(req, 0, offsetof(struct req, decode_buf)); 2057 | 2058 | return req; 2059 | } 2060 | 2061 | 2062 | static struct lsxpack_header * 2063 | interop_server_hset_prepare_decode (void *hset_p, struct lsxpack_header *xhdr, 2064 | size_t req_space) 2065 | { 2066 | struct req *req = hset_p; 2067 | 2068 | if (xhdr) 2069 | { 2070 | LSQ_WARN("we don't reallocate headers: can't give more"); 2071 | return NULL; 2072 | } 2073 | 2074 | if (req->flags & HAVE_XHDR) 2075 | { 2076 | if (req->decode_off + lsxpack_header_get_dec_size(&req->xhdr) 2077 | >= sizeof(req->decode_buf)) 2078 | { 2079 | LSQ_WARN("Not enough room in header"); 2080 | return NULL; 2081 | } 2082 | req->decode_off += lsxpack_header_get_dec_size(&req->xhdr); 2083 | } 2084 | else 2085 | req->flags |= HAVE_XHDR; 2086 | 2087 | lsxpack_header_prepare_decode(&req->xhdr, req->decode_buf, 2088 | req->decode_off, sizeof(req->decode_buf) - req->decode_off); 2089 | return &req->xhdr; 2090 | } 2091 | 2092 | 2093 | static int 2094 | interop_server_hset_add_header (void *hset_p, struct lsxpack_header *xhdr) 2095 | { 2096 | struct req *req = hset_p; 2097 | const char *name, *value; 2098 | unsigned name_len, value_len; 2099 | 2100 | if (!xhdr) 2101 | { 2102 | if (req->pseudo_headers == (PH_AUTHORITY|PH_METHOD|PH_PATH)) 2103 | return 0; 2104 | else 2105 | { 2106 | LSQ_INFO("%s: missing some pseudo-headers: 0x%X", __func__, 2107 | req->pseudo_headers); 2108 | return 1; 2109 | } 2110 | } 2111 | 2112 | name = lsxpack_header_get_name(xhdr); 2113 | value = lsxpack_header_get_value(xhdr); 2114 | name_len = xhdr->name_len; 2115 | value_len = xhdr->val_len; 2116 | 2117 | req->qif_str = realloc(req->qif_str, 2118 | req->qif_sz + name_len + value_len + 2); 2119 | if (!req->qif_str) 2120 | { 2121 | LSQ_ERROR("malloc failed"); 2122 | return -1; 2123 | } 2124 | memcpy(req->qif_str + req->qif_sz, name, name_len); 2125 | req->qif_str[req->qif_sz + name_len] = '\t'; 2126 | memcpy(req->qif_str + req->qif_sz + name_len + 1, value, value_len); 2127 | req->qif_str[req->qif_sz + name_len + 1 + value_len] = '\n'; 2128 | req->qif_sz += name_len + value_len + 2; 2129 | 2130 | if (5 == name_len && 0 == strncmp(name, ":path", 5)) 2131 | { 2132 | if (req->path) 2133 | return 1; 2134 | req->path = strndup(value, value_len); 2135 | if (!req->path) 2136 | return -1; 2137 | req->pseudo_headers |= PH_PATH; 2138 | return 0; 2139 | } 2140 | 2141 | if (7 == name_len && 0 == strncmp(name, ":method", 7)) 2142 | { 2143 | if (req->method != UNSET) 2144 | return 1; 2145 | req->method_str = strndup(value, value_len); 2146 | if (!req->method_str) 2147 | return -1; 2148 | if (0 == strcmp(req->method_str, "GET")) 2149 | req->method = GET; 2150 | else if (0 == strcmp(req->method_str, "POST")) 2151 | req->method = POST; 2152 | else 2153 | req->method = UNSUPPORTED; 2154 | req->pseudo_headers |= PH_METHOD; 2155 | return 0; 2156 | } 2157 | 2158 | if (10 == name_len && 0 == strncmp(name, ":authority", 10)) 2159 | { 2160 | req->authority_str = strndup(value, value_len); 2161 | if (!req->authority_str) 2162 | return -1; 2163 | req->pseudo_headers |= PH_AUTHORITY; 2164 | return 0; 2165 | } 2166 | 2167 | return 0; 2168 | } 2169 | 2170 | 2171 | static void 2172 | interop_server_hset_destroy (void *hset_p) 2173 | { 2174 | struct req *req = hset_p; 2175 | free(req->qif_str); 2176 | free(req->path); 2177 | free(req->method_str); 2178 | free(req->authority_str); 2179 | free(req); 2180 | } 2181 | 2182 | 2183 | static const struct lsquic_hset_if header_bypass_api = 2184 | { 2185 | .hsi_create_header_set = interop_server_hset_create, 2186 | .hsi_prepare_decode = interop_server_hset_prepare_decode, 2187 | .hsi_process_header = interop_server_hset_add_header, 2188 | .hsi_discard_header_set = interop_server_hset_destroy, 2189 | }; 2190 | 2191 | 2192 | int 2193 | main (int argc, char **argv) 2194 | { 2195 | int opt, s; 2196 | struct stat st; 2197 | struct server_ctx server_ctx; 2198 | struct prog prog; 2199 | const char *const *alpn; 2200 | 2201 | #if !(HAVE_OPEN_MEMSTREAM || HAVE_REGEX) 2202 | fprintf(stderr, "cannot run server without regex or open_memstream\n"); 2203 | return 1; 2204 | #endif 2205 | 2206 | memset(&server_ctx, 0, sizeof(server_ctx)); 2207 | TAILQ_INIT(&server_ctx.sports); 2208 | server_ctx.prog = &prog; 2209 | 2210 | // eQUIC 2211 | server_ctx.has_eBPF = 0; 2212 | requests_per_second = 0; 2213 | pthread_mutex_init(&lock, NULL); 2214 | pthread_create(&tid, NULL, &request_counter, NULL); 2215 | 2216 | prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports, 2217 | &http_server_if, &server_ctx); 2218 | 2219 | // eQUIC 2220 | while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:P:he" 2221 | #if HAVE_OPEN_MEMSTREAM 2222 | "Q:" 2223 | #endif 2224 | ))) 2225 | { 2226 | switch (opt) { 2227 | case 'e': 2228 | /* eQUIC initialization and setup */ 2229 | server_ctx.has_eBPF = 1; 2230 | equic_t equic; 2231 | equic_get_interface(&equic, "eth0"); 2232 | equic_read_object(&equic, "/src/equic/bin/equic_kern.o"); 2233 | signal(SIGINT, equic_sigint_callback); 2234 | signal(SIGTERM, equic_sigterm_callback); 2235 | equic_load(&equic); 2236 | server_ctx.equic = &equic; 2237 | break; 2238 | case 'n': 2239 | server_ctx.max_conn = atoi(optarg); 2240 | break; 2241 | case 'p': 2242 | server_ctx.push_path = optarg; 2243 | break; 2244 | case 'r': 2245 | if (-1 == stat(optarg, &st)) 2246 | { 2247 | perror("stat"); 2248 | exit(2); 2249 | } 2250 | #ifndef WIN32 2251 | if (!S_ISDIR(st.st_mode)) 2252 | { 2253 | fprintf(stderr, "`%s' is not a directory\n", optarg); 2254 | exit(2); 2255 | } 2256 | #endif 2257 | server_ctx.document_root = optarg; 2258 | break; 2259 | case 'w': 2260 | s_immediate_write = atoi(optarg); 2261 | break; 2262 | case 'P': 2263 | #if HAVE_PREADV 2264 | s_pwritev = strtoull(optarg, NULL, 10); 2265 | break; 2266 | #else 2267 | fprintf(stderr, "preadv is not supported on this platform, " 2268 | "cannot use -P\n"); 2269 | exit(EXIT_FAILURE); 2270 | #endif 2271 | case 'y': 2272 | server_ctx.delay_resp_sec = atoi(optarg); 2273 | break; 2274 | case 'h': 2275 | usage(argv[0]); 2276 | prog_print_common_options(&prog, stdout); 2277 | exit(0); 2278 | #if HAVE_OPEN_MEMSTREAM 2279 | case 'Q': 2280 | /* XXX A bit hacky, as `prog' has already been initialized... */ 2281 | prog.prog_engine_flags &= ~LSENG_HTTP; 2282 | prog.prog_api.ea_stream_if = &hq_server_if; 2283 | add_alpn(optarg); 2284 | break; 2285 | #endif 2286 | default: 2287 | if (0 != prog_set_opt(&prog, opt, optarg)) 2288 | exit(1); 2289 | } 2290 | } 2291 | 2292 | if (!server_ctx.document_root) 2293 | { 2294 | #if HAVE_REGEX 2295 | LSQ_NOTICE("Document root is not set: start in Interop Mode"); 2296 | init_map_regexes(); 2297 | prog.prog_api.ea_stream_if = &interop_http_server_if; 2298 | prog.prog_api.ea_hsi_if = &header_bypass_api; 2299 | prog.prog_api.ea_hsi_ctx = NULL; 2300 | #else 2301 | LSQ_ERROR("Document root is not set: use -r option"); 2302 | exit(EXIT_FAILURE); 2303 | #endif 2304 | } 2305 | 2306 | if (s_immediate_write && s_pwritev) 2307 | { 2308 | LSQ_ERROR("-w and -P are incompatible options"); 2309 | exit(EXIT_FAILURE); 2310 | } 2311 | 2312 | alpn = lsquic_get_h3_alpns(prog.prog_settings.es_versions); 2313 | while (*alpn) 2314 | { 2315 | if (0 == add_alpn(*alpn)) 2316 | ++alpn; 2317 | else 2318 | { 2319 | LSQ_ERROR("cannot add ALPN %s", *alpn); 2320 | exit(EXIT_FAILURE); 2321 | } 2322 | } 2323 | 2324 | if (0 != prog_prep(&prog)) 2325 | { 2326 | LSQ_ERROR("could not prep"); 2327 | exit(EXIT_FAILURE); 2328 | } 2329 | 2330 | LSQ_DEBUG("entering event loop"); 2331 | 2332 | s = prog_run(&prog); 2333 | prog_cleanup(&prog); 2334 | 2335 | #if HAVE_REGEX 2336 | if (!server_ctx.document_root) 2337 | free_map_regexes(); 2338 | #endif 2339 | 2340 | exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 2341 | } 2342 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pantuza/equic/a64166b49780851a6db3b163e608d6058c722387/logs/.gitkeep -------------------------------------------------------------------------------- /src/equic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "equic_user.h" 5 | 6 | 7 | int 8 | main(int argc, char **argv) 9 | { 10 | equic_t equic; 11 | 12 | equic_get_interface(&equic, "eth0"); 13 | equic_read_object(&equic, "/src/equic/bin/equic_kern.o"); 14 | 15 | /* Sets signal handlers */ 16 | signal(SIGINT, equic_sigint_callback); 17 | signal(SIGTERM, equic_sigterm_callback); 18 | 19 | equic_load(&equic); 20 | 21 | return EXIT_SUCCESS; 22 | } 23 | -------------------------------------------------------------------------------- /src/equic_kern.c: -------------------------------------------------------------------------------- 1 | /* Linux headers for bpf and networking */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* libbpf header files */ 9 | #include "bpf/bpf_helpers.h" 10 | #include "bpf/bpf_endian.h" 11 | 12 | 13 | /** 14 | * Maximum simultaneous connections allowed from a given source IPv4 address 15 | */ 16 | #define QUIC_QUOTA_LIMIT 5 17 | 18 | /** 19 | * Maximum requests per second allowed from a given source IPv4 address 20 | */ 21 | #define QUIC_RATE_LIMIT 20 22 | 23 | 24 | /* Map that controls connections counter by source IP */ 25 | struct bpf_map_def SEC("maps") counters = { 26 | 27 | .type = BPF_MAP_TYPE_HASH, 28 | 29 | /* A 32 bits representing the source IPv4 address */ 30 | .key_size = sizeof(__be32), 31 | 32 | /* A integer counter of the connections */ 33 | .value_size = sizeof(int), 34 | 35 | .max_entries = 1024, 36 | }; 37 | 38 | 39 | /* Map that controls requests rate limit by source IP */ 40 | struct bpf_map_def SEC("maps") rate_limit_map = { 41 | 42 | .type = BPF_MAP_TYPE_HASH, 43 | 44 | /* A 32 bits representing the source IPv4 address */ 45 | .key_size = sizeof(__be32), 46 | 47 | /* A integer counter of the requests */ 48 | .value_size = sizeof(int), 49 | 50 | .max_entries = 1024, 51 | }; 52 | 53 | 54 | /** 55 | * eQUIC XDP hook that verifies if a given source IPv4 address 56 | * has reached its connection quota limit 57 | */ 58 | SEC("equic") 59 | int udp_quota (struct xdp_md *ctx) 60 | { 61 | __u64 begin_in_ns = bpf_ktime_get_ns(); 62 | 63 | __be32 key; 64 | int value = 0; 65 | 66 | void *begin = (void *)(long)ctx->data; 67 | void *end = (void *)(long)ctx->data_end; 68 | 69 | /* Assign a Ethernet header */ 70 | struct ethhdr *eth = begin; 71 | 72 | /* Assign a IPv4 header */ 73 | int ip_begin = sizeof(*eth); 74 | struct iphdr *ip = begin + ip_begin; 75 | int ip_end = ip_begin + sizeof(struct iphdr); 76 | 77 | if (begin + ip_end > end) { 78 | return XDP_ABORTED; 79 | } 80 | 81 | /* Reads the packet source IPv4 address used as map key */ 82 | key = ip->saddr; 83 | 84 | if (ip->protocol == IPPROTO_UDP) { 85 | 86 | /* Assigns a UDP header */ 87 | struct udphdr *udp = (void*)ip + sizeof(*ip); 88 | if ((void *)udp + sizeof(*udp) > end) { 89 | return XDP_ABORTED; 90 | } 91 | 92 | int *curr_value = bpf_map_lookup_elem(&counters, &key); 93 | if(!curr_value) { 94 | #ifdef DEBUG 95 | bpf_printk("[XDP] UDP=true, Action=Pass, Status=LookupMiss, Key=%d\n", key); 96 | #endif 97 | 98 | return XDP_PASS; 99 | } 100 | 101 | /** 102 | * Start droping traffic from the given IPv4 address once it 103 | * reached the simultaneous QUIC connections quota 104 | */ 105 | if (*curr_value >= QUIC_QUOTA_LIMIT) { 106 | #ifdef DEBUG 107 | bpf_printk("[XDP] Action=Drop, QuotaExceeded=%llu\n", *curr_value); 108 | __u64 end_in_ns = bpf_ktime_get_ns(); 109 | __u64 elapsed_us = (end_in_ns - begin_in_ns) / 1000; 110 | bpf_printk( 111 | "[XDP] BlockDuration=%llu, Begin=%llu, End=%llu\n", 112 | elapsed_us, begin_in_ns, end_in_ns 113 | ); 114 | #endif 115 | return XDP_DROP; 116 | } 117 | 118 | #ifdef DEBUG 119 | bpf_printk("[XDP] Action=Pass, UDP=true, Status=LookupHit, Key=%d\n", key); 120 | #endif 121 | return XDP_PASS; 122 | } 123 | 124 | #ifdef DEBUG 125 | bpf_printk("[XDP] Action=Pass, UDP=false\n"); 126 | #endif 127 | return XDP_PASS; 128 | } 129 | 130 | 131 | char _license[] SEC("license") = "GPL"; 132 | -------------------------------------------------------------------------------- /src/equic_user.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* Linux OS dependent */ 9 | #include 10 | #include 11 | 12 | 13 | /* libbpf included headers */ 14 | #include 15 | #include 16 | 17 | #include "equic_user.h" 18 | 19 | 20 | /* Network interface index */ 21 | static int EQUIC_IFINDEX; 22 | 23 | 24 | /* eBPF program ID */ 25 | static __u32 EQUIC_PROG_ID; 26 | 27 | 28 | /* XDP Flags used for eQUIC program */ 29 | static unsigned int EQUIC_XDP_FLAGS = XDP_FLAGS_UPDATE_IF_NOEXIST; 30 | 31 | 32 | /** 33 | * SIGINT - Interruption signal callback 34 | * It unloads eQUIC eBPF program from interface 35 | */ 36 | void 37 | equic_sigint_callback (int signal) 38 | { 39 | printf("[eQUIC] Action=exit, Signal=INT\n"); 40 | 41 | __u32 prog_id = 0; 42 | 43 | if ( bpf_get_link_xdp_id(EQUIC_IFINDEX, &prog_id, EQUIC_XDP_FLAGS) ) { 44 | printf("[eQUIC] Error=CallError, Type=BPF, Function=bpf_get_link_xdp_id\n"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | if ( prog_id == EQUIC_PROG_ID ) { 49 | 50 | bpf_set_link_xdp_fd(EQUIC_IFINDEX, -1, EQUIC_XDP_FLAGS); 51 | printf("[eQUIC] Action=Unload, Type=BPF, InterfaceIndex=%d\n", EQUIC_IFINDEX); 52 | 53 | } else if (!prog_id) { 54 | printf("[eQUIC] Error=NotFound, Type=BPF, Message=No program found\n"); 55 | 56 | } else { 57 | printf("[eQUIC] Action=Update, Type=BPF\n"); 58 | } 59 | 60 | exit(EXIT_SUCCESS); 61 | } 62 | 63 | 64 | /** 65 | * SIGTERM - Termination signal callback 66 | * It unloads eQUIC eBPF program from interface 67 | */ 68 | void 69 | equic_sigterm_callback(int signal) 70 | { 71 | printf("[eQUIC] Action=exit, Signal=TERM\n"); 72 | 73 | /* Meanwhile we just call the same behavior as SIGINT */ 74 | equic_sigint_callback(signal); 75 | } 76 | 77 | 78 | /** 79 | * Communicate with Kernel space through eBPF Maps. 80 | * Increment counters map with data from QUIC 81 | */ 82 | void 83 | equic_inc_counter (equic_t *equic, const struct sockaddr *client) 84 | { 85 | 86 | struct sockaddr_in *client_addr_in = (struct sockaddr_in *)client; 87 | 88 | __be32 key = client_addr_in->sin_addr.s_addr; 89 | char *addr = inet_ntoa(client_addr_in->sin_addr); 90 | 91 | int value; 92 | 93 | /* Lookup map using ip address as the key */ 94 | if (bpf_map_lookup_elem(equic->counters_map_fd, &key, &value) == 0) { 95 | 96 | value = value + 1; /* If we find, increment the value */ 97 | 98 | /* Then update the map with the new value */ 99 | if(bpf_map_update_elem(equic->counters_map_fd, &key, &value, BPF_EXIST) == -1) { 100 | 101 | printf("[eQUIC] Action=CounterIncrement, Error=%s, Type=BPFMapUpdate, Function=bpf_map_update_elem\n", strerror(errno)); 102 | } else { 103 | printf("[eQUIC] Action=CounterIncrement, Type=BPFMapUpdate, MapKey=%s, MapValue=%d\n", addr, value); 104 | } 105 | 106 | } else { 107 | 108 | value = 1; // First connection mean value 1 109 | 110 | /* If we didn't find key, create it */ 111 | if(bpf_map_update_elem(equic->counters_map_fd, &key, &value, BPF_NOEXIST) == -1) { 112 | 113 | /* Otherwise print the error */ 114 | printf("[eQUIC] Action=CounterCreation, Error=%s, Type=BPFMapUpdate, Function=bpf_map_update_elem\n", strerror(errno)); 115 | } else { 116 | 117 | printf("[eQUIC] Action=CounterCreation, Type=BPFMapUpdate, MapKey=%s, MapValue=%d\n", addr, value); 118 | } 119 | } 120 | } 121 | 122 | 123 | /** 124 | * Communicate with Kernel space through eBPF Maps. 125 | * Increment counters map with data from QUIC 126 | */ 127 | void 128 | equic_dec_counter (equic_t *equic, const struct sockaddr *client) 129 | { 130 | 131 | struct sockaddr_in *client_addr_in = (struct sockaddr_in *)client; 132 | 133 | __be32 key = client_addr_in->sin_addr.s_addr; 134 | char *addr = inet_ntoa(client_addr_in->sin_addr); 135 | 136 | int value; 137 | 138 | /* Lookup map using ip address as the key */ 139 | if (bpf_map_lookup_elem(equic->counters_map_fd, &key, &value) == 0) { 140 | 141 | if (value == 0) { 142 | return; // No counters to be udpated 143 | } 144 | 145 | /* Otherwise, decrement the current counter */ 146 | value = value - 1; 147 | 148 | /* Then update the map with the new value */ 149 | if(bpf_map_update_elem(equic->counters_map_fd, &key, &value, BPF_EXIST) == -1) { 150 | 151 | printf("[eQUIC] Action=CounterDecrement, Error=%s, Type=BPFMapUpdate, Function=bpf_map_update_elem\n", strerror(errno)); 152 | } else { 153 | printf("[eQUIC] Action=CounterDecrement, Type=BPFMapUpdate, MapKey=%s, MapValue=%d\n", addr, value); 154 | } 155 | } 156 | } 157 | 158 | 159 | /** 160 | * Reads the network interface index from 161 | * a given name 162 | */ 163 | void 164 | equic_get_interface (equic_t *equic, char if_name[]) 165 | { 166 | equic->if_index = if_nametoindex(if_name); 167 | EQUIC_IFINDEX = equic->if_index; 168 | 169 | if ( !equic->if_index ) { 170 | printf("[eQUIC] Error=Name to index failed, Type=OS, Interface=%s\n", if_name); 171 | exit(EXIT_FAILURE); 172 | } 173 | 174 | printf("[eQUIC] Action=Read, Type=OS, Interface=%d\n", equic->if_index); 175 | } 176 | 177 | 178 | /** 179 | * Reads the .o object program to be loaded in the 180 | * kernel. This functions reads all maps and the 181 | * kernel hook function 182 | */ 183 | void 184 | equic_read_object (equic_t *equic, char bpf_prog_path[]) 185 | { 186 | struct bpf_prog_load_attr prog_attr = { 187 | .prog_type = BPF_PROG_TYPE_XDP, 188 | .file = bpf_prog_path, 189 | }; 190 | struct bpf_object *obj; 191 | 192 | if ( bpf_prog_load_xattr(&prog_attr, &obj, &equic->prog_fd) ) { 193 | printf("[eQUIC] Error=Load prog attr failed, Type=BPF\n"); 194 | exit(EXIT_FAILURE); 195 | } 196 | printf("[eQUIC] Action=Load, Type=BPF, Object=%s\n", bpf_prog_path); 197 | 198 | struct bpf_map *map = bpf_object__find_map_by_name(obj, "counters"); 199 | if ( !map ) { 200 | printf("[eQUIC] Error=Find map failed, Type=BPF\n"); 201 | exit(EXIT_FAILURE); 202 | } 203 | equic->counters_map_fd = bpf_map__fd(map); 204 | printf("[eQUIC] Action=Load, Type=BPF, Map=counters\n"); 205 | 206 | if ( !equic->prog_fd ) { 207 | printf("[eQUIC] Error=%s, Type=BPF, Function=bpf_prog_load_xattr\n", strerror(errno)); 208 | exit(EXIT_FAILURE); 209 | } 210 | } 211 | 212 | 213 | /** 214 | * Loads the eBPF kernel program on the linux kernel 215 | * XDP hook on the network interface 216 | */ 217 | void 218 | equic_load (equic_t *equic) 219 | { 220 | if (bpf_set_link_xdp_fd(equic->if_index, equic->prog_fd, EQUIC_XDP_FLAGS) < 0) { 221 | printf("[eQUIC] Error=Link setup failed, Type=BPF, Function=bpf_set_link_xdp_fd\n"); 222 | exit(EXIT_FAILURE); 223 | } 224 | 225 | struct bpf_prog_info info = {}; 226 | __u32 info_len = sizeof(info); 227 | 228 | if (bpf_obj_get_info_by_fd(equic->prog_fd, &info, &info_len)) { 229 | printf("[eQUIC] Error=%s, Type=BPF, Function=bpf_obj_get_info_by_fd\n", strerror(errno)); 230 | exit(EXIT_FAILURE); 231 | } 232 | EQUIC_PROG_ID = info.id; 233 | 234 | printf("[eQUIC] Action=Setup, Type=BPF, Hook=XDP\n"); 235 | } 236 | -------------------------------------------------------------------------------- /src/equic_user.h: -------------------------------------------------------------------------------- 1 | #ifndef EQUIC_H 2 | #define EQUIC_H 3 | 4 | #include 5 | 6 | 7 | /* Struct used to store eQUIC public variables */ 8 | struct equic_meta 9 | { 10 | /* Interface Index */ 11 | int if_index; 12 | 13 | /* eBPF program file descriptor */ 14 | int prog_fd; 15 | 16 | /* Counters Map file descriptor */ 17 | int counters_map_fd; 18 | }; 19 | 20 | /* Create a public type to create equic_meta struct */ 21 | typedef struct equic_meta equic_t; 22 | 23 | 24 | 25 | /** 26 | * 27 | * Public functions of eQUIC module 28 | * 29 | */ 30 | void equic_sigint_callback (int signal); 31 | 32 | void equic_sigterm_callback (int signal); 33 | 34 | void equic_get_interface (equic_t *equic, char if_name[]); 35 | 36 | void equic_read_object (equic_t *equic, char bpf_prog_path[]); 37 | 38 | void equic_load (equic_t *equic); 39 | 40 | void equic_inc_counter (equic_t *equic, const struct sockaddr *client); 41 | 42 | void equic_dec_counter (equic_t *equic, const struct sockaddr *client); 43 | 44 | 45 | #endif /* EQUIC_H */ 46 | -------------------------------------------------------------------------------- /ssl/Readme.md: -------------------------------------------------------------------------------- 1 | # SSL 2 | 3 | This directory contains a PEM certificate `cert.pem` and a private key 4 | `private.key` used for testing and local experimentation only. 5 | 6 | Don't use those files in production. If you want to generate self 7 | signed certificate and keys you can use the `generate-key.sh` script 8 | or modify it. 9 | -------------------------------------------------------------------------------- /ssl/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpjCCAo4CCQC8XnbCiOFezjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls 3 | b2NhbGhvc3QwIBcNMjAwODIyMjE0MzAxWhgPMjEyMDA3MjkyMTQzMDFaMBQxEjAQ 4 | BgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB 5 | AMpqBxREzGT/r+A53x/9/9hI+lUknnzQtp74ys6Xdp/0lVs8PD3hPeD9YxBCy7c0 6 | gTQvk9YxkwsonSDCS8BdsHRan0T2CnvGNBSjVowyq7tu7rOcNXJTk9qzj9xbJT1B 7 | 8SwmdWBER+D/YnyKAqRSNUZzRwbDlOX1EawhySOTsbLok0Ssed8fF1B4YVoCNtG3 8 | zotMRkYyiaowZTIid2aYvS2tKsOI8iWRdtDB6KC8FuebvikMw9XmZgNFtnquqea7 9 | wFsWGLzOl8rEaYYuEeEkY6Qpx9ETW8OjZpqq1kzYOaIHpy+q5r262rYC4FkRMf/F 10 | h534ftBrk0GxOoFG+14siJyQsPSyAyt6h9wmPxa8fXHS/sVoviFtCxEhlX8r4Two 11 | zY8b56on9TQ8rfLpEPMWPCrgWa4w20BhFCvJLS8i8f6fWu8HHPs5YxOscGT/NpDg 12 | rt6c3daZ89HVDJQGd3rCepb2ETGaMOzOQTwromj+NClhPVztsMWMhkfrE/PFNkQv 13 | 3O/FJs5nHk0BU7eHb5ghKGUmTpBrtW3bNUbOgYiL8LnEYpXt+slVl8SqhJLIn6OG 14 | 0h8IWYGy5bM57zBWjK00W/KLZ+WI3PJUidfPr+AS14g+L2dgglsiskg1g2L9eE5S 15 | V2ulheXqAEtUJPYIe3cWvrrREL2WzAZZEzBlhWlcsly5AgMBAAEwDQYJKoZIhvcN 16 | AQELBQADggIBAEevdnYfCdd+JlKqm6+rfDd4p/xgmCUUUbgWsV5jZ0JsnQwtcJzB 17 | hOIU9o5FWRVQ71uQwpFOTJOqeDeCnR+XQATm9ujm9Ixd94aYqRhCa/Xgo0IMdwl3 18 | cXo/oK3LXWF9oAHu4eMcKENyE4LG8HoktyZYfagt4horbeAqMInx4vS/Nkj/B5jq 19 | 4Aw5XxtVltZxD2/g8b4w0R8z19yyk0MhpIG1DDaCSx7tWvRmlCPr9V9yZJr2YoQi 20 | 1K9CsR+ad3N+utIsxBCWo85oQKzxpBOLXkFz+yYmPiSDb3Cm5x8FA/GXf637LCEq 21 | 27VpIu4ra3gkQFycxYoLtVnfW6HFcoLvHSMwci/OZSg4JUbtvul2fPxaoNViyyVv 22 | 718q7+gQSf8Moo/0W4t1uFev1E4AVu6NJzQBZ81kK7EPNT/1cWfv7LxV1HsmjyGu 23 | GyWiaGtVEFrx47xydVMSiMuLllzcUA8FZPdSxiEkIzdBRqLMAoDaR8DPVy55NJTd 24 | FmeYyKBfUxqI963qUE7p9TtnYpf9d/PdAuvTNkkzGZBnFRunoU9HaNsOljE1cjW3 25 | sI4FpAkrzpbF+tS5KFu54FhK4Z0FuuXEqavmmcWMjpC/nzN03ZEQuUZps12EtrN8 26 | Mhk8Js2CG0BagvWfeurFWJ9bH4FwLhs8vpGD8Gv72uGJGNyF4zNbjpqA 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /ssl/generate-key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This repository already comes with a certificate and private key 5 | # for testing purposes. But you can use or modify this small script 6 | # in order to generate new certificates and keys 7 | # 8 | 9 | 10 | # Generates a self signed key with 100 years for expiration. 11 | # Used for testing purpose 12 | openssl req -x509 \ 13 | -newkey rsa:4096 \ 14 | -keyout private.key \ 15 | -out cert.pem \ 16 | -days 36500 \ 17 | -nodes \ 18 | -subj "/CN=localhost" 19 | -------------------------------------------------------------------------------- /ssl/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQDKagcURMxk/6/g 3 | Od8f/f/YSPpVJJ580Lae+MrOl3af9JVbPDw94T3g/WMQQsu3NIE0L5PWMZMLKJ0g 4 | wkvAXbB0Wp9E9gp7xjQUo1aMMqu7bu6znDVyU5Pas4/cWyU9QfEsJnVgREfg/2J8 5 | igKkUjVGc0cGw5Tl9RGsIckjk7Gy6JNErHnfHxdQeGFaAjbRt86LTEZGMomqMGUy 6 | IndmmL0trSrDiPIlkXbQweigvBbnm74pDMPV5mYDRbZ6rqnmu8BbFhi8zpfKxGmG 7 | LhHhJGOkKcfRE1vDo2aaqtZM2DmiB6cvqua9utq2AuBZETH/xYed+H7Qa5NBsTqB 8 | RvteLIickLD0sgMreofcJj8WvH1x0v7FaL4hbQsRIZV/K+E8KM2PG+eqJ/U0PK3y 9 | 6RDzFjwq4FmuMNtAYRQryS0vIvH+n1rvBxz7OWMTrHBk/zaQ4K7enN3WmfPR1QyU 10 | Bnd6wnqW9hExmjDszkE8K6Jo/jQpYT1c7bDFjIZH6xPzxTZEL9zvxSbOZx5NAVO3 11 | h2+YIShlJk6Qa7Vt2zVGzoGIi/C5xGKV7frJVZfEqoSSyJ+jhtIfCFmBsuWzOe8w 12 | VoytNFvyi2fliNzyVInXz6/gEteIPi9nYIJbIrJINYNi/XhOUldrpYXl6gBLVCT2 13 | CHt3Fr660RC9lswGWRMwZYVpXLJcuQIDAQABAoICAEdd+N8Bntthd15kqTH7eXCF 14 | 0pWgkqzJXgpRCBitOKsPLnX7l/Xta8bfMEV0d6VBwuhmegaEdibyC+yX9ON/ZQ6x 15 | QQSsh2e6q3Q3XvWRjBFK/KihRArqQvBSbT+xd0DQ9jqt2Ka3jGxdPxlQA7xyK8yK 16 | KpnM7bvim3eIdgWwNnAxJnOJKHMt4CeEOjblHvT0jMN6gjCP8w/pflSd6GFpKxJm 17 | wvQsXcw3LKBQLjg1+RFyTSrmEEpjsMYolz1MRiVPP3QeJ1yyrrxUYixoI++wx90C 18 | yiwCWlFxVGTmJSb0+eUSM7VP2Yk9z41rGDTvJiTbPYTKL9RGiaYY2dro7wn2Q7fn 19 | A9v0hCSqipxQUI/iqIpG8Q6q9hLVxItpMkWytGQn/zPRGtbHZcNoHKF2HxnsxJln 20 | ArAewI0Wz9oQPlEblkTxznBFMR9PvrfiSyKax1XoHKHHmzIFzIKUyepKEQm0KoVw 21 | +z7THZBqBjI+F8A9lws37xQvvizMbdLFqdrXT7eKhRn/vdM6PxqahiW9NkD9eGHY 22 | xd1ZSXsVRWLUg384LjDrt5Om6pU2Qtdx7QpZhsIH13MUEL77yvGqjfRFY1Pk5c// 23 | 5gA5X9YHRMvxflHQJ4jex4jAqB4wp08NanrbCxHQL5/24Iw4e+fIBj7KcKlZcpAu 24 | yHl++9clEzmsjVlTqw2xAoIBAQD3/Fss9gluUSYt6zQNwqUAx46FBUduvR+L0mNG 25 | eX4oqlgXF83gMiaS5w6l74bxXzrmAOkRqEYXMWPHvYZm1/zi39h76iyDBVwTptOF 26 | aKonNIyqj83QNCuQ1juGzJDaLfgX2jSwyEvp845KmyZiz1IHVhKnzCi5FJO7hb6W 27 | HWs7Wahb+iUjohOPIAAWQiGc2e0E/xuSHx2dHV0w96uXIbXG65Ri48+rpZCrW0LY 28 | jIXNMBBM60hCHymrk9BBpm0b12G1t9WCil3D6qX6lnm1A8dXpuTwxFmgp66Hth+T 29 | ZADjRBbJmaxFvc3p8UQEVbunkx6/k6stPJarAEceIQCJ5CdlAoIBAQDQ9KWodVbM 30 | PbmxgG52Zl5ZIXiK+M/vfN81Pw+QyJ4ZoElfhMe0RhA+9wMTS/cCH/lSXhj2SdXQ 31 | GKMcoXGbdYBg1Ps6L2w+oARzc4fcOtfwuOfuD9dugg7FNZ0WnSrWq5fcUAzwXKhM 32 | DO5jYfhw7ORkK5nr0U6isKyO3xEnUEExner+EM8nE0aUJoefIgb9GHebkbT2Ol2Y 33 | ahg8ighBlLH2hiK9xejZCrlVcxKN78eDfDOsr76R0x4shf9s8qT/9YypQul3SxvT 34 | c6FOFsJ7dZf+Z13c6kG2czuOQRnrCdh4nuQA/4YE6R0P/04qZdZEITuZvZehhvLJ 35 | 3eEdvxSZ/RzFAoIBADo2KtDrEFutAOFWjOmN/12nenUPQC1mh7kyscshSdQR6IvD 36 | MrimDRf+1RlumKagTBshq5RRg+J93YZNnvcVTG+VTCQKXFiFMZALYSu33flGBJ8l 37 | EpMFLBTbtS13Z6lPkSVIO4Yj/m30ljo9IU2gjQjb05U//28yvVsPkiV60mkkKrks 38 | hvtOWHkIcfzHmUxQe6rol3g9hZ4T/oQ4Hzdi2mDPfA2anG0WeFdBHPfoRKBd3/Wu 39 | ZU/gMM/t6rz+bC5U6MLXXG9AO7J5qmygVgF/2ZNhwewiq0djFT0WK2NV3j97BD5e 40 | rGnSnDuGPwkldt9nUZh2+X+s8VFCx27zIEU+IWECggEAXgcpIIQk2eWQ0So9CvSH 41 | 0k34g3jxgRBNFRXaeEl+s4NoYR5MC3hXyS24aY+OiJtAw2rqirGxEGJ1y+VuTQl0 42 | Cp7sqkM6z3eMaTN82IJ2N7HNVFg2lljaMq4skUeKRm2S+aXgHK1lYkuTXo9wj8ha 43 | AiWIrxBR9W3RSL1iZ7F6lMw+ObPws+G9qd8oulhCktPWxt+rcf3TrklXvK5UhsZV 44 | ytyTd5g+bqSfv673k31p52YVjsjK1a4N7vUherI5w6CIeoZMrszSqbVnyc7+FzZU 45 | fjufhlC331U9YIc1FYopqiW97iEN+tm3pUI6t2N6JtBZlJY3TI2AZYx9x7f7mkvH 46 | DQKCAQAxBQBG1daZSYbhT1ZM8EJG1BZND7xDhGIrpH2N5tgZEmJUSifh8E13PheI 47 | Dnrdael+tTbOtP/iviVGHxJUCNqEUJF0k039GIJ53IEWhiOaA1RX/Lh+cOXW4k0a 48 | Vza6JfL7oTmfmO1Mp6GlYtNADXuTXMRUd4i7sCCuLiglM+ZUbZTQy329bvOBbzls 49 | fgwz7/CA1zd1QpjUtvIgQnT+8f2Clbu7Qg65jDNTnDeIKkqB4OmZkgzaXehwqxqP 50 | WPK8WdDXYzDFNZN6t8d/g0sP6FeXCuoI+jgblBM5Jx2+pGoHqk4ZfRsHzgRcGYOE 51 | ce+Oj+D3QbuNjdKXfZli53wbQ0Bw 52 | -----END PRIVATE KEY----- 53 | --------------------------------------------------------------------------------