diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | url_handler/.gitignore | 1 | ||||
-rw-r--r-- | url_handler/Makefile | 22 | ||||
-rw-r--r-- | url_handler/handler.cpp | 146 | ||||
-rw-r--r-- | url_handler/handler.h | 9 | ||||
-rw-r--r-- | url_handler/main.cpp | 159 | ||||
-rwxr-xr-x | url_handler/ping_script.py | 27 |
8 files changed, 367 insertions, 0 deletions
@@ -1,3 +1,4 @@ +.clangd/ *.o client server @@ -10,9 +10,11 @@ OBJECTS = $(patsubst %.c,%.o,$(SOURCES)) .PHONY: all clean all: $(TARGETS) + @$(MAKE) -C url_handler all clean: rm -f $(TARGETS) *.o + @$(MAKE) -C url_handler clean $(TARGETS): %: %.o $(OBJECTS) diff --git a/url_handler/.gitignore b/url_handler/.gitignore new file mode 100644 index 0000000..8cffaca --- /dev/null +++ b/url_handler/.gitignore @@ -0,0 +1 @@ +url_handler diff --git a/url_handler/Makefile b/url_handler/Makefile new file mode 100644 index 0000000..3c03697 --- /dev/null +++ b/url_handler/Makefile @@ -0,0 +1,22 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra -O2 -g -std=c++17 -fwrapv -pthread +LDFLAGS = -pthread + +TARGET = url_handler + +SOURCES = $(wildcard *.cpp) +OBJECTS = $(SOURCES:.cpp=.o) + +.PHONY: all clean + +all: $(TARGET) + +clean: + rm -f $(TARGET) *.o + + +$(TARGET): $(OBJECTS) + $(CXX) $^ -o $@ $(LDFLAGS) + +%.o: %.cpp $(wildcard *.h) + $(CXX) $(CXXFLAGS) -c -o $@ $< diff --git a/url_handler/handler.cpp b/url_handler/handler.cpp new file mode 100644 index 0000000..b202d5b --- /dev/null +++ b/url_handler/handler.cpp @@ -0,0 +1,146 @@ +#include <fstream> +#include <filesystem> +#include <unordered_map> +#include <mutex> +#include <thread> +#include <memory> +#include <cstdlib> +#include "handler.h" + +using namespace std; + + +static string shell_escape(const string &str) { + string res; + res.reserve(str.size() + 16); + res += '\''; + + for (char c : str) { + if (c == '\'') res += "'\"'\"'"; + else res += c; + } + + res += '\''; + return res; +} + +static string read_file(const string &fname) { + ifstream f(fname); + if (!f) return {}; + + string contents; + char buffer[4096]; + while (true) { + f.read(buffer, sizeof buffer); + contents.insert(contents.size(), buffer, f.gcount()); + if (!f) break; + } + + return contents; +} + +static string download_url(const string &url) { + string tempdir_name = "/tmp/tmp.url_handler.XXXXXX"; + if (mkdtemp(tempdir_name.data()) == nullptr) { + perror("mkdtemp"); + return {}; + } + + string tempfile_name = tempdir_name + "/file"; + + string cmd = "curl -sL " + shell_escape(url) + " >" + shell_escape(tempfile_name); + // fprintf(stderr, "cmd = %s\n", cmd.data()); + + system(cmd.data()); + + string response = read_file(tempfile_name); + filesystem::remove_all(tempdir_name); + + fprintf(stderr, "Received response for url '%s'\n", url.data()); + + return response; +} + +struct State { + string url; + + thread download_thread; + + // mutex protects 'response' and 'response_present'; the download thread + // sets 'response_present' to true and simultaneously 'response' to the + // right value. After 'response_present' can be observed to be true by the + // main thread, the download thread doesn't touch anything anymore. + mutex res_mt; + bool response_present = false; + string response; + + // owned by host thread + size_t rescur = 0; +}; + +static unordered_map<uint32_t, unique_ptr<State>> state_map; + +uint16_t handle_icmp(uint32_t source_addr, const array<uint8_t, 16> &payload) { + auto state_it = state_map.find(source_addr); + if (state_it == state_map.end()) { + state_it = state_map.emplace(source_addr, make_unique<State>()).first; + } + State &state = *state_it->second; + + switch (payload[0]) { + case 100: // url fragment + // fprintf(stderr, "msg(%u): url fragment\n", source_addr); + { + lock_guard<mutex> guard(state.res_mt); + if (state.response_present) return 0; + } + for (size_t i = 1; i < 16; i++) { + if (payload[i] == '\0') { + fprintf(stderr, "URL received: <%s>\n", state.url.data()); + state.download_thread = thread([&state]() { + string response = download_url(state.url); + + { + lock_guard<mutex> guard(state.res_mt); + state.response = move(response); + state.response_present = true; + } + + // fprintf(stderr, "[dlth] response received: <%s>\n", state.response.data()); + }); + break; + } + state.url.push_back(payload[i]); + } + return 1; + + case 101: { // query whether response is ready + // fprintf(stderr, "msg(%u): ready query\n", source_addr); + lock_guard<mutex> guard(state.res_mt); + return state.response_present ? 1 : 0; + } + + case 102: { // get response + // fprintf(stderr, "msg(%u): get_response\n", source_addr); + { + lock_guard<mutex> guard(state.res_mt); + if (!state.response_present) return 0; + } + + uint16_t retval = 0; + for (size_t i = 0; i < 2 && state.rescur < state.response.size(); i++) { + retval |= (uint16_t)state.response[state.rescur] << (8 * i); + state.rescur++; + } + + if (state.rescur == state.response.size()) { + state.download_thread.join(); // should already have exited + state_map.erase(state_it); + } + // fprintf(stderr, " -> retval %016X\n", (unsigned)retval); + return retval; + } + } + + return 0; +} diff --git a/url_handler/handler.h b/url_handler/handler.h new file mode 100644 index 0000000..135e5b7 --- /dev/null +++ b/url_handler/handler.h @@ -0,0 +1,9 @@ +#pragma once + +#include <array> +#include <cstdint> + +using namespace std; + + +uint16_t handle_icmp(uint32_t source_addr, const array<uint8_t, 16> &payload); diff --git a/url_handler/main.cpp b/url_handler/main.cpp new file mode 100644 index 0000000..47166db --- /dev/null +++ b/url_handler/main.cpp @@ -0,0 +1,159 @@ +#include <vector> +#include <string> +#include <tuple> +#include <utility> +#include <cstdio> +#include <cstring> +#include <cassert> +#include <unistd.h> +#include <signal.h> +#include <sys/select.h> +#include <sys/wait.h> +#include "handler.h" + +using namespace std; + + +struct Pipes { + int rfd, wfd; +}; + +static pair<pid_t, Pipes> start_process(const vector<string> &cmd) { + int host2child[2], child2host[2]; + if (pipe(host2child) < 0 || pipe(child2host) < 0) { + perror("pipe"); + exit(1); + } + + pid_t pid = fork(); + if (pid == 0) { + close(host2child[1]); + close(child2host[0]); + dup2(host2child[0], STDIN_FILENO); + dup2(child2host[1], STDOUT_FILENO); + + vector<char*> argv(cmd.size() + 1); + for (size_t i = 0; i < cmd.size(); i++) { + argv[i] = (char*)cmd[i].data(); + fprintf(stderr, "argv[%zu] = %p (%s)\n", i, cmd[i].data(), cmd[i].data()); + } + argv[cmd.size()] = nullptr; + + execvp(argv[0], argv.data()); + perror("execvp"); + exit(1); + } + + if (pid < 0) { + perror("fork"); + exit(1); + } + + close(host2child[0]); + close(child2host[1]); + return make_pair(pid, Pipes{child2host[0], host2child[1]}); +} + +static bool poll_process_exited(pid_t pid) { + int status; + pid_t ret = waitpid(pid, &status, WNOHANG); + if (ret == 0) return false; + if (ret < 0) { + if (errno == EINTR) return false; // wut? we said WNOHANG, right? + perror("wait"); + return true; + } + assert(ret == pid); + return WIFEXITED(status); +} + +static void wait_process_exited(pid_t pid) { + while (true) { + int status; + pid_t ret = waitpid(pid, &status, 0); + if (ret < 0) { + if (errno == EINTR) continue; + perror("wait"); + exit(1); + } + if (ret == 0) continue; // wut? + assert(ret == pid); + if (WIFEXITED(pid)) break; + } +} + +int main(int argc, char **argv) { + vector<string> server_args(argc - 1); + for (int i = 0; i < argc - 1; i++) server_args[i] = argv[i + 1]; + + // make sure we actually receive the signal + signal(SIGCHLD, [](int) {}); + + pid_t child_pid; + Pipes pipes; + tie(child_pid, pipes) = start_process(server_args); + + int exit_code = 0; + + while (true) { + fd_set inset; + FD_ZERO(&inset); + FD_SET(pipes.rfd, &inset); + + int ret = select(pipes.rfd + 1, &inset, nullptr, nullptr, nullptr); + + if (poll_process_exited(child_pid)) { + child_pid = -1; + goto exit_cleanup; + } + + if (ret < 0) { + if (errno == EINTR) continue; + perror("select"); + exit_code = 1; + goto exit_cleanup; + } + + if (FD_ISSET(pipes.rfd, &inset)) { + uint8_t buffer[24]; + size_t cursor = 0; + while (cursor < 24) { + ssize_t nread = read(pipes.rfd, buffer + cursor, 24 - cursor); + if (nread < 0) { + perror("read"); + goto exit_cleanup; + } + cursor += nread; + } + + if (memcmp(buffer, "payl", 4) != 0) { + fprintf(stderr, "Invalid event message from server: id bytes '%c%c%c%c'\n", + buffer[0], buffer[1], buffer[2], buffer[3]); + fprintf(stderr, "<"); + fwrite(buffer, 1, 24, stderr); + fprintf(stderr, ">\n"); + exit_code = 1; + goto exit_cleanup; + } + + uint32_t source_addr = *(uint32_t*)&buffer[4]; + array<uint8_t, 16> payload; + memcpy(payload.data(), &buffer[8], 16); + uint16_t seqnum = handle_icmp(source_addr, payload); + + if (write(pipes.wfd, &seqnum, 2) != 2) { + perror("write"); + exit_code = 1; + goto exit_cleanup; + } + } + } + +exit_cleanup: + if (child_pid != -1) { + kill(child_pid, SIGTERM); + wait_process_exited(child_pid); + } + + return exit_code; +} diff --git a/url_handler/ping_script.py b/url_handler/ping_script.py new file mode 100755 index 0000000..30f15e5 --- /dev/null +++ b/url_handler/ping_script.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import sys, os, time, subprocess + +url = sys.argv[1] +if len(url) % 15 == 0: url += "\0" +while len(url) % 15 != 0: url += "\0" + +for i in range(len(url) // 15): + subprocess.check_call(["ping", "-c", "1", "-p", "64" + "".join("{:02X}".format(ord(x)) for x in url[15*i:15*i+15]), "127.0.0.1"], stdout=subprocess.DEVNULL) + +while True: + out = subprocess.check_output(["ping", "-c", "1", "-p", "65", "127.0.0.1"], stderr=subprocess.STDOUT).decode("utf-8") + if out.find("icmp_seq=1 ") != -1: + break + time.sleep(0.2) + +while True: + out = subprocess.check_output(["ping", "-c", "1", "-p", "66", "127.0.0.1"], stderr=subprocess.STDOUT).decode("utf-8") + idx = out.find("icmp_seq=") + idx2 = out.find(" ", idx) + seqnum = int(out[idx+9:idx2]) + n0 = seqnum % 256 + n1 = seqnum >> 8 + if n0 == 0: break + print(chr(n0), end="") + if n1 == 0: break + print(chr(n1), end="") |