├── LICENSE ├── Makefile ├── README.md ├── build └── .gitkeep └── src ├── announces.c ├── announces.h ├── conf.c ├── conf.h ├── dht.c ├── dht.h ├── ext-cli.c ├── ext-cli.h ├── ext-lpd.c ├── ext-lpd.h ├── kad.c ├── kad.h ├── log.c ├── log.h ├── main.c ├── main.h ├── net.c ├── net.h ├── peerfile.c ├── peerfile.h ├── results.c ├── results.h ├── unix.c ├── unix.h ├── utils.c ├── utils.h ├── windows.c └── windows.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 by Moritz Warning 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS += -Wall -Wwrite-strings -pedantic -std=gnu99 3 | LDFLAGS += -lc 4 | FEATURES ?= cli lpd debug 5 | 6 | OBJS = build/kad.o build/log.o build/results.o \ 7 | build/conf.o build/net.o build/utils.o \ 8 | build/announces.o build/peerfile.o 9 | 10 | ifeq ($(OS),Windows_NT) 11 | OBJS += build/unix.o build/windows.o 12 | else 13 | OBJS += build/unix.o 14 | endif 15 | 16 | .PHONY: all clean strip install \ 17 | dhtd install uninstall 18 | 19 | all: dhtd 20 | 21 | ifeq ($(findstring cli,$(FEATURES)),cli) 22 | OBJS += build/ext-cli.o 23 | CFLAGS += -DCLI 24 | endif 25 | 26 | ifeq ($(findstring lpd,$(FEATURES)),lpd) 27 | OBJS += build/ext-lpd.o 28 | CFLAGS += -DLPD 29 | endif 30 | 31 | ifeq ($(findstring debug,$(FEATURES)),debug) 32 | CFLAGS += -g -DDEBUG 33 | endif 34 | 35 | build/%.o : src/%.c src/%.h 36 | $(CC) $(CFLAGS) -c -o $@ $< 37 | 38 | dhtd: build/main.o $(OBJS) $(EXTRA) 39 | $(CC) $(CFLAGS) build/main.o $(OBJS) $(LDFLAGS) -o build/dhtd 40 | ln -s dhtd build/dhtd-ctl 2> /dev/null || true 41 | 42 | clean: 43 | rm -rf build/* 44 | 45 | install: 46 | cp build/dhtd $(DESTDIR)/usr/bin/ 2> /dev/null || true 47 | ln -s dhtd $(DESTDIR)/usr/bin/dhtd-ctl || true 48 | 49 | uninstall: 50 | rm $(DESTDIR)/usr/bin/dhtd 2> /dev/null || true 51 | rm $(DESTDIR)/usr/bin/dhtd-ctl 2> /dev/null || true 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DHTd 2 | 3 | A standalone [Kademlia DHT](https://en.wikipedia.org/wiki/Kademlia) (Distribute Hash Table) to connect to the mainline BitTorrent network. 4 | 5 | A Distributed Hash Table (DHT) is a distributed system storing key/value pairs used by BitTorrent clients to find peers. DHTd can be used in the same way to find the IP address of other instances in a decentralized way without any authentication. Or you can use it as a bootstrap node. 6 | 7 | Otherwise you can think of it as a [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi) for people who are into decentralized networks. :-) 8 | 9 | Features: 10 | 11 | * small at 110KB 12 | * announce and lookup for hashes 13 | * local peer discovery 14 | * run as bootstrap node 15 | * scripting capabilities 16 | * only libc as dependency 17 | * available for OpenWrt 18 | 19 | ## Quick Start 20 | 21 | ### Compile 22 | 23 | Compilation is rather simple: 24 | 25 | ``` 26 | $ git clone https://github.com/mwarning/dhtd.git 27 | $ cd dhtd 28 | $ make 29 | [...] 30 | $ ./build/dhtd -h 31 | ``` 32 | 33 | (The `$` is the terminal prompt, it is included here to distinguish commands from program output) 34 | 35 | ### Run 36 | 37 | Run DHTd in background: 38 | 39 | ``` 40 | $ dhtd --daemon --peer bttracker.debian.org:6881 --peer router.bittorrent.com:6881 41 | ``` 42 | 43 | Example output after a few minutes: 44 | 45 | ``` 46 | $ dhtd-ctl status 47 | DHTd 1.0.0 ( cli debug lpd ) 48 | DHT id: 24e56b174415846be9050d628e8d8c0eda42de96 49 | DHT uptime: 120d6h 50 | DHT listen on: IPv4+IPv6 / device: / port: 6881 51 | DHT nodes: 1090 IPv4 (402 good), 373 IPv6 (349 good) 52 | DHT storage: 280 entries with 648 addresses 53 | DHT searches: 0 IPv4 (0 done), 0 IPv6 active (0 done) 54 | DHT announcements: 0 55 | DHT blocklist: 3 56 | DHT traffic: 24.6 G, 6.8 K/s (in) / 68.5 G, 2.5 K/s (out) 57 | ``` 58 | 59 | Start a search: 60 | 61 | ``` 62 | $ dhtd-ctl search 6f84758b0ddd8dc05840bf932a77935d8b5b8b93 63 | Search started. 64 | ``` 65 | (the id is from a magnet link of a Debian Linux torrent file) 66 | 67 | After a few seconds: 68 | 69 | ``` 70 | $ dhtd-ctl results 6f84758b0ddd8dc05840bf932a77935d8b5b8b93 71 | [2a01:e0a:ea:d9d0::1]:24007 72 | [2a01:e0a:5c4:f490::1]:26915 73 | [2001:470:8:62b:c41a:db05:69db:bb]:59863 74 | [2001:bc8:32d7:25e::42]:51413 75 | [2003:d6:af28:9200:dea6:32ff:fec4:6592]:50476 76 | [2003:e6:2f01:8400::65e]:63746 77 | [2003:e6:2f01:8400:6a6:6b4b:8433:95dd]:63746 78 | [2003:f6:3f2b:3300::1]:51413 79 | [2601:18d:8d7f:e566:beae:c5ff:fe66:ec70]:6881 80 | [2603:8001:4000:6d07:549d:1d6a:9b33:b14f]:38934 81 | ``` 82 | 83 | Note: 84 | - Searches/Results are discarded after about 62 minutes. 85 | - You cannot search for the id of the node itself, only ids that someone announced. 86 | - Use `lookup` to start/continue a search and also print out results. 87 | - Use `--execute ` command line argument to execute a script for each result. 88 | 89 | ## Command Line Arguments 90 | 91 | Startup command line arguments for `dhtd`. 92 | 93 | * `--announce` *id*[:*port*] 94 | Announce a id and optional port. 95 | This option may occur multiple times. 96 | * `--peerfile` *file* 97 | Import/Export peers from and to a file. 98 | * `--peer` *address* 99 | Add a static peer address. 100 | This option may occur multiple times. 101 | * `--execute` *file* 102 | Execute a script for each result. 103 | * `--port` *port* 104 | Bind DHT to this port. 105 | Default: 6881 106 | * `--config` *file* 107 | Provide a configuration file with one command line 108 | option on each line. Comments start after '#'. 109 | * `--ifname` *interface* 110 | Bind to this interface. 111 | Default: *any* 112 | * `--daemon`, `-d` 113 | Run the node in background. 114 | * `--verbosity` *level* 115 | Verbosity level: quiet, verbose or debug. 116 | Default: verbose 117 | * `--user` *user* 118 | Change the UUID after start. 119 | * `--pidfile` *file* 120 | Write process pid to a file. 121 | * `--ipv4`, `-4`, `--ipv6`, `-6` 122 | Enable IPv4 or IPv6 only mode. 123 | Default: IPv4+IPv6 124 | * `--lpd-disable` 125 | Disable multicast to discover local peers. 126 | * `--cli-disable-stdin` 127 | Disable the local control interface. 128 | * `--cli-path` *path* 129 | Bind the remote control interface to this unix socket path. 130 | Default: /tmp/dhtd.sock 131 | * `--help`, `-h` 132 | Print this help. 133 | * `--version`, `-v` 134 | Print program version. 135 | 136 | ## Command Line Interface 137 | 138 | List of commands that can be send to a running `dhtd` instance via the command line control interface `dhtd-ctl`. 139 | 140 | * `status` 141 | The current state of this node. 142 | * `lookup ` 143 | Start search and print results. 144 | * `search ` 145 | Start a search for announced values. 146 | * `results ` 147 | Print the results of a search. 148 | * `announce-start [:]` 149 | Start to announce an id along with a network port. 150 | * `announce-stop ` 151 | Stop the announcement. 152 | * `searches` 153 | Print a list of all searches. They expire after 62min. 154 | * `announcements` 155 | Print a list of all announcements. 156 | * `peer
:` 157 | Add a peer by address. 158 | * `constants|blocklist|peers|buckets|storage` 159 | Print various internal data. 160 | 161 | Legend: 162 | 163 | `` 20 bytes as hexadecimal string 164 | 165 | `` Network port number between 1-65536 166 | 167 | `
` IPv4 or IPv6 address 168 | 169 | ## License 170 | 171 | MIT/X11 172 | 173 | ## Authors 174 | 175 | * DHTd: Moritz Warning (http://github.com/mwarning/dhtd) 176 | * Kademlia DHT: Juliusz Chroboczek (https://github.com/jech/dht) 177 | 178 | ## Links 179 | 180 | * [kademlia-dht](https://github.com/quarterblue/kademlia-dht) standalone DHT implementation in Rust 181 | * Kademlia DHT formal [specification](http://maude.sip.ucm.es/kademlia/files/pita_kademlia.pdf) 182 | -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarning/dhtd/fa9d0ae8541d53e08d5a2d8e3cccf6c7e3ab5c91/build/.gitkeep -------------------------------------------------------------------------------- /src/announces.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "log.h" 8 | #include "conf.h" 9 | #include "utils.h" 10 | #include "net.h" 11 | #include "kad.h" 12 | #include "announces.h" 13 | 14 | 15 | // Announce values every 20 minutes 16 | #define ANNOUNCES_INTERVAL (20*60) 17 | 18 | 19 | static time_t g_announces_expire = 0; 20 | static time_t g_announces_announce = 0; 21 | static struct announcement_t *g_values = NULL; 22 | 23 | 24 | struct announcement_t* announces_get(void) 25 | { 26 | return g_values; 27 | } 28 | 29 | struct announcement_t* announces_find(const uint8_t id[]) 30 | { 31 | struct announcement_t *value; 32 | 33 | value = g_values; 34 | while (value) { 35 | if (id_equal(id, value->id)) { 36 | return value; 37 | } 38 | value = value->next; 39 | } 40 | return NULL; 41 | } 42 | 43 | void announces_print(FILE *fp) 44 | { 45 | time_t now = time_now_sec(); 46 | int value_counter = 0; 47 | int nodes_counter = kad_count_nodes(false); 48 | struct announcement_t *value = g_values; 49 | 50 | fprintf(fp, "Announcements:\n"); 51 | fprintf(fp, "interval: %dm\n", ANNOUNCES_INTERVAL / 60); 52 | 53 | while (value) { 54 | fprintf(fp, " id: %s\n", str_id(value->id)); 55 | fprintf(fp, " port: %d\n", value->port); 56 | if (value->refresh < now) { 57 | if (nodes_counter > 0) { 58 | fprintf(fp, " refresh: now\n"); 59 | } else { 60 | // no nodes we can announce to 61 | fprintf(fp, " refresh: wait\n"); 62 | } 63 | } else { 64 | fprintf(fp, " refresh: in %d min\n", (int) ((value->refresh - now) / 60)); 65 | } 66 | 67 | if (value->lifetime == LONG_MAX) { 68 | fprintf(fp, " lifetime: entire runtime\n"); 69 | } else { 70 | fprintf(fp, " lifetime: %d min left\n", (int) ((value->lifetime - now) / 60)); 71 | } 72 | 73 | value_counter++; 74 | value = value->next; 75 | } 76 | 77 | fprintf(fp, " Found %d entries.\n", value_counter); 78 | } 79 | 80 | // Announce a sanitized query 81 | struct announcement_t *announces_add(FILE *fp, uint8_t id[], int port, time_t lifetime) 82 | { 83 | struct announcement_t *cur; 84 | struct announcement_t *new; 85 | time_t now = time_now_sec(); 86 | 87 | // port must be != 0 88 | if (!port_valid(port)) { 89 | return NULL; 90 | } 91 | 92 | // Value already exists - refresh 93 | if ((cur = announces_find(id)) != NULL) { 94 | cur->refresh = now - 1; 95 | 96 | if (lifetime > now) { 97 | cur->lifetime = lifetime; 98 | } 99 | 100 | // Trigger immediate handling 101 | g_announces_announce = 0; 102 | 103 | if (fp) fprintf(fp, "Announcement already exists. Triggered again.\n"); 104 | return cur; 105 | } 106 | 107 | // Prepend new entry 108 | new = (struct announcement_t*) calloc(1, sizeof(struct announcement_t)); 109 | memcpy(new->id, id, SHA1_BIN_LENGTH); 110 | new->port = port; 111 | new->refresh = now - 1; // Send first announcement as soon as possible 112 | new->lifetime = lifetime; 113 | 114 | if (lifetime == LONG_MAX) { 115 | log_debug("Add announcement for %s:%hu. Keep alive for entire runtime.", str_id(id), port); 116 | } else { 117 | log_debug("Add announcement for %s:%hu. Keep alive for %lu minutes.", str_id(id), port, (lifetime - now) / 60); 118 | } 119 | 120 | // Prepend to list 121 | new->next = g_values; 122 | g_values = new; 123 | 124 | // Trigger immediate handling 125 | g_announces_announce = 0; 126 | 127 | if (fp) fprintf(fp, "Announcement started (port %d).\n", port); 128 | 129 | return new; 130 | } 131 | 132 | void value_free(struct announcement_t *value) 133 | { 134 | free(value); 135 | } 136 | 137 | bool announcement_remove(const uint8_t id[]) 138 | { 139 | struct announcement_t *pre; 140 | struct announcement_t *cur; 141 | 142 | pre = NULL; 143 | cur = g_values; 144 | while (cur) { 145 | if (id_equal(id, cur->id)) { 146 | if (pre) { 147 | pre->next = cur->next; 148 | } else { 149 | g_values = cur->next; 150 | } 151 | value_free(cur); 152 | return true; 153 | } 154 | pre = cur; 155 | cur = cur->next; 156 | } 157 | 158 | return false; 159 | } 160 | 161 | static void announces_expire(void) 162 | { 163 | struct announcement_t *pre; 164 | struct announcement_t *cur; 165 | time_t now; 166 | 167 | now = time_now_sec(); 168 | pre = NULL; 169 | cur = g_values; 170 | while (cur) { 171 | if (cur->lifetime < now) { 172 | if (pre) { 173 | pre->next = cur->next; 174 | } else { 175 | g_values = cur->next; 176 | } 177 | value_free(cur); 178 | return; 179 | } 180 | pre = cur; 181 | cur = cur->next; 182 | } 183 | } 184 | 185 | static void announces_announce(void) 186 | { 187 | struct announcement_t *value; 188 | time_t now; 189 | 190 | now = time_now_sec(); 191 | value = g_values; 192 | while (value) { 193 | if (value->refresh < now) { 194 | log_debug("Announce %s:%hu", str_id(value->id), value->port); 195 | kad_start_search(NULL, value->id, value->port); 196 | value->refresh = now + ANNOUNCES_INTERVAL; 197 | } 198 | value = value->next; 199 | } 200 | } 201 | 202 | static void announces_handle(int _rc, int _sock) 203 | { 204 | // Expire search results 205 | if (g_announces_expire <= time_now_sec()) { 206 | announces_expire(); 207 | 208 | // Try again in ~1 minute 209 | g_announces_expire = time_add_mins(1); 210 | } 211 | 212 | if (g_announces_announce <= time_now_sec() && kad_count_nodes(false) != 0) { 213 | announces_announce(); 214 | 215 | // Try again in ~1 minute 216 | g_announces_announce = time_add_mins(1); 217 | } 218 | } 219 | 220 | void announces_setup(void) 221 | { 222 | // Cause the callback to be called in intervals 223 | net_add_handler(-1, &announces_handle); 224 | } 225 | 226 | void announces_free(void) 227 | { 228 | struct announcement_t *cur; 229 | struct announcement_t *next; 230 | 231 | cur = g_values; 232 | while (cur) { 233 | next = cur->next; 234 | value_free(cur); 235 | cur = next; 236 | } 237 | g_values = NULL; 238 | } 239 | -------------------------------------------------------------------------------- /src/announces.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _EXT_ANNOUNCES_H_ 3 | #define _EXT_ANNOUNCES_H_ 4 | 5 | #include 6 | #include 7 | 8 | /* 9 | * Announce a value id / port pair in regular 10 | * intervals until the lifetime expires. 11 | */ 12 | 13 | struct announcement_t { 14 | struct announcement_t *next; 15 | uint8_t id[SHA1_BIN_LENGTH]; 16 | uint16_t port; 17 | time_t lifetime; // Keep entry refreshed until the lifetime expires 18 | time_t refresh; // Next time the entry need to be refreshed 19 | }; 20 | 21 | void announces_setup(void); 22 | void announces_free(void); 23 | 24 | struct announcement_t* announces_get(void); 25 | struct announcement_t* announces_find(const uint8_t id[]); 26 | bool announcement_remove(const uint8_t id[]); 27 | 28 | // List all entries 29 | void announces_print(FILE *fp); 30 | 31 | // Add a value id / port that will be announced until lifetime is exceeded 32 | struct announcement_t *announces_add(FILE *fp, uint8_t id[], int port, time_t lifetime); 33 | 34 | 35 | #endif // _EXT_ANNOUNCES_H_ 36 | -------------------------------------------------------------------------------- /src/conf.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "main.h" 14 | #include "log.h" 15 | #include "utils.h" 16 | #include "conf.h" 17 | #include "peerfile.h" 18 | #include "announces.h" 19 | #include "kad.h" 20 | #ifdef __CYGWIN__ 21 | #include "windows.h" 22 | #endif 23 | 24 | // Global object variables 25 | struct gconf_t *gconf = NULL; 26 | 27 | static const char *g_announce_args[32] = { 0 }; 28 | 29 | const char *dhtd_version_str = PROGRAM_NAME " " PROGRAM_VERSION " (" 30 | #ifdef CLI 31 | " command-line-interface" 32 | #endif 33 | #ifdef DEBUG 34 | " debug-build" 35 | #endif 36 | #ifdef LPD 37 | " local-peer-discovery" 38 | #endif 39 | " )"; 40 | 41 | static const char *dhtd_usage_str = 42 | "DHTd is a small DHT daemon.\n" 43 | "\n" 44 | "Usage: dhtd [OPTIONS]\n" 45 | "\n" 46 | " --announce [:} Announce a id and optional port.\n" 47 | " This option may occur multiple times.\n\n" 48 | " --peerfile Import/Export peers from and to a file.\n\n" 49 | " --peer
Add a static peer address.\n" 50 | " This option may occur multiple times.\n\n" 51 | " --execute Execute a script for each result.\n\n" 52 | " --port Bind DHT to this port.\n" 53 | " Default: "STR(DHT_PORT)"\n\n" 54 | " --config Provide a configuration file with one command line\n" 55 | " option on each line. Comments start after '#'.\n\n" 56 | " --ifname Bind to this interface.\n" 57 | " Default: \n\n" 58 | " --daemon, -d Run the node in background.\n\n" 59 | " --verbosity Verbosity level: quiet, verbose or debug.\n" 60 | " Default: verbose\n\n" 61 | " --user Change the UUID after start.\n\n" 62 | " --pidfile Write process pid to a file.\n\n" 63 | " --ipv4, -4, --ipv6, -6 Enable IPv4 or IPv6 only mode.\n" 64 | " Default: IPv4+IPv6\n\n" 65 | #ifdef LPD 66 | " --lpd-disable Disable local peer discovery.\n\n" 67 | #endif 68 | #ifdef CLI 69 | " --cli-disable-stdin Disable the local control interface.\n\n" 70 | " --cli-path Bind the remote control interface to this unix socket path.\n" 71 | " Default: "CLI_PATH"\n\n" 72 | #endif 73 | #ifdef __CYGWIN__ 74 | " --service-start Start, install and remove DHTd as Windows service.\n" 75 | " --service-install DHTd will be started/shut down along with Windows\n" 76 | " --service-remove or on request by using the Service Control Manager.\n\n" 77 | #endif 78 | " --help, -h Print this help.\n\n" 79 | " --version, -v Print program version.\n"; 80 | 81 | 82 | const char *verbosity_str(int verbosity) 83 | { 84 | switch (verbosity) { 85 | case VERBOSITY_QUIET: return "quiet"; 86 | case VERBOSITY_VERBOSE: return "verbose"; 87 | case VERBOSITY_DEBUG: return "debug"; 88 | default: 89 | log_error("Invalid verbosity: %d", verbosity); 90 | exit(1); 91 | } 92 | } 93 | 94 | void conf_info(void) 95 | { 96 | log_info("Starting %s", dhtd_version_str); 97 | log_info("Net Mode: %s", str_af(gconf->af)); 98 | log_info("Run Mode: %s", gconf->is_daemon ? "daemon" : "foreground"); 99 | 100 | if (gconf->configfile) { 101 | log_info("Configuration File: %s", gconf->configfile); 102 | } 103 | 104 | log_info("Verbosity: %s", verbosity_str(gconf->verbosity)); 105 | log_info("Peer File: %s", gconf->peerfile ? gconf->peerfile : "none"); 106 | #ifdef LPD 107 | log_info("Local Peer Discovery: %s", gconf->lpd_disable ? "disabled" : "enabled"); 108 | #endif 109 | } 110 | 111 | void conf_free(void) 112 | { 113 | free(gconf->user); 114 | free(gconf->pidfile); 115 | free(gconf->peerfile); 116 | free(gconf->dht_ifname); 117 | free(gconf->configfile); 118 | 119 | #ifdef CLI 120 | free(gconf->cli_path); 121 | #endif 122 | 123 | free(gconf); 124 | } 125 | 126 | // Enumerate all options to keep binary size smaller 127 | enum { 128 | oAnnounce, 129 | oPidFile, 130 | oPeerFile, 131 | oPeer, 132 | oVerbosity, 133 | oCliDisableStdin, 134 | oCliPath, 135 | oConfig, 136 | oIpv4, 137 | oIpv6, 138 | oPort, 139 | oLpdDisable, 140 | oServiceInstall, 141 | oServiceRemove, 142 | oServiceStart, 143 | oIfname, 144 | oExecute, 145 | oUser, 146 | oDaemon, 147 | oHelp, 148 | oVersion 149 | }; 150 | 151 | static const option_t g_options[] = { 152 | {"--announce", 1, oAnnounce}, 153 | {"--pidfile", 1, oPidFile}, 154 | {"--peerfile", 1, oPeerFile}, 155 | {"--peer", 1, oPeer}, 156 | {"--verbosity", 1, oVerbosity}, 157 | #ifdef CLI 158 | {"--cli-disable-stdin", 0, oCliDisableStdin}, 159 | {"--cli-path", 1, oCliPath}, 160 | #endif 161 | {"--config", 1, oConfig}, 162 | {"--port", 1, oPort}, 163 | {"-4", 0, oIpv4}, 164 | {"--ipv4", 0, oIpv4}, 165 | {"-6", 0, oIpv6}, 166 | {"--ipv6", 0, oIpv6}, 167 | #ifdef LPD 168 | {"--lpd-disable", 0, oLpdDisable}, 169 | #endif 170 | #ifdef __CYGWIN__ 171 | {"--service-install", 0, oServiceInstall}, 172 | {"--service-remove", 0, oServiceRemove}, 173 | {"--service-start", 0, oServiceStart}, 174 | #endif 175 | {"--ifname", 1, oIfname}, 176 | {"--execute", 1, oExecute}, 177 | {"--user", 1, oUser}, 178 | {"--daemon", 0, oDaemon}, 179 | {"-d", 0, oDaemon}, 180 | {"-h", 0, oHelp}, 181 | {"--help", 0, oHelp}, 182 | {"-v", 0, oVersion}, 183 | {"--version", 0, oVersion}, 184 | {NULL, 0, 0} 185 | }; 186 | 187 | // Set a string once - error when already set 188 | static bool conf_str(const char opt[], char *dst[], const char src[]) 189 | { 190 | if (*dst != NULL) { 191 | log_error("Value was already set for %s: %s", opt, src); 192 | return false; 193 | } 194 | 195 | *dst = strdup(src); 196 | return true; 197 | } 198 | 199 | static bool conf_port(const char opt[], int *dst, const char src[]) 200 | { 201 | int port = parse_int(src, -1); 202 | 203 | // port must be != 0 204 | if (!port_valid(port)) { 205 | log_error("Invalid port for %s: %s", opt, src); 206 | return false; 207 | } 208 | 209 | if (*dst >= 0) { 210 | log_error("Value was already set for %s: %s", opt, src); 211 | return false; 212 | } 213 | 214 | *dst = port; 215 | return true; 216 | } 217 | 218 | // forward declaration 219 | static bool conf_set(const char opt[], const char val[]); 220 | 221 | static bool conf_load_file(const char path[]) 222 | { 223 | char line[32 + 256]; 224 | const char *argv[8]; 225 | struct stat s; 226 | 227 | if (stat(path, &s) == 0 && !(s.st_mode & S_IFREG)) { 228 | log_error("File expected: %s", path); 229 | return false; 230 | } 231 | 232 | FILE *file = fopen(path, "r"); 233 | if (file == NULL) { 234 | log_error("Cannot open file: %s (%s)", path, strerror(errno)); 235 | return false; 236 | } 237 | 238 | ssize_t nline = 0; 239 | while (fgets(line, sizeof(line), file) != NULL) { 240 | nline += 1; 241 | 242 | // Cut off comments 243 | char *last = strchr(line, '#'); 244 | if (last) { 245 | *last = '\0'; 246 | } 247 | 248 | if (line[0] == '\n' || line[0] == '\0') { 249 | continue; 250 | } 251 | 252 | int argc = setargs(&argv[0], ARRAY_SIZE(argv), line); 253 | 254 | if (argc == 1 || argc == 2) { 255 | // Prevent recursive inclusion 256 | if (strcmp(argv[0], "--config") == 0) { 257 | fclose(file); 258 | log_error("Option '--config' not allowed inside a configuration file, line %ld.", nline); 259 | return false; 260 | } 261 | 262 | // parse --option value / --option 263 | if (!conf_set(argv[0], (argc == 2) ? argv[1] : NULL)) { 264 | fclose(file); 265 | return false; 266 | } 267 | } else { 268 | fclose(file); 269 | log_error("Invalid line in config file: %s (%d)", path, nline); 270 | return false; 271 | } 272 | } 273 | 274 | fclose(file); 275 | return true; 276 | } 277 | 278 | // Append to an array 279 | static bool array_append(const char **array, size_t array_length, const char element[]) 280 | { 281 | size_t i = 0; 282 | 283 | while ((i < array_length) && (array[i] != NULL)) { 284 | i += 1; 285 | } 286 | 287 | if (i < array_length) { 288 | array[i] = strdup(element); 289 | return true; 290 | } else { 291 | return false; 292 | } 293 | } 294 | 295 | static bool conf_set(const char opt[], const char val[]) 296 | { 297 | const option_t *option = find_option(g_options, opt); 298 | 299 | if (option == NULL) { 300 | log_error("Unknown parameter: %s", opt); 301 | return false; 302 | } 303 | 304 | if (option->num_args == 1 && val == NULL) { 305 | log_error("Argument expected for %s", opt); 306 | return false; 307 | } 308 | 309 | if (option->num_args == 0 && val != NULL) { 310 | log_error("No argument expected for %s", opt); 311 | return false; 312 | } 313 | 314 | switch (option->code) 315 | { 316 | case oAnnounce: 317 | if (!is_announcement(val)) { 318 | log_error("Invalid announcement: %s", opt); 319 | return false; 320 | } 321 | if (!array_append(&g_announce_args[0], ARRAY_SIZE(g_announce_args), val)) { 322 | log_error("Too many announcements"); 323 | return false; 324 | } 325 | break; 326 | case oPidFile: 327 | return conf_str(opt, &gconf->pidfile, val); 328 | case oPeerFile: 329 | return conf_str(opt, &gconf->peerfile, val); 330 | case oPeer: 331 | return peerfile_add_peer(val); 332 | case oVerbosity: 333 | if (strcmp(val, "quiet") == 0) { 334 | gconf->verbosity = VERBOSITY_QUIET; 335 | } else if (strcmp(val, "verbose") == 0) { 336 | gconf->verbosity = VERBOSITY_VERBOSE; 337 | } else if (strcmp(val, "debug") == 0) { 338 | gconf->verbosity = VERBOSITY_DEBUG; 339 | } else { 340 | log_error("Invalid argument for %s", opt); 341 | return false; 342 | } 343 | break; 344 | #ifdef CLI 345 | case oCliDisableStdin: 346 | gconf->cli_disable_stdin = true; 347 | break; 348 | case oCliPath: 349 | if (strlen(val) > FIELD_SIZEOF(struct sockaddr_un, sun_path) - 1) { 350 | log_error("Path too long for %s", opt); 351 | return false; 352 | } 353 | return conf_str(opt, &gconf->cli_path, val); 354 | #endif 355 | case oConfig: 356 | return conf_str(opt, &gconf->configfile, val); 357 | case oIpv4: 358 | case oIpv6: 359 | if (gconf->af != AF_UNSPEC) { 360 | log_error("IPv4 or IPv6 mode already set: %s", opt); 361 | return false; 362 | } 363 | 364 | gconf->af = (option->code == oIpv6) ? AF_INET6 : AF_INET; 365 | break; 366 | case oPort: 367 | return conf_port(opt, &gconf->dht_port, val); 368 | #ifdef LPD 369 | case oLpdDisable: 370 | gconf->lpd_disable = true; 371 | break; 372 | #endif 373 | #ifdef __CYGWIN__ 374 | case oServiceInstall: 375 | windows_service_install(); 376 | exit(0); 377 | case oServiceRemove: 378 | windows_service_remove(); 379 | exit(0); 380 | case oServiceStart: 381 | gconf->service_start = true; 382 | break; 383 | #endif 384 | case oIfname: 385 | return conf_str(opt, &gconf->dht_ifname, val); 386 | case oExecute: 387 | return conf_str(opt, &gconf->execute_path, val); 388 | case oUser: 389 | return conf_str(opt, &gconf->user, val); 390 | case oDaemon: 391 | gconf->is_daemon = true; 392 | break; 393 | case oHelp: 394 | printf("%s\n", dhtd_usage_str); 395 | exit(0); 396 | case oVersion: 397 | printf("%s\n", dhtd_version_str); 398 | exit(0); 399 | default: 400 | return false; 401 | } 402 | 403 | return true; 404 | } 405 | 406 | // Load some values that depend on proper settings 407 | bool conf_load(void) 408 | { 409 | uint8_t id[SHA1_BIN_LENGTH]; 410 | int port; 411 | 412 | for (size_t i = 0; g_announce_args[i]; i += 1) { 413 | const char* arg = g_announce_args[i]; 414 | 415 | if (parse_annoucement(id, &port, arg, gconf->dht_port)) { 416 | announces_add(NULL, id, port, LONG_MAX); 417 | } else { 418 | log_error("Invalid announcement: %s", arg); 419 | return false; 420 | } 421 | } 422 | 423 | return true; 424 | } 425 | 426 | static struct gconf_t *conf_alloc(void) 427 | { 428 | time_t now = time(NULL); 429 | 430 | struct gconf_t *conf = (struct gconf_t*) calloc(1, sizeof(struct gconf_t)); 431 | *conf = ((struct gconf_t) { 432 | .dht_port = DHT_PORT, 433 | .af = AF_UNSPEC, 434 | #ifdef DEBUG 435 | .verbosity = VERBOSITY_DEBUG, 436 | #else 437 | .verbosity = VERBOSITY_VERBOSE, 438 | #endif 439 | #ifdef CLI 440 | .cli_path = strdup(CLI_PATH), 441 | #endif 442 | .time_now = now, 443 | .startup_time = now, 444 | .is_running = true 445 | }); 446 | 447 | return conf; 448 | } 449 | 450 | bool conf_setup(int argc, char **argv) 451 | { 452 | const char *opt; 453 | const char *val; 454 | 455 | gconf = conf_alloc(); 456 | 457 | for (size_t i = 1; i < argc; ++i) { 458 | opt = argv[i]; 459 | val = argv[i + 1]; 460 | 461 | if (val && val[0] != '-') { 462 | // -x abc 463 | if (!conf_set(opt, val)) { 464 | return false; 465 | } 466 | i += 1; 467 | } else { 468 | // -x 469 | if (!conf_set(opt, NULL)) { 470 | return false; 471 | } 472 | } 473 | } 474 | 475 | if (gconf->configfile) { 476 | if (!conf_load_file(gconf->configfile)) { 477 | return false; 478 | } 479 | } 480 | 481 | return true; 482 | } 483 | -------------------------------------------------------------------------------- /src/conf.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _CONF_H_ 3 | #define _CONF_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "main.h" 10 | 11 | // Measurement duration for traffic 12 | #define TRAFFIC_DURATION_SECONDS 8 13 | 14 | extern const char *dhtd_version_str; 15 | 16 | bool conf_setup(int argc, char **argv); 17 | bool conf_load(void); 18 | void conf_info(void); 19 | void conf_free(void); 20 | 21 | 22 | struct gconf_t { 23 | // Current time 24 | time_t time_now; 25 | 26 | // DHTd startup time 27 | time_t startup_time; 28 | 29 | // Drop privileges to user 30 | char *user; 31 | 32 | // Write a pid file if set 33 | char *pidfile; 34 | 35 | // Import/Export peers from this file 36 | char *peerfile; 37 | 38 | // Path to configuration file 39 | char *configfile; 40 | 41 | // Start in Foreground / Background 42 | bool is_daemon; 43 | 44 | // Thread terminator 45 | bool is_running; 46 | 47 | // Quiet / Verbose / Debug 48 | int verbosity; 49 | 50 | // Write log to /var/log/message 51 | bool use_syslog; 52 | 53 | // Net mode (AF_INET / AF_INET6 / AF_UNSPEC) 54 | int af; 55 | 56 | // DHT port number 57 | int dht_port; 58 | 59 | // DHT interface 60 | char *dht_ifname; 61 | 62 | // Script to execute on each new result 63 | char* execute_path; 64 | 65 | #ifdef __CYGWIN__ 66 | // Start as windows service 67 | bool service_start; 68 | #endif 69 | 70 | #ifdef LPD 71 | // Disable local peer discovery 72 | bool lpd_disable; 73 | #endif 74 | 75 | #ifdef CLI 76 | char *cli_path; 77 | bool cli_disable_stdin; 78 | #endif 79 | 80 | // Traffic measurement 81 | time_t traffic_time; 82 | uint64_t traffic_in_sum; 83 | uint64_t traffic_out_sum; 84 | uint32_t traffic_in[TRAFFIC_DURATION_SECONDS]; 85 | uint32_t traffic_out[TRAFFIC_DURATION_SECONDS]; 86 | }; 87 | 88 | extern struct gconf_t *gconf; 89 | 90 | #endif // _CONF_H_ 91 | -------------------------------------------------------------------------------- /src/dht.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2011 by Juliusz Chroboczek 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | typedef void 28 | dht_callback_t(void *closure, int event, 29 | const unsigned char *info_hash, 30 | const void *data, size_t data_len); 31 | 32 | #define DHT_EVENT_NONE 0 33 | #define DHT_EVENT_VALUES 1 34 | #define DHT_EVENT_VALUES6 2 35 | #define DHT_EVENT_SEARCH_DONE 3 36 | #define DHT_EVENT_SEARCH_DONE6 4 37 | #define DHT_EVENT_SEARCH_EXPIRED 5 38 | 39 | extern FILE *dht_debug; 40 | 41 | int dht_init(int s, int s6, const unsigned char *id, const unsigned char *v); 42 | int dht_insert_node(const unsigned char *id, struct sockaddr *sa, int salen); 43 | int dht_ping_node(const struct sockaddr *sa, int salen); 44 | int dht_periodic(const void *buf, size_t buflen, 45 | const struct sockaddr *from, int fromlen, time_t *tosleep, 46 | dht_callback_t *callback, void *closure); 47 | int dht_search(const unsigned char *id, int port, int af, 48 | dht_callback_t *callback, void *closure); 49 | int dht_nodes(int af, 50 | int *good_return, int *dubious_return, int *cached_return, 51 | int *incoming_return); 52 | void dht_dump_tables(FILE *f); 53 | int dht_get_nodes(struct sockaddr_in *sin, int *num, 54 | struct sockaddr_in6 *sin6, int *num6); 55 | int dht_uninit(void); 56 | 57 | /* This must be provided by the user. */ 58 | int dht_sendto(int sockfd, const void *buf, int len, int flags, 59 | const struct sockaddr *to, int tolen); 60 | int dht_blacklisted(const struct sockaddr *sa, int salen); 61 | void dht_hash(void *hash_return, int hash_size, 62 | const void *v1, int len1, 63 | const void *v2, int len2, 64 | const void *v3, int len3); 65 | int dht_random_bytes(void *buf, size_t size); 66 | 67 | #ifdef __cplusplus 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /src/ext-cli.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "main.h" 15 | #include "conf.h" 16 | #include "utils.h" 17 | #include "log.h" 18 | #include "kad.h" 19 | #include "net.h" 20 | #include "unix.h" 21 | #include "results.h" 22 | #include "announces.h" 23 | #include "ext-cli.h" 24 | 25 | 26 | static const char *g_client_usage = 27 | PROGRAM_NAME" Control Program - Send commands to a DHTd instance.\n\n" 28 | "Usage: dhtd-ctl [OPTIONS] [COMMANDS]\n" 29 | "\n" 30 | " -p Connect to this unix socket (Default: "CLI_PATH")\n" 31 | " -h Print this help.\n" 32 | "\n"; 33 | 34 | static const char* g_server_usage = 35 | "Usage:\n" 36 | " status\n" 37 | " help\n" 38 | " lookup \n" 39 | " search \n" 40 | " results \n" 41 | " announce-start [:]\n" 42 | " announce-stop \n" 43 | " searches\n" 44 | " announcements\n" 45 | " peer
\n" 46 | " constants|blocklist|peers|buckets|storage\n"; 47 | 48 | static const char* g_server_help = 49 | " DHTd is a standalone DHT node for the mainline BitTorrent network.\n" 50 | " Announce and search for peers that have announced an identifier.\n" 51 | " The result is a list of IP addresses and ports of those peers.\n" 52 | "\n" 53 | " status\n" 54 | " The current state of this node.\n" 55 | " lookup \n" 56 | " Start search and print results.\n" 57 | " search \n" 58 | " Start a search for announced values.\n" 59 | " results \n" 60 | " Print the results of a search.\n" 61 | " announce-start [:]\n" 62 | " Start to announce an id along with a network port.\n" 63 | " announce-stop \n" 64 | " Stop the announcement.\n" 65 | " searches\n" 66 | " Print a list of all searches. They expire after 62min.\n" 67 | " announcements\n" 68 | " Print a list of all announcements.\n" 69 | " peer
:\n" 70 | " Add a peer by address.\n" 71 | " constants|blocklist|peers|buckets|storage\n" 72 | " Print various internal data.\n" 73 | " -----\n" 74 | " 20 bytes as base16 (hexadecimal) or base32 string\n" 75 | " Network port number between 1-65536\n" 76 | "
IPv4 or IPv6 address\n" 77 | ""; 78 | 79 | static int g_cli_sock = -1; 80 | 81 | static void cmd_ping(FILE *fp, const IP *addr) 82 | { 83 | if (kad_ping(addr)) { 84 | fprintf(fp, "Send ping to: %s\n", str_addr(addr)); 85 | } else { 86 | fprintf(fp, "Failed to send ping.\n"); 87 | } 88 | } 89 | 90 | enum { 91 | oHelp, 92 | oPeer, 93 | oSearch, 94 | oResults, 95 | oLookup, 96 | oStatus, 97 | oAnnounceStart, 98 | oAnnounceStop, 99 | oPrintBlocked, 100 | oPrintConstants, 101 | oPrintPeers, 102 | oPrintAnnouncements, 103 | oPrintBuckets, 104 | oPrintSearches, 105 | oPrintStorage 106 | }; 107 | 108 | static const option_t g_options[] = { 109 | {"h", 1, oHelp}, 110 | {"help", 1, oHelp}, 111 | {"peer", 2, oPeer}, 112 | {"search", 2, oSearch}, 113 | {"results", 2, oResults}, 114 | {"lookup", 2, oLookup}, 115 | {"query", 2, oLookup}, // for backwards compatibility 116 | {"status", 1, oStatus}, 117 | {"announce-start", 2, oAnnounceStart}, 118 | {"announce-stop", 2, oAnnounceStop}, 119 | {"blocklist", 1, oPrintBlocked}, 120 | {"constants", 1, oPrintConstants}, 121 | {"peers", 1, oPrintPeers}, 122 | {"announcements", 1, oPrintAnnouncements}, 123 | {"buckets", 1, oPrintBuckets}, 124 | {"searches", 1, oPrintSearches}, 125 | {"storage", 1, oPrintStorage}, 126 | {NULL, 0, 0} 127 | }; 128 | 129 | static void cmd_exec(FILE *fp, char request[], bool allow_debug) 130 | { 131 | uint8_t id[SHA1_BIN_LENGTH]; 132 | const char *argv[8]; 133 | int argc = setargs(&argv[0], ARRAY_SIZE(argv), request); 134 | 135 | if (argc == 0) { 136 | // Print usage 137 | fprintf(fp, "%s", g_server_usage); 138 | return; 139 | } 140 | 141 | const option_t *option = find_option(g_options, argv[0]); 142 | 143 | if (option == NULL) { 144 | fprintf(fp, "Unknown command.\n"); 145 | return; 146 | } 147 | 148 | if (option->num_args != argc) { 149 | fprintf(fp, "Unexpected number of arguments.\n"); 150 | return; 151 | } 152 | 153 | // parse identifier 154 | switch (option->code) { 155 | case oSearch: case oResults: case oLookup: case oAnnounceStop: 156 | if (!parse_id(id, sizeof(id), argv[1], strlen(argv[1]))) { 157 | fprintf(fp, "Failed to parse identifier.\n"); 158 | return; 159 | } 160 | } 161 | 162 | switch (option->code) { 163 | case oHelp: 164 | fprintf(fp, "%s", g_server_help); 165 | break; 166 | case oPeer: { 167 | const char *addr_str = argv[1]; 168 | const char *port_str = STR(DHT_PORT); // fallback port 169 | IP addr4 = {0}; 170 | IP addr6 = {0}; 171 | bool parsed4 = false; 172 | bool parsed6 = false; 173 | 174 | switch (gconf->af) { 175 | case AF_INET: 176 | parsed4 = addr_parse(&addr4, addr_str, port_str, AF_INET); 177 | break; 178 | case AF_INET6: 179 | parsed6 = addr_parse(&addr6, addr_str, port_str, AF_INET6); 180 | break; 181 | default: 182 | parsed4 = addr_parse(&addr4, addr_str, port_str, AF_INET); 183 | parsed6 = addr_parse(&addr6, addr_str, port_str, AF_INET6); 184 | } 185 | 186 | if (!parsed4 && !parsed6) { 187 | fprintf(fp, "Failed to parse/resolve address.\n"); 188 | } 189 | 190 | if (parsed4) { 191 | cmd_ping(fp, &addr4); 192 | } 193 | 194 | if (parsed6) { 195 | cmd_ping(fp, &addr6); 196 | } 197 | 198 | break; 199 | } 200 | case oLookup: 201 | kad_start_search(NULL, id, 0); 202 | results_print(fp, id); 203 | break; 204 | case oSearch: 205 | kad_start_search(fp, id, 0); 206 | break; 207 | case oResults: 208 | results_print(fp, id); 209 | break; 210 | case oStatus: 211 | kad_status(fp); 212 | break; 213 | case oAnnounceStart: { 214 | int port; 215 | if (parse_annoucement(&id[0], &port, argv[1], gconf->dht_port)) { 216 | announces_add(fp, id, port, LONG_MAX); 217 | } else { 218 | fprintf(fp, "Invalid announcement.\n"); 219 | } 220 | break; 221 | } 222 | case oAnnounceStop: 223 | announcement_remove(id); 224 | break; 225 | case oPrintSearches: 226 | kad_print_searches(fp); 227 | break; 228 | case oPrintAnnouncements: 229 | announces_print(fp); 230 | break; 231 | case oPrintBlocked: 232 | kad_print_blocklist(fp); 233 | break; 234 | case oPrintConstants: 235 | kad_print_constants(fp); 236 | break; 237 | case oPrintPeers: 238 | kad_export_peers(fp); 239 | break; 240 | case oPrintBuckets: 241 | kad_print_buckets(fp); 242 | break; 243 | case oPrintStorage: 244 | kad_print_storage(fp); 245 | break; 246 | } 247 | } 248 | 249 | static void cli_client_handler(int rc, int clientsock) 250 | { 251 | // save state since a line and come in multiple calls 252 | static char request[256]; 253 | static ssize_t request_length = 0; 254 | static int current_clientsock = -1; 255 | static FILE* current_clientfd = NULL; 256 | 257 | if (rc <= 0) { 258 | return; 259 | } 260 | 261 | ssize_t remaining = sizeof(request) - request_length; 262 | ssize_t size = read(clientsock, &request[request_length], remaining); 263 | 264 | if (size == -1) { 265 | return; 266 | } else { 267 | request_length += size; 268 | } 269 | 270 | if (current_clientfd == NULL) { 271 | current_clientfd = fdopen(clientsock, "w"); 272 | } 273 | 274 | if (request_length > 0 && size != 0) { 275 | // split lines 276 | char* beg = request; 277 | const char* end = request + request_length; 278 | char *cur = beg; 279 | while (true) { 280 | char *next = memchr(cur, '\n', end - cur); 281 | if (next) { 282 | *next = '\0'; // replace newline with 0 283 | #ifdef DEBUG 284 | cmd_exec(current_clientfd, cur, true); 285 | #else 286 | cmd_exec(current_clientfd, cur, false); 287 | #endif 288 | fflush(current_clientfd); 289 | cur = next + 1; 290 | 291 | // force connection to be 292 | // closed after one command 293 | size = 0; 294 | } else { 295 | break; 296 | } 297 | } 298 | 299 | // move unhandled data to the front of the buffer 300 | if (cur > beg) { 301 | memmove(beg, cur, cur - beg); 302 | request_length = end - cur; 303 | remaining = sizeof(request) - request_length; 304 | } 305 | } 306 | 307 | if (size == 0 || remaining == 0) { 308 | // socket closed 309 | if (current_clientfd) { 310 | fclose(current_clientfd); 311 | } else { 312 | close(current_clientsock); 313 | } 314 | 315 | current_clientsock = -1; 316 | current_clientfd = NULL; 317 | request_length = 0; 318 | 319 | net_remove_handler(clientsock, &cli_client_handler); 320 | } 321 | } 322 | 323 | static void cli_server_handler(int rc, int serversock) 324 | { 325 | if (rc <= 0) { 326 | return; 327 | } 328 | 329 | int clientsock = accept(serversock, NULL, NULL); 330 | if (clientsock < 0) { 331 | log_error("accept(): %s", strerror(errno)); 332 | return; 333 | } 334 | 335 | net_add_handler(clientsock, &cli_client_handler); 336 | } 337 | 338 | // special case for local console 339 | static void cli_console_handler(int rc, int fd) 340 | { 341 | char request[256]; 342 | 343 | if (rc <= 0) { 344 | return; 345 | } 346 | 347 | // Read line 348 | char *ptr = fgets(request, sizeof(request), stdin); 349 | if (ptr == NULL) { 350 | return; 351 | } 352 | 353 | // Output to stdout (not stdin) 354 | cmd_exec(stdout, request, true); 355 | } 356 | 357 | bool cli_setup(void) 358 | { 359 | if (!unix_create_unix_socket(gconf->cli_path, &g_cli_sock)) { 360 | return false; 361 | } else { 362 | log_info("CLI: Bind to %s", gconf->cli_path); 363 | 364 | net_add_handler(g_cli_sock, &cli_server_handler); 365 | 366 | if (!gconf->is_daemon && !gconf->cli_disable_stdin) { 367 | fprintf(stdout, "Press Enter for help.\n"); 368 | net_add_handler(STDIN_FILENO, &cli_console_handler); 369 | } 370 | 371 | return true; 372 | } 373 | } 374 | 375 | void cli_free(void) 376 | { 377 | if (g_cli_sock >= 0) { 378 | unix_remove_unix_socket(gconf->cli_path, g_cli_sock); 379 | } 380 | } 381 | 382 | #ifdef __CYGWIN__ 383 | static int select_read(int sockfd, char buffer[], int bufsize, struct timeval *tv) 384 | { 385 | fd_set rfds; 386 | 387 | FD_ZERO(&rfds); 388 | FD_SET(sockfd, &rfds); 389 | 390 | int retval = select(sockfd + 1, &rfds, NULL, NULL, tv); 391 | 392 | if (retval == -1) { 393 | // Error 394 | return -1; 395 | } else if (retval) { 396 | // Data available 397 | return read(sockfd, buffer, bufsize); 398 | } else { 399 | // Timeout reached 400 | return 0; 401 | } 402 | } 403 | #endif 404 | 405 | int cli_client(int argc, char *argv[]) 406 | { 407 | char buffer[1024]; 408 | struct sockaddr_un addr = { 0 }; 409 | 410 | // Default unix socket path 411 | const char *path = CLI_PATH; 412 | 413 | // Skip program name 414 | argc -= 1; 415 | argv += 1; 416 | 417 | if (argc >= 1) { 418 | if (strcmp(argv[0], "-h") == 0) { 419 | fprintf(stdout, "%s", g_client_usage); 420 | return EXIT_SUCCESS; 421 | } else if (strcmp(argv[0], "-p") == 0) { 422 | if (argc >= 2) { 423 | path = argv[1]; 424 | // Skip option and path 425 | argc -= 2; 426 | argv += 2; 427 | } else { 428 | fprintf(stderr, "Path is missing!\n"); 429 | return EXIT_FAILURE; 430 | } 431 | } 432 | } 433 | 434 | if (strlen(path) >= FIELD_SIZEOF(struct sockaddr_un, sun_path)) { 435 | fprintf(stderr, "Path too long!\n"); 436 | return EXIT_FAILURE; 437 | } 438 | 439 | size_t pos = 0; 440 | if (!isatty(fileno(stdin))) { 441 | bool all = false; 442 | while (pos < sizeof(buffer)) { 443 | int c = getchar(); 444 | if (c == -1) { 445 | all = true; 446 | break; 447 | } 448 | buffer[pos++] = c; 449 | } 450 | 451 | if (!all) { 452 | fprintf(stderr, "Input too long!\n"); 453 | return EXIT_FAILURE; 454 | } 455 | 456 | if (pos == 0 || buffer[pos-1] != '\n') { 457 | // Append newline if not present 458 | buffer[pos++] = '\n'; 459 | } 460 | } else { 461 | // Concatenate arguments 462 | for (size_t i = 0; i < argc; i++) { 463 | size_t len = strlen(argv[i]); 464 | if ((pos + len + 1) >= sizeof(buffer)) { 465 | fprintf(stderr, "Input too long!\n"); 466 | return EXIT_FAILURE; 467 | } 468 | memcpy(&buffer[pos], argv[i], len); 469 | pos += len; 470 | buffer[pos++] = ' '; 471 | } 472 | // Append newline 473 | buffer[pos++] = '\n'; 474 | } 475 | 476 | int sock = socket(AF_LOCAL, SOCK_STREAM, 0); 477 | if (sock < 0) { 478 | fprintf(stderr, "socket() %s\n", strerror(errno)); 479 | return EXIT_FAILURE; 480 | } 481 | 482 | addr.sun_family = AF_LOCAL; 483 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 484 | 485 | if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 486 | fprintf(stderr, "Failed to connect to '%s': %s\n", path, strerror(errno)); 487 | goto error; 488 | } 489 | 490 | #ifdef __CYGWIN__ 491 | struct timeval tv; 492 | 493 | /* Set receive timeout: 200ms */ 494 | tv.tv_sec = 0; 495 | tv.tv_usec = 200000; 496 | 497 | if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)) < 0) { 498 | fprintf(stderr, "setsockopt() %s\n", strerror(errno)); 499 | goto error; 500 | } 501 | #endif 502 | 503 | // Write request 504 | size_t ret = write(sock, buffer, pos); 505 | 506 | if (ret < 0) { 507 | fprintf(stderr, "write() %s\n", strerror(errno)); 508 | goto error; 509 | } 510 | 511 | while (true) { 512 | // Receive replies 513 | #ifdef __CYGWIN__ 514 | ssize_t size = select_read(sock, buffer, sizeof(buffer), &tv); 515 | #else 516 | ssize_t size = read(sock, buffer, sizeof(buffer)); 517 | #endif 518 | if (size > 0 && size <= sizeof(buffer)) { 519 | // Print to console 520 | printf("%.*s", (int) size, buffer); 521 | } else { 522 | // socket closed (0) or error 523 | break; 524 | } 525 | } 526 | 527 | close(sock); 528 | 529 | return EXIT_SUCCESS; 530 | 531 | error: 532 | if (sock > 0) { 533 | close(sock); 534 | } 535 | 536 | return EXIT_FAILURE; 537 | } 538 | -------------------------------------------------------------------------------- /src/ext-cli.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _EXT_CLI_H_ 3 | #define _EXT_CLI_H_ 4 | 5 | // dhtd-ctl 6 | int cli_client(int argc, char *argv[]); 7 | 8 | // Start the remote console interface 9 | bool cli_setup(void); 10 | void cli_free(void); 11 | 12 | #endif // _EXT_CLI_H_ 13 | -------------------------------------------------------------------------------- /src/ext-lpd.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "main.h" 16 | #include "conf.h" 17 | #include "log.h" 18 | #include "utils.h" 19 | #include "net.h" 20 | #include "kad.h" 21 | #include "ext-lpd.h" 22 | 23 | /* 24 | * Local Peer Discovery 25 | */ 26 | 27 | #if defined(__FreeBSD__) 28 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP 29 | #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP 30 | #endif 31 | 32 | enum { 33 | // Packets per minute to be handled 34 | PACKET_LIMIT_MAX = 20, 35 | // Limit multicast message to the same subnet 36 | TTL_SAME_SUBNET = 1 37 | }; 38 | 39 | struct lpd_state { 40 | IP mcast_addr; 41 | time_t mcast_time; 42 | int packet_limit; 43 | int sock_send; 44 | int sock_listen; 45 | }; 46 | 47 | struct lpd_state g_lpd4 = { 48 | .mcast_addr = {0}, 49 | .mcast_time = 0, 50 | .packet_limit = PACKET_LIMIT_MAX, 51 | .sock_send = -1, 52 | .sock_listen = -1 53 | }; 54 | 55 | struct lpd_state g_lpd6 = { 56 | .mcast_addr = {0}, 57 | .mcast_time = 0, 58 | .packet_limit = PACKET_LIMIT_MAX, 59 | .sock_send = -1, 60 | .sock_listen = -1 61 | }; 62 | 63 | static bool filter_ifa(const struct ifaddrs *ifa, int family) 64 | { 65 | if ((ifa->ifa_addr == NULL) 66 | || (ifa->ifa_addr->sa_family != family) 67 | || !(ifa->ifa_flags & IFF_RUNNING) 68 | || (ifa->ifa_flags & IFF_LOOPBACK)) { 69 | return false; 70 | } 71 | 72 | // if DHT interface set, use only that interface (if it exists) 73 | if (gconf->dht_ifname) { 74 | return (0 == strcmp(gconf->dht_ifname, ifa->ifa_name)); 75 | } else { 76 | return true; 77 | } 78 | } 79 | 80 | static void join_mcast(const struct lpd_state* lpd, const struct ifaddrs *ifas) 81 | { 82 | const char *prev_ifname = NULL; 83 | int family = lpd->mcast_addr.ss_family; 84 | 85 | for (const struct ifaddrs *ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { 86 | if (!filter_ifa(ifa, family)) { 87 | continue; 88 | } 89 | 90 | if (ifa->ifa_addr->sa_family == AF_INET) { 91 | struct ip_mreq mcastReq = {0}; 92 | 93 | mcastReq.imr_multiaddr = ((IP4*) &lpd->mcast_addr)->sin_addr; 94 | mcastReq.imr_interface.s_addr = htonl(INADDR_ANY); 95 | 96 | // ignore error (we might already be subscribed) 97 | if (setsockopt(lpd->sock_listen, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void const*)&mcastReq, sizeof(mcastReq)) != 0) { 98 | log_warning("LPD: failed to join IPv4 multicast group: %s", strerror(errno)); 99 | } 100 | } else { // AF_INET6 101 | // skip previous interface (relies on order of ifas) 102 | if (prev_ifname && 0 == strcmp(prev_ifname, ifa->ifa_name)) { 103 | continue; 104 | } else { 105 | prev_ifname = ifa->ifa_name; 106 | } 107 | 108 | unsigned ifindex = if_nametoindex(ifa->ifa_name); 109 | struct ipv6_mreq mreq6 = {0}; 110 | 111 | memcpy(&mreq6.ipv6mr_multiaddr, &((IP6*) &lpd->mcast_addr)->sin6_addr, 16); 112 | mreq6.ipv6mr_interface = ifindex; 113 | 114 | // ignore error (we might already be subscribed) 115 | if (setsockopt(lpd->sock_listen, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) != 0) { 116 | log_warning("LPD: failed to join IPv6 multicast group: %s", strerror(errno)); 117 | } 118 | } 119 | } 120 | } 121 | 122 | static void send_mcasts(const struct lpd_state* lpd, const struct ifaddrs *ifas) 123 | { 124 | char message[16]; 125 | 126 | sprintf(message, "DHT %d", gconf->dht_port); 127 | 128 | int family = lpd->mcast_addr.ss_family; 129 | const char *prev_ifname = NULL; 130 | 131 | for (const struct ifaddrs *ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { 132 | if (!filter_ifa(ifa, family)) { 133 | continue; 134 | } 135 | 136 | if (ifa->ifa_addr->sa_family == AF_INET) { 137 | struct in_addr addr = ((struct sockaddr_in*) ifa->ifa_addr)->sin_addr; 138 | 139 | if (setsockopt(lpd->sock_send, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)) != 0) { 140 | log_error("setsockopt(IP_MULTICAST_IF) %s %s", ifa->ifa_name, strerror(errno)); 141 | continue; 142 | } 143 | } else { // AF_INET6 144 | // skip previous interface (relies on order of ifas) 145 | if (prev_ifname && 0 == strcmp(prev_ifname, ifa->ifa_name)) { 146 | continue; 147 | } else { 148 | prev_ifname = ifa->ifa_name; 149 | } 150 | 151 | unsigned ifindex = if_nametoindex(ifa->ifa_name); 152 | if (setsockopt(lpd->sock_send, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) != 0) { 153 | log_error("setsockopt(IPV6_MULTICAST_IF) %s %s", ifa->ifa_name, strerror(errno)); 154 | continue; 155 | } 156 | } 157 | 158 | sendto(lpd->sock_send, (void const*) message, strlen(message), 0, 159 | (struct sockaddr const*) &lpd->mcast_addr, addr_len(&lpd->mcast_addr)); 160 | 161 | log_debug("LPD: Send discovery message to %s on %s", str_addr(&lpd->mcast_addr), ifa->ifa_name); 162 | } 163 | } 164 | 165 | static void handle_mcast(int mcast_rc, struct lpd_state* lpd) 166 | { 167 | // called at least every second 168 | if (lpd->mcast_time <= time_now_sec()) { 169 | struct ifaddrs *ifaddrs; 170 | if (getifaddrs(&ifaddrs) == 0) { 171 | // join multicast group (in case of new interfaces) 172 | join_mcast(lpd, ifaddrs); 173 | 174 | // No peers known, send multicast 175 | if (kad_count_nodes(false) == 0) { 176 | send_mcasts(lpd, ifaddrs); 177 | } 178 | freeifaddrs(ifaddrs); 179 | } else { 180 | log_error("getifaddrs() %s", strerror(errno)); 181 | } 182 | 183 | // Cap number of received packets to 10 per minute 184 | lpd->packet_limit = 5 * PACKET_LIMIT_MAX; 185 | 186 | // Try again in ~5 minutes 187 | lpd->mcast_time = time_add_mins(5); 188 | } 189 | 190 | if (mcast_rc <= 0) { 191 | return; 192 | } 193 | 194 | // Receive multicast ping 195 | socklen_t addrlen = sizeof(IP); 196 | IP address = {0}; 197 | char buf[16]; 198 | int rc = recvfrom(lpd->sock_listen, buf, sizeof(buf) - 1, 0, (struct sockaddr*) &address, (socklen_t*) &addrlen); 199 | if (rc <= 0) { 200 | log_warning("LPD: Cannot receive multicast message: %s", strerror(errno)); 201 | return; 202 | } else if (lpd->packet_limit < 0) { 203 | // Too much traffic 204 | return; 205 | } else { 206 | lpd->packet_limit -= 1; 207 | } 208 | 209 | buf[rc] = '\0'; 210 | 211 | if (0 == strncmp(buf, "DHT ", 4)) { 212 | int port = parse_int(&buf[4], -1); 213 | if (port_valid(port)) { 214 | port_set(&address, port); 215 | log_debug("LPD: Ping lonely peer at %s", str_addr(&address)); 216 | kad_ping(&address); 217 | } 218 | } 219 | } 220 | 221 | static void handle_mcast4(int rc, int sock) 222 | { 223 | assert(sock == g_lpd4.sock_listen); 224 | handle_mcast(rc, &g_lpd4); 225 | } 226 | 227 | static void handle_mcast6(int rc, int sock) 228 | { 229 | assert(sock == g_lpd6.sock_listen); 230 | handle_mcast(rc, &g_lpd6); 231 | } 232 | 233 | static int create_send_socket(int af) 234 | { 235 | const int scope = TTL_SAME_SUBNET; 236 | const int opt_off = 0; 237 | 238 | int sock = net_socket("LPD", NULL, IPPROTO_IP, af); 239 | if (sock < 0) { 240 | return -1; 241 | } 242 | 243 | if (af == AF_INET) { 244 | in_addr_t iface = INADDR_ANY; 245 | 246 | // IPv4 247 | if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void const*)&scope, sizeof(scope)) != 0) { 248 | goto fail; 249 | } 250 | 251 | if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char*)&iface, sizeof(iface)) != 0) { 252 | goto fail; 253 | } 254 | 255 | if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void const*)&opt_off, sizeof(opt_off)) != 0) { 256 | goto fail; 257 | } 258 | } else { 259 | // IPv6 260 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char*)&scope, sizeof(scope)) != 0) { 261 | goto fail; 262 | } 263 | 264 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (void const*)&opt_off, sizeof(opt_off)) != 0) { 265 | goto fail; 266 | } 267 | } 268 | 269 | return sock; 270 | 271 | fail: 272 | close(sock); 273 | 274 | log_warning("LPD: Cannot create send %s socket: %s", str_af(af), strerror(errno)); 275 | 276 | return -1; 277 | } 278 | 279 | static int create_receive_socket(const IP *mcast_addr) 280 | { 281 | const int opt_off = 0; 282 | const int opt_on = 1; 283 | 284 | socklen_t addrlen = addr_len(mcast_addr); 285 | int af = mcast_addr->ss_family; 286 | 287 | int sock = net_socket("LPD", NULL, IPPROTO_UDP, af); 288 | if (sock < 0) { 289 | return -1; 290 | } 291 | 292 | if (af == AF_INET6) { 293 | // IPv6 294 | int loop = 0; 295 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&loop, sizeof(loop)) != 0) { 296 | goto fail; 297 | } 298 | } else { 299 | // IPv4 300 | if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void const*)&opt_off, sizeof(opt_off)) != 0) { 301 | goto fail; 302 | } 303 | } 304 | 305 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void const*)&opt_on, sizeof(opt_on)) != 0) { 306 | goto fail; 307 | } 308 | 309 | if (bind(sock, (struct sockaddr*)mcast_addr, addrlen) != 0) { 310 | goto fail; 311 | } 312 | 313 | return sock; 314 | 315 | fail: 316 | 317 | close(sock); 318 | 319 | log_warning("LPD: Cannot create receive %s socket: %s", str_af(af), strerror(errno)); 320 | 321 | return -1; 322 | } 323 | 324 | bool lpd_setup(void) 325 | { 326 | bool ready = false; 327 | 328 | if (gconf->lpd_disable) { 329 | return true; 330 | } 331 | 332 | const char *ifname = gconf->dht_ifname; 333 | 334 | if (ifname && (gconf->af == AF_UNSPEC || gconf->af == AF_INET)) { 335 | log_warning("LPD: ifname setting not supported for IPv4"); 336 | } 337 | 338 | addr_parse(&g_lpd4.mcast_addr, LPD_ADDR4, STR(LPD_PORT), AF_INET); 339 | addr_parse(&g_lpd6.mcast_addr, LPD_ADDR6, STR(LPD_PORT), AF_INET6); 340 | 341 | // Setup IPv4 sockets 342 | g_lpd4.sock_listen = create_receive_socket(&g_lpd4.mcast_addr); 343 | g_lpd4.sock_send = create_send_socket(AF_INET); 344 | 345 | // Setup IPv6 sockets 346 | g_lpd6.sock_listen = create_receive_socket(&g_lpd6.mcast_addr); 347 | g_lpd6.sock_send = create_send_socket(AF_INET6); 348 | 349 | if (g_lpd4.sock_listen >= 0 && g_lpd4.sock_send >= 0) { 350 | net_add_handler(g_lpd4.sock_listen, &handle_mcast4); 351 | ready = true; 352 | } 353 | 354 | if (g_lpd6.sock_listen >= 0 && g_lpd6.sock_send >= 0) { 355 | net_add_handler(g_lpd6.sock_listen, &handle_mcast6); 356 | ready = true; 357 | } 358 | 359 | return ready; 360 | } 361 | 362 | void lpd_free(void) 363 | { 364 | // Nothing to do 365 | } 366 | -------------------------------------------------------------------------------- /src/ext-lpd.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _LPD_H 3 | #define _LPD_H 4 | 5 | /* 6 | * Send multicast messages to discover 7 | * new nodes if no other nodes are known. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | bool lpd_setup(void); 16 | void lpd_free(void); 17 | 18 | #endif // _LPD_H 19 | -------------------------------------------------------------------------------- /src/kad.c: -------------------------------------------------------------------------------- 1 | 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "log.h" 11 | #include "main.h" 12 | #include "utils.h" 13 | #include "conf.h" 14 | #include "utils.h" 15 | #include "net.h" 16 | #include "announces.h" 17 | #include "results.h" 18 | 19 | // include dht.c instead of dht.h to access private vars 20 | #include "dht.c" 21 | 22 | 23 | /* 24 | * The interface that is used to interact with the DHT. 25 | */ 26 | 27 | // Next time to do DHT maintenance 28 | static time_t g_dht_maintenance = 0; 29 | static int g_dht_socket4 = -1; 30 | static int g_dht_socket6 = -1; 31 | 32 | // This callback is called when a search result arrives or a search completes 33 | void dht_callback_func(void *closure, int event, const uint8_t *info_hash, const void *data, size_t data_len) 34 | { 35 | switch (event) { 36 | case DHT_EVENT_VALUES: 37 | results_add(info_hash, AF_INET, data, data_len); 38 | break; 39 | case DHT_EVENT_VALUES6: 40 | results_add(info_hash, AF_INET6, data, data_len); 41 | break; 42 | case DHT_EVENT_SEARCH_DONE: 43 | case DHT_EVENT_SEARCH_DONE6: 44 | break; 45 | case DHT_EVENT_SEARCH_EXPIRED: 46 | results_clear(info_hash); 47 | break; 48 | } 49 | } 50 | 51 | static void clear_old_traffic_counters(void) 52 | { 53 | size_t idx = gconf->time_now % TRAFFIC_DURATION_SECONDS; 54 | uint32_t since = (gconf->time_now - gconf->traffic_time); 55 | size_t n = MIN(since, TRAFFIC_DURATION_SECONDS); 56 | 57 | // clear old traffic measurement buckets 58 | for (size_t i = 0; i < n; ++i) { 59 | size_t j = (TRAFFIC_DURATION_SECONDS + idx + i + 1) % TRAFFIC_DURATION_SECONDS; 60 | gconf->traffic_in[j] = 0; 61 | gconf->traffic_out[j] = 0; 62 | } 63 | } 64 | 65 | static void record_traffic(uint32_t in_bytes, uint32_t out_bytes) 66 | { 67 | clear_old_traffic_counters(); 68 | 69 | gconf->traffic_in_sum += in_bytes; 70 | gconf->traffic_out_sum += out_bytes; 71 | 72 | size_t idx = gconf->time_now % TRAFFIC_DURATION_SECONDS; 73 | gconf->traffic_time = gconf->time_now; 74 | gconf->traffic_in[idx] += out_bytes; 75 | gconf->traffic_out[idx] += in_bytes; 76 | } 77 | 78 | // Handle incoming packets and pass them to the DHT code 79 | void dht_handler(int rc, int sock) 80 | { 81 | uint8_t buf[1500]; 82 | ssize_t buflen = 0; 83 | IP from; 84 | 85 | if (rc > 0) { 86 | // Check which socket received the data 87 | socklen_t fromlen = sizeof(from); 88 | buflen = recvfrom(sock, buf, sizeof(buf) - 1, 0, (struct sockaddr*) &from, &fromlen); 89 | 90 | if (buflen <= 0 || buflen >= sizeof(buf)) { 91 | return; 92 | } 93 | 94 | record_traffic(buflen, 0); 95 | 96 | // The DHT code expects the message to be null-terminated. 97 | buf[buflen] = '\0'; 98 | } 99 | 100 | if (buflen > 0) { 101 | // Handle incoming data 102 | time_t time_wait = 0; 103 | socklen_t fromlen = sizeof(from); 104 | rc = dht_periodic(buf, buflen, (struct sockaddr*) &from, fromlen, &time_wait, dht_callback_func, NULL); 105 | 106 | if (rc < 0 && errno != EINTR) { 107 | if (rc == EINVAL || rc == EFAULT) { 108 | log_error("KAD: Error calling dht_periodic"); 109 | exit(1); 110 | } 111 | g_dht_maintenance = time_now_sec() + 1; 112 | } else { 113 | g_dht_maintenance = time_now_sec() + time_wait; 114 | } 115 | } else if (g_dht_maintenance <= time_now_sec()) { 116 | // Do a maintenance call 117 | time_t time_wait = 0; 118 | rc = dht_periodic(NULL, 0, NULL, 0, &time_wait, dht_callback_func, NULL); 119 | 120 | // Wait for the next maintenance call 121 | g_dht_maintenance = time_now_sec() + time_wait; 122 | //log_debug("KAD: Next maintenance call in %u seconds.", (unsigned) time_wait); 123 | } else { 124 | rc = 0; 125 | } 126 | 127 | if (rc < 0) { 128 | if (errno == EINTR) { 129 | return; 130 | } else if (rc == EINVAL || rc == EFAULT) { 131 | log_error("KAD: Error using select: %s", strerror(errno)); 132 | return; 133 | } else { 134 | g_dht_maintenance = time_now_sec() + 1; 135 | } 136 | } 137 | } 138 | 139 | /* 140 | * Kademlia needs dht_blacklisted/dht_hash/dht_random_bytes functions to be present. 141 | */ 142 | 143 | int dht_sendto(int sockfd, const void *buf, int buflen, int flags, const struct sockaddr *to, int tolen) 144 | { 145 | record_traffic(0, buflen); 146 | 147 | return sendto(sockfd, buf, buflen, flags, to, tolen); 148 | } 149 | 150 | int dht_blacklisted(const struct sockaddr *sa, int salen) 151 | { 152 | return 0; 153 | } 154 | 155 | // Hashing for the DHT - implementation does not matter for interoperability 156 | void dht_hash(void *hash_return, int hash_size, 157 | const void *v1, int len1, 158 | const void *v2, int len2, 159 | const void *v3, int len3) 160 | { 161 | union { 162 | uint8_t data[8]; 163 | uint16_t num4[4]; 164 | uint32_t num2[2]; 165 | uint64_t num1[1]; 166 | } hash; 167 | 168 | assert(len1 == 8); 169 | memcpy(&hash.data, v1, 8); 170 | 171 | assert(len2 == 4 || len2 == 16); 172 | if (len2 == 4) { 173 | const uint32_t d2 = *((uint32_t*) v2); 174 | hash.num2[0] ^= d2; 175 | hash.num2[1] ^= d2; 176 | } else { 177 | hash.num1[0] ^= *((uint64_t*) v2); 178 | hash.num1[0] ^= *((uint64_t*) v2 + 8); 179 | } 180 | 181 | assert(len3 == 2); 182 | const uint16_t d3 = *((uint16_t*) v3); 183 | hash.num4[0] ^= d3; 184 | hash.num4[1] ^= d3; 185 | hash.num4[2] ^= d3; 186 | hash.num4[3] ^= d3; 187 | 188 | assert(hash_size == 8); 189 | memcpy(hash_return, &hash.data, 8); 190 | } 191 | 192 | int dht_random_bytes(void *buf, size_t size) 193 | { 194 | return bytes_random(buf, size); 195 | } 196 | 197 | bool kad_setup(void) 198 | { 199 | uint8_t node_id[SHA1_BIN_LENGTH]; 200 | int af = gconf->af; 201 | 202 | #ifdef DEBUG 203 | // Let the DHT output debug text 204 | dht_debug = stdout; 205 | #endif 206 | 207 | bytes_random(node_id, SHA1_BIN_LENGTH); 208 | 209 | if (af == AF_INET || af == AF_UNSPEC) { 210 | g_dht_socket4 = net_bind("KAD", "0.0.0.0", gconf->dht_port, gconf->dht_ifname, IPPROTO_UDP); 211 | } 212 | 213 | if (af == AF_INET6 || af == AF_UNSPEC) { 214 | g_dht_socket6 = net_bind("KAD", "::", gconf->dht_port, gconf->dht_ifname, IPPROTO_UDP); 215 | } 216 | 217 | if (g_dht_socket4 >= 0) { 218 | net_add_handler(g_dht_socket4, &dht_handler); 219 | } 220 | 221 | if (g_dht_socket6 >= 0) { 222 | net_add_handler(g_dht_socket6, &dht_handler); 223 | } 224 | 225 | if (g_dht_socket4 < 0 && g_dht_socket6 < 0) { 226 | return false; 227 | } 228 | 229 | // Init the DHT. Also set the sockets into non-blocking mode. 230 | if (dht_init(g_dht_socket4, g_dht_socket6, node_id, (uint8_t*) "DD\0\0") < 0) { 231 | log_error("KAD: Failed to initialize the DHT."); 232 | return false; 233 | } 234 | 235 | return true; 236 | } 237 | 238 | void kad_free(void) 239 | { 240 | dht_uninit(); 241 | } 242 | 243 | static unsigned kad_count_bucket(const struct bucket *bucket, bool good) 244 | { 245 | unsigned count = 0; 246 | 247 | while (bucket) { 248 | if (good) { 249 | struct node *node = bucket->nodes; 250 | while (node) { 251 | count += node_good(node) ? 1 : 0; 252 | node = node->next; 253 | } 254 | } else { 255 | count += bucket->count; 256 | } 257 | bucket = bucket->next; 258 | } 259 | 260 | return count; 261 | } 262 | 263 | int kad_count_nodes(bool good) 264 | { 265 | // count nodes in IPv4 and IPv6 buckets 266 | return kad_count_bucket(buckets, good) + kad_count_bucket(buckets6, good); 267 | } 268 | 269 | void kad_status(FILE *fp) 270 | { 271 | struct storage *strg = storage; 272 | struct search *srch = searches; 273 | struct announcement_t *announcement = announces_get(); 274 | int numsearches4_active = 0; 275 | int numsearches4_done = 0; 276 | int numsearches6_active = 0; 277 | int numsearches6_done = 0; 278 | int numstorage = 0; 279 | int numstorage_peers = 0; 280 | int numannounces = 0; 281 | 282 | // Count searches 283 | while (srch) { 284 | if (srch->af == AF_INET6) { 285 | if (srch->done) { 286 | numsearches6_done += 1; 287 | } else { 288 | numsearches6_active += 1; 289 | } 290 | } else { 291 | if (srch->done) { 292 | numsearches4_done += 1; 293 | } else { 294 | numsearches4_active += 1; 295 | } 296 | } 297 | srch = srch->next; 298 | } 299 | 300 | // Count storage and peers 301 | while (strg) { 302 | numstorage_peers += strg->numpeers; 303 | numstorage += 1; 304 | strg = strg->next; 305 | } 306 | 307 | while (announcement) { 308 | numannounces += 1; 309 | announcement = announcement->next; 310 | } 311 | 312 | // Use dht data structure! 313 | int nodes4 = kad_count_bucket(buckets, false); 314 | int nodes6 = kad_count_bucket(buckets6, false); 315 | int nodes4_good = kad_count_bucket(buckets, true); 316 | int nodes6_good = kad_count_bucket(buckets6, true); 317 | 318 | clear_old_traffic_counters(); 319 | uint32_t traffic_sum_in = 0; 320 | uint32_t traffic_sum_out = 0; 321 | for (size_t i = 0; i < TRAFFIC_DURATION_SECONDS; ++i) { 322 | traffic_sum_in += gconf->traffic_in[i]; 323 | traffic_sum_out += gconf->traffic_out[i]; 324 | } 325 | 326 | fprintf( 327 | fp, 328 | "%s\n" 329 | "DHT id: %s\n" 330 | "DHT uptime: %s\n" 331 | "DHT listen on: %s / device: %s / port: %d\n" 332 | "DHT nodes: %d IPv4 (%d good), %d IPv6 (%d good)\n" 333 | "DHT storage: %d entries with %d addresses\n" 334 | "DHT searches: %d IPv4 (%d done), %d IPv6 active (%d done)\n" 335 | "DHT announcements: %d\n" 336 | "DHT blocklist: %d\n" 337 | "DHT traffic: %s, %s/s (in) / %s, %s/s (out)\n", 338 | dhtd_version_str, 339 | str_id(myid), 340 | str_time(gconf->time_now - gconf->startup_time), 341 | str_af(gconf->af), gconf->dht_ifname ? gconf->dht_ifname : "", gconf->dht_port, 342 | nodes4, nodes4_good, nodes6, nodes6_good, 343 | numstorage, numstorage_peers, 344 | numsearches4_active, numsearches4_done, numsearches6_active, numsearches6_done, 345 | numannounces, 346 | (next_blacklisted % DHT_MAX_BLACKLISTED), 347 | str_bytes(gconf->traffic_in_sum), 348 | str_bytes(traffic_sum_in / TRAFFIC_DURATION_SECONDS), 349 | str_bytes(gconf->traffic_out_sum), 350 | str_bytes(traffic_sum_out / TRAFFIC_DURATION_SECONDS) 351 | ); 352 | } 353 | 354 | bool kad_ping(const IP* addr) 355 | { 356 | return dht_ping_node((struct sockaddr *)addr, addr_len(addr)) >= 0; 357 | } 358 | 359 | bool kad_start_search(FILE *fp, const uint8_t id[], uint16_t port) 360 | { 361 | int af = gconf->af; 362 | int rc4 = -1; 363 | int rc6 = -1; 364 | 365 | if (af == AF_UNSPEC || af == AF_INET) { 366 | rc4 = dht_search(id, port, AF_INET, dht_callback_func, NULL); 367 | } 368 | 369 | if (af == AF_UNSPEC || af == AF_INET6) { 370 | rc6 = dht_search(id, port, AF_INET6, dht_callback_func, NULL); 371 | } 372 | 373 | if (rc4 == 1 || rc6 == 1) { 374 | if (fp) fprintf(fp, "Search started.\n"); 375 | return true; 376 | } 377 | 378 | if (rc4 == 0 || rc6 == 0) { 379 | if (fp) fprintf(fp, "Search in progress.\n"); 380 | return true; 381 | } 382 | 383 | if (fp) fprintf(fp, "Failed to start search.\n"); 384 | 385 | return false; 386 | } 387 | 388 | bool kad_block(const IP* addr) 389 | { 390 | blacklist_node(NULL, (struct sockaddr *) addr, sizeof(IP)); 391 | 392 | return true; 393 | } 394 | 395 | // Export known peers; the maximum is 400 nodes 396 | int kad_export_peers(FILE *fp) 397 | { 398 | // get number of good nodes 399 | int num4 = kad_count_bucket(buckets, true); 400 | int num6 = kad_count_bucket(buckets6, true); 401 | 402 | IP4 *addr4 = (IP4*) malloc(num4 * sizeof(IP4)); 403 | IP6 *addr6 = (IP6*) malloc(num6 * sizeof(IP6)); 404 | 405 | if (addr4 == NULL) { 406 | num4 = 0; 407 | } 408 | 409 | if (addr6 == NULL) { 410 | num6 = 0; 411 | } 412 | 413 | dht_get_nodes(addr4, &num4, addr6, &num6); 414 | 415 | for (size_t i = 0; i < num4; ++i) { 416 | #ifdef __CYGWIN__ 417 | fprintf(fp, "%s\r\n", str_addr((IP*) &addr4[i])); 418 | #else 419 | fprintf(fp, "%s\n", str_addr((IP*) &addr4[i])); 420 | #endif 421 | } 422 | 423 | for (size_t i = 0; i < num6; ++i) { 424 | #ifdef __CYGWIN__ 425 | fprintf(fp, "%s\r\n", str_addr((IP*) &addr6[i])); 426 | #else 427 | fprintf(fp, "%s\n", str_addr((IP*) &addr6[i])); 428 | #endif 429 | } 430 | 431 | if (addr4) { 432 | free(addr4); 433 | } 434 | 435 | if (addr6) { 436 | free(addr6); 437 | } 438 | 439 | return num4 + num6; 440 | } 441 | 442 | static void kad_print_buckets_interal(FILE* fp, int af, const struct bucket *b) 443 | { 444 | unsigned bucket_i, node_i, all_nodes = 0; 445 | 446 | for (bucket_i = 0; b; ++bucket_i) { 447 | fprintf(fp, " bucket: %s\n", str_id(b->first)); 448 | 449 | struct node *n = b->nodes; 450 | for (node_i = 0; n; ++node_i) { 451 | fprintf(fp, " id: %s\n", str_id(n->id)); 452 | fprintf(fp, " address: %s\n", str_addr(&n->ss)); 453 | fprintf(fp, " pinged: %d\n", n->pinged); 454 | n = n->next; 455 | } 456 | fprintf(fp, " %u nodes.\n", node_i); 457 | all_nodes += node_i; 458 | b = b->next; 459 | } 460 | 461 | fprintf(fp, "Found %u %s buckets with %u nodes.\n", bucket_i, (af == AF_INET) ? "IPv4" : "IPv6", all_nodes); 462 | } 463 | 464 | // Print buckets (leaf/finger table) 465 | void kad_print_buckets(FILE* fp) 466 | { 467 | int af = gconf->af; 468 | 469 | if (af == AF_UNSPEC || af == AF_INET) { 470 | kad_print_buckets_interal(fp, AF_INET, buckets); 471 | } 472 | 473 | if (af == AF_UNSPEC || af == AF_INET6) { 474 | kad_print_buckets_interal(fp, AF_INET6, buckets6); 475 | } 476 | } 477 | 478 | // Print searches 479 | void kad_print_searches(FILE *fp) 480 | { 481 | size_t i; 482 | 483 | struct search *s = searches; 484 | for (i = 0; s; ++i) { 485 | fprintf(fp, " id: %s\n", str_id(s->id)); 486 | fprintf(fp, " net: %s, port: %u, done: %s\n", 487 | (s->af == AF_INET) ? "IPv4" : "IPv6", 488 | (unsigned) s->port, 489 | s->done ? "true" : "false" 490 | ); 491 | fprintf(fp, " results: %u\n", (unsigned) results_count(s->id, s->af)); 492 | /* 493 | if (do_print_nodes) { 494 | for (j = 0; j < s->numnodes; ++j) { 495 | struct search_node *sn = &s->nodes[j]; 496 | fprintf(fp, " node: %s\n", str_id(sn->id)); 497 | fprintf(fp, " address: %s\n", str_addr(&sn->ss)); 498 | fprintf(fp, " pinged: %d, pinged: %d, acked: %d\n", 499 | sn->pinged, sn->replied, sn->acked); 500 | } 501 | fprintf(fp, " Found %u nodes.\n", (unsigned) j); 502 | } else { 503 | fprintf(fp, " nodes: %u\n", (unsigned) s->numnodes); 504 | }*/ 505 | s = s->next; 506 | } 507 | 508 | fprintf(fp, " Found %u searches\n", (unsigned) i); 509 | } 510 | 511 | // Print announced ids we have received 512 | void kad_print_storage(FILE *fp) 513 | { 514 | size_t i, j; 515 | 516 | struct storage *s = storage; 517 | for (i = 0; s; ++i) { 518 | fprintf(fp, " id: %s\n", str_id(s->id)); 519 | for (j = 0; j < s->numpeers; ++j) { 520 | struct peer *p = &s->peers[j]; 521 | fprintf(fp, " address: %s\n", str_addr2(&p->ip[0], p->len, p->port)); 522 | } 523 | fprintf(fp, " Found %u addresses.\n", (unsigned) j); 524 | s = s->next; 525 | } 526 | fprintf(fp, " Found %u stored hashes from received announcements.\n", (unsigned) i); 527 | } 528 | 529 | void kad_print_blocklist(FILE *fp) 530 | { 531 | size_t i; 532 | 533 | for (i = 0; i < (next_blacklisted % DHT_MAX_BLACKLISTED); i++) { 534 | fprintf(fp, " %s\n", str_addr(&blacklist[i])); 535 | } 536 | 537 | fprintf(fp, " Found %u blocked addresses.\n", (unsigned) i); 538 | } 539 | 540 | void kad_print_constants(FILE *fp) 541 | { 542 | fprintf(fp, "DHT_SEARCH_EXPIRE_TIME: %d\n", DHT_SEARCH_EXPIRE_TIME); 543 | fprintf(fp, "DHT_MAX_SEARCHES: %d\n", DHT_MAX_SEARCHES); 544 | 545 | // Maximum number of announced hashes we track 546 | fprintf(fp, "DHT_MAX_HASHES: %d\n", DHT_MAX_HASHES); 547 | 548 | // Maximum number of peers for each announced hash we track 549 | fprintf(fp, "DHT_MAX_PEERS: %d\n", DHT_MAX_PEERS); 550 | 551 | // Maximum number of blocked nodes 552 | fprintf(fp, "DHT_MAX_BLACKLISTED: %d\n", DHT_MAX_BLACKLISTED); 553 | 554 | fprintf(fp, "MAX_RESULTS_PER_SEARCH: %d\n", MAX_RESULTS_PER_SEARCH); 555 | } 556 | -------------------------------------------------------------------------------- /src/kad.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _KAD_H_ 3 | #define _KAD_H_ 4 | 5 | /* 6 | * Interface to interact with the DHT implementation. 7 | */ 8 | 9 | bool kad_setup(void); 10 | void kad_free(void); 11 | 12 | // Ping this node to add it to the node table 13 | bool kad_ping(const IP *addr); 14 | 15 | // Block a specific address 16 | bool kad_block(const IP* addr); 17 | 18 | bool kad_start_search(FILE *fp, const uint8_t id[], uint16_t port); 19 | 20 | // Export good peers 21 | int kad_export_peers(FILE *fp); 22 | 23 | // Print status information 24 | void kad_status(FILE *fp); 25 | 26 | // Count good or all known peers 27 | int kad_count_nodes(bool good); 28 | 29 | // Announce query until lifetime expires. 30 | bool kad_announce(const uint8_t id[], int port, time_t lifetime); 31 | 32 | void kad_print_buckets(FILE *fp); 33 | void kad_print_searches(FILE *fp); 34 | void kad_print_storage(FILE *fp); 35 | void kad_print_blocklist(FILE *fp); 36 | void kad_print_constants(FILE *fp); 37 | 38 | #endif // _KAD_H_ 39 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "main.h" 9 | #include "conf.h" 10 | #include "log.h" 11 | 12 | 13 | #ifdef DEBUG 14 | 15 | // Program start time 16 | static struct timespec log_start = { 0, 0 }; 17 | 18 | const char *log_time(void) 19 | { 20 | static char buf[16]; 21 | struct timespec now = { 0, 0 }; 22 | 23 | clock_gettime(CLOCK_MONOTONIC, &now); 24 | 25 | // Initialize clock 26 | if (log_start.tv_sec == 0 && log_start.tv_nsec == 0) { 27 | clock_gettime(CLOCK_MONOTONIC, &log_start); 28 | } 29 | 30 | sprintf(buf, "[%8.2f] ", 31 | ((double) now.tv_sec + 1.0e-9 * (double)now.tv_nsec) - 32 | ((double) log_start.tv_sec + 1.0e-9 * (double)log_start.tv_nsec) 33 | ); 34 | 35 | return buf; 36 | } 37 | 38 | #endif 39 | 40 | void log_print(int priority, const char format[], ...) 41 | { 42 | char buf[1024]; 43 | const char *time; 44 | va_list vlist; 45 | 46 | va_start(vlist, format); 47 | vsnprintf(buf, sizeof(buf), format, vlist); 48 | va_end(vlist); 49 | 50 | #ifdef DEBUG 51 | time = log_time(); 52 | #else 53 | time = ""; 54 | #endif 55 | 56 | if (gconf != NULL && gconf->use_syslog) { 57 | // Write messages to e.g. /var/log/syslog 58 | openlog(PROGRAM_NAME, LOG_PID | LOG_CONS, LOG_USER | LOG_PERROR); 59 | syslog(priority, "%s%s", time, buf); 60 | closelog(); 61 | } else { 62 | FILE *out = (priority == LOG_ERR) ? stderr : stdout; 63 | fprintf(out, "%s%s\n", time, buf); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _LOG_H_ 3 | #define _LOG_H_ 4 | 5 | 6 | #include 7 | 8 | // Verbosity levels 9 | enum { 10 | VERBOSITY_DEBUG, 11 | VERBOSITY_VERBOSE, 12 | VERBOSITY_QUIET 13 | }; 14 | 15 | #define log_error(...) \ 16 | log_print(LOG_ERR, __VA_ARGS__); 17 | 18 | #define log_info(...) \ 19 | do { \ 20 | if (gconf->verbosity != VERBOSITY_QUIET) \ 21 | log_print(LOG_INFO, __VA_ARGS__); \ 22 | } while (0) 23 | 24 | #define log_warning(...) \ 25 | do { \ 26 | if (gconf->verbosity != VERBOSITY_QUIET) \ 27 | log_print(LOG_WARNING, __VA_ARGS__); \ 28 | } while (0) 29 | 30 | #ifdef DEBUG 31 | #define log_debug(...) \ 32 | do { \ 33 | if (gconf->verbosity == VERBOSITY_DEBUG) \ 34 | log_print(LOG_DEBUG, __VA_ARGS__); \ 35 | } while (0) 36 | #else 37 | #define log_debug(...) // Exclude debug messages from debug build 38 | #endif 39 | 40 | // Print a log message 41 | void log_print(int priority, const char format[], ...); 42 | 43 | #endif // _LOG_H_ 44 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef __CYGWIN__ 7 | #include 8 | #endif 9 | 10 | #include "main.h" 11 | #include "conf.h" 12 | #include "log.h" 13 | #include "kad.h" 14 | #include "utils.h" 15 | #include "unix.h" 16 | #include "net.h" 17 | #include "announces.h" 18 | #include "peerfile.h" 19 | #ifdef __CYGWIN__ 20 | #include "windows.h" 21 | #endif 22 | 23 | #ifdef LPD 24 | #include "ext-lpd.h" 25 | #endif 26 | #include "ext-cli.h" 27 | 28 | static bool g_pidfile_written = false; 29 | 30 | 31 | int main_run(void) 32 | { 33 | bool rc = true; 34 | 35 | /* Run setup */ 36 | if (!conf_load()) { 37 | return EXIT_FAILURE; 38 | } 39 | 40 | // Setup the Kademlia DHT 41 | rc &= kad_setup(); 42 | 43 | // Setup handler for announcements 44 | announces_setup(); 45 | 46 | // Setup import of peerfile 47 | peerfile_setup(); 48 | 49 | // Setup extensions 50 | #ifdef LPD 51 | rc &= lpd_setup(); 52 | #endif 53 | 54 | #ifdef CLI 55 | rc &= cli_setup(); 56 | #endif 57 | 58 | /* Run program */ 59 | if (rc) { 60 | // Loop over all sockets and file descriptors 61 | net_loop(); 62 | log_info("Shutting down..."); 63 | } 64 | 65 | // Export peers if a file is provided 66 | peerfile_export(); 67 | 68 | /* Free resources */ 69 | 70 | #ifdef CLI 71 | cli_free(); 72 | #endif 73 | #ifdef LPD 74 | lpd_free(); 75 | #endif 76 | 77 | peerfile_free(); 78 | 79 | announces_free(); 80 | 81 | kad_free(); 82 | 83 | conf_free(); 84 | 85 | net_free(); 86 | 87 | if (g_pidfile_written) { 88 | unlink(gconf->pidfile); 89 | } 90 | 91 | return rc ? EXIT_SUCCESS : EXIT_FAILURE; 92 | } 93 | 94 | #ifdef __CYGWIN__ 95 | int main(int argc, char *argv[]) 96 | { 97 | char cmd[512]; 98 | char path[256]; 99 | char *p; 100 | 101 | #ifdef CLI 102 | if (strstr(argv[0], "dhtd-ctl")) { 103 | return cli_client(argc, argv); 104 | } 105 | #endif 106 | 107 | if (!conf_setup(argc, argv)) { 108 | return EXIT_FAILURE; 109 | } 110 | 111 | if (gconf->service_start) { 112 | gconf->use_syslog = true; 113 | 114 | // Get dhtd.exe binary lcoation 115 | if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strrchr(path, '\\'))) { 116 | *(p + 1) = '\0'; 117 | } else { 118 | log_error("Cannot get location of dhtd binary."); 119 | exit(1); 120 | } 121 | 122 | // Set DNS server to localhost 123 | sprintf(cmd, "cmd.exe /c \"%s\\dns_setup.bat\"", path); 124 | windows_exec(cmd); 125 | 126 | int rc = windows_service_start((void (*)()) main_run); 127 | 128 | // Reset DNS settings to DHCP 129 | sprintf(cmd, "cmd.exe /c \"%s\\dns_reset.bat\"", path); 130 | windows_exec(cmd); 131 | 132 | return rc; 133 | } 134 | 135 | if (gconf->is_daemon) { 136 | gconf->use_syslog = true; 137 | 138 | // Close pipes 139 | fclose(stderr); 140 | fclose(stdout); 141 | fclose(stdin); 142 | 143 | // Fork before any threads are started 144 | unix_fork(); 145 | 146 | // Change working directory to C:\ directory or disk equivalent 147 | if (GetModuleFileNameA(NULL, path, sizeof(path)) && (p = strchr(path, '\\'))) { 148 | *(p + 1) = 0; 149 | SetCurrentDirectoryA(path); 150 | } 151 | 152 | } else { 153 | conf_info(); 154 | } 155 | 156 | // Catch signals 157 | windows_signals(); 158 | 159 | // Write pid file 160 | if (gconf->pidfile) { 161 | unix_write_pidfile(GetCurrentProcessId(), gconf->pidfile); 162 | g_pidfile_written = true; 163 | } 164 | 165 | // Drop privileges 166 | unix_dropuid0(); 167 | 168 | return main_run(); 169 | } 170 | #else 171 | int main(int argc, char *argv[]) 172 | { 173 | if (strstr(argv[0], "dhtd-ctl")) { 174 | return cli_client(argc, argv); 175 | } 176 | 177 | if (!conf_setup(argc, argv)) { 178 | return EXIT_FAILURE; 179 | } 180 | 181 | if (gconf->is_daemon) { 182 | gconf->use_syslog = true; 183 | 184 | // Close pipes 185 | fclose(stderr); 186 | fclose(stdout); 187 | fclose(stdin); 188 | 189 | // Fork before any threads are started 190 | unix_fork(); 191 | 192 | if (chdir("/") != 0) { 193 | log_error("Changing working directory to '/' failed: %s", strerror(errno)); 194 | exit(1); 195 | } 196 | } else { 197 | conf_info(); 198 | } 199 | 200 | // Catch signals 201 | unix_signals(); 202 | 203 | // Write pid file 204 | if (gconf->pidfile) { 205 | unix_write_pidfile(getpid(), gconf->pidfile); 206 | g_pidfile_written = true; 207 | } 208 | 209 | // Drop privileges 210 | unix_dropuid0(); 211 | 212 | return main_run(); 213 | } 214 | #endif 215 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _MAIN_H_ 3 | #define _MAIN_H_ 4 | 5 | 6 | #include 7 | 8 | #define PROGRAM_NAME "DHTd" 9 | #define PROGRAM_VERSION "1.0.5" 10 | 11 | #define SHA1_BIN_LENGTH 20 12 | 13 | // Default addresses and ports 14 | #define LPD_ADDR4 "239.192.152.143" 15 | #define LPD_ADDR6 "ff15::efc0:988f" 16 | #define CLI_PATH "/tmp/dhtd.sock" 17 | #define LPD_PORT 6771 18 | #define DHT_PORT 6881 19 | 20 | typedef struct sockaddr_storage IP; 21 | typedef struct sockaddr_in IP4; 22 | typedef struct sockaddr_in6 IP6; 23 | 24 | 25 | void main_setup(void); 26 | void main_free(void); 27 | 28 | 29 | #endif // _MAIN_H_ 30 | -------------------------------------------------------------------------------- /src/net.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include // close() 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "main.h" 18 | #include "conf.h" 19 | #include "log.h" 20 | #include "utils.h" 21 | #include "net.h" 22 | 23 | 24 | static struct pollfd g_fds[16] = { 0 }; 25 | static net_callback* g_cbs[16] = { NULL }; 26 | static int g_count = 0; 27 | static bool g_entry_removed = false; 28 | 29 | 30 | // Set a socket non-blocking 31 | int net_set_nonblocking(int fd) 32 | { 33 | return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 34 | } 35 | 36 | void net_add_handler(int fd, net_callback *cb) 37 | { 38 | if (cb == NULL) { 39 | log_error("net_add_handler() Callback is null."); 40 | exit(1); 41 | } 42 | 43 | if (g_count == ARRAY_SIZE(g_cbs)) { 44 | log_error("net_add_handler() No more space for handlers."); 45 | exit(1); 46 | } 47 | 48 | if (fd >= 0) { 49 | net_set_nonblocking(fd); 50 | } 51 | 52 | g_cbs[g_count] = cb; 53 | g_fds[g_count].fd = fd; 54 | g_fds[g_count].events = POLLIN; 55 | 56 | g_count += 1; 57 | } 58 | 59 | void net_remove_handler(int fd, net_callback *cb) 60 | { 61 | if (cb == NULL) { 62 | log_error("net_remove_handler() callback is null"); 63 | exit(1); 64 | } 65 | 66 | for (size_t i = 0; i < g_count; i++) { 67 | if (g_cbs[i] == cb && g_fds[i].fd == fd) { 68 | // mark for removal in compress_entries() 69 | g_cbs[i] = NULL; 70 | g_entry_removed = true; 71 | return; 72 | } 73 | } 74 | 75 | log_error("net_remove_handler() handler not found"); 76 | exit(1); 77 | } 78 | 79 | static void compress_entries(void) 80 | { 81 | for (size_t i = 0; i < g_count; i += 1) { 82 | while (g_cbs[i] == NULL && i < g_count) { 83 | g_count -= 1; 84 | g_cbs[i] = g_cbs[g_count]; 85 | g_fds[i].fd = g_fds[g_count].fd; 86 | g_fds[i].events = g_fds[g_count].events; 87 | } 88 | } 89 | } 90 | 91 | void net_loop(void) 92 | { 93 | bool call_all = false; 94 | time_t call_all_time = time(NULL); 95 | 96 | // call all callbacks immediately 97 | for (size_t i = 0; i < g_count; i++) { 98 | g_cbs[i](-1, g_fds[i].fd); 99 | } 100 | 101 | while (gconf->is_running) { 102 | int rc = poll(g_fds, g_count, 1000); 103 | 104 | if (rc < 0) { 105 | //log_error("poll(): %s", strerror(errno)); 106 | break; 107 | } 108 | 109 | gconf->time_now = time(NULL); 110 | 111 | if ((gconf->time_now - call_all_time) >= 1) { 112 | call_all = true; 113 | call_all_time = gconf->time_now; 114 | } else { 115 | call_all = false; 116 | } 117 | 118 | for (size_t i = 0; i < g_count; i++) { 119 | int revents = g_fds[i].revents; 120 | if (revents || call_all) { 121 | g_cbs[i](revents, g_fds[i].fd); 122 | } 123 | } 124 | 125 | if (g_entry_removed) { 126 | compress_entries(); 127 | g_entry_removed = false; 128 | } 129 | } 130 | } 131 | 132 | int net_socket(const char name[], const char ifname[], const int protocol, const int af) 133 | { 134 | const int opt_on = 1; 135 | int sock = -1; 136 | 137 | // Disable IPv6 or IPv4 138 | if (gconf->af != AF_UNSPEC && gconf->af != af) { 139 | goto fail; 140 | } 141 | 142 | if ((sock = socket(af, (protocol == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM, protocol)) < 0) { 143 | log_error("%s: Failed to create socket: %s", name, strerror(errno)); 144 | goto fail; 145 | } 146 | 147 | if (net_set_nonblocking(sock) < 0) { 148 | log_error("%s: Failed to make socket nonblocking: %s", name, strerror(errno)); 149 | goto fail; 150 | } 151 | 152 | #if defined(__APPLE__) || defined(__CYGWIN__) || defined(__FreeBSD__) 153 | if (ifname) { 154 | log_error("%s: Bind to device not supported on Windows, MacOSX and FreeBSD.", name); 155 | goto fail; 156 | } 157 | #else 158 | if (ifname && setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname))) { 159 | log_error("%s: Unable to bind to device %s: %s", name, ifname, strerror(errno)); 160 | goto fail; 161 | } 162 | #endif 163 | 164 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt_on, sizeof(opt_on)) < 0) { 165 | log_error("%s: Unable to set SO_REUSEADDR: %s", name, strerror(errno)); 166 | goto fail; 167 | } 168 | 169 | return sock; 170 | 171 | fail: 172 | close(sock); 173 | 174 | return -1; 175 | } 176 | 177 | int net_bind( 178 | const char name[], 179 | const char addr[], 180 | const int port, 181 | const char ifname[], 182 | const int protocol) 183 | { 184 | const int opt_on = 1; 185 | socklen_t addrlen; 186 | IP sockaddr; 187 | int sock = -1; 188 | 189 | if (!addr_parse(&sockaddr, addr, "0", AF_UNSPEC)) { 190 | log_error("%s: Failed to parse IP address '%s'", name, addr); 191 | goto fail; 192 | } 193 | 194 | port_set(&sockaddr, port); 195 | 196 | if ((sock = net_socket(name, ifname, protocol, sockaddr.ss_family)) < 0) { 197 | goto fail; 198 | } 199 | 200 | if (sockaddr.ss_family == AF_INET6) { 201 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt_on, sizeof(opt_on)) < 0) { 202 | log_error("%s: Failed to set IPV6_V6ONLY for %s: %s", 203 | name, str_addr(&sockaddr), strerror(errno)); 204 | goto fail; 205 | } 206 | } 207 | 208 | addrlen = addr_len(&sockaddr); 209 | if (bind(sock, (struct sockaddr*) &sockaddr, addrlen) < 0) { 210 | log_error("%s: Failed to bind socket to %s: %s", 211 | name, str_addr(&sockaddr), strerror(errno) 212 | ); 213 | goto fail; 214 | } 215 | 216 | if (protocol == IPPROTO_TCP && listen(sock, 5) < 0) { 217 | log_error("%s: Failed to listen on %s: %s", 218 | name, str_addr(&sockaddr), strerror(errno) 219 | ); 220 | goto fail; 221 | } 222 | 223 | log_info(ifname ? "%s: Bind to %s, interface %s" : "%s: Bind to %s", 224 | name, str_addr(&sockaddr), ifname 225 | ); 226 | 227 | return sock; 228 | 229 | fail: 230 | close(sock); 231 | return -1; 232 | } 233 | 234 | void net_free(void) 235 | { 236 | int i; 237 | 238 | for (i = 0; i < g_count; i++) { 239 | g_cbs[i] = NULL; 240 | close(g_fds[i].fd); 241 | g_fds[i] = (struct pollfd){ 0 }; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _NET_H 3 | #define _NET_H 4 | 5 | 6 | // Callback for event loop 7 | typedef void net_callback(int revents, int fd); 8 | 9 | // Create a socket and bind to interface 10 | int net_socket( 11 | const char name[], 12 | const char ifname[], 13 | const int protocol, 14 | const int af 15 | ); 16 | 17 | // Create a socket and bind to address/interface 18 | int net_bind( 19 | const char name[], 20 | const char addr[], 21 | const int port, 22 | const char ifname[], 23 | const int protocol 24 | ); 25 | 26 | // Add callback with file descriptor to listen for packets 27 | void net_add_handler(int fd, net_callback *callback); 28 | 29 | // Remove callback 30 | void net_remove_handler(int fd, net_callback *callback); 31 | 32 | // Start loop for all network events 33 | void net_loop(void); 34 | 35 | // Close sockets 36 | void net_free(void); 37 | 38 | #endif // _NET_H 39 | -------------------------------------------------------------------------------- /src/peerfile.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "main.h" 10 | #include "conf.h" 11 | #include "log.h" 12 | #include "utils.h" 13 | #include "net.h" 14 | #include "kad.h" 15 | #include "peerfile.h" 16 | 17 | 18 | struct peer { 19 | struct peer *next; 20 | char* addr_str; 21 | }; 22 | 23 | // Next time to import peers from peer file 24 | static time_t peerfile_import_time = 0; 25 | 26 | // Next time to export peers to peer file 27 | static time_t peerfile_export_time = 0; 28 | 29 | // A list of static peers, given by --peer argument 30 | static struct peer *g_peers = NULL; 31 | 32 | 33 | void peerfile_export(void) 34 | { 35 | const char *filename = gconf->peerfile; 36 | if (filename == NULL) { 37 | return; 38 | } 39 | 40 | if ((time_now_sec() - gconf->startup_time) < (5 * 60)) { 41 | log_info("PEERFILE: No peers exported. DHTd needs to run at least 5 minutes."); 42 | return; 43 | } 44 | 45 | if (kad_count_nodes(true) == 0) { 46 | log_info("PEERFILE: No peers to export."); 47 | return; 48 | } 49 | 50 | FILE *fp = fopen(filename, "w"); 51 | if (fp == NULL) { 52 | log_warning("PEERFILE: Cannot open file '%s' for peer export: %s", filename, strerror(errno)); 53 | return; 54 | } 55 | 56 | log_info("PEERFILE: Export peers to %s", filename); 57 | 58 | int num = kad_export_peers(fp); 59 | fclose(fp); 60 | 61 | // No peers to export 62 | if (num <= 0) { 63 | log_info("PEERFILE: No peers to export."); 64 | return; 65 | } 66 | 67 | log_info("PEERFILE: %d peers exported: %s", num, filename); 68 | } 69 | 70 | static int peerfile_import_peer(const char addr_str[]) 71 | { 72 | const char *port_str = STR(DHT_PORT); 73 | bool parsed4 = false; 74 | bool pinged4 = false; 75 | bool parsed6 = false; 76 | bool pinged6 = false; 77 | IP addr = {0}; 78 | int af = gconf->af; 79 | 80 | if (af == AF_UNSPEC || af == AF_INET6) { 81 | if (addr_parse(&addr, addr_str, port_str, AF_INET6)) { 82 | parsed6 = true; 83 | if (kad_ping(&addr)) { 84 | pinged6 = true; 85 | } 86 | } 87 | } 88 | 89 | if (af == AF_UNSPEC || af == AF_INET) { 90 | if (addr_parse(&addr, addr_str, port_str, AF_INET)) { 91 | parsed4 = true; 92 | if (kad_ping(&addr)) { 93 | pinged4 = true; 94 | } 95 | } 96 | } 97 | 98 | if (!parsed4 && !parsed6) { 99 | log_warning("PEERFILE: Cannot resolve address: '%s'", addr_str); 100 | return 0; 101 | } 102 | 103 | if (!pinged4 && !pinged6) { 104 | log_warning("PEERFILE: Cannot ping address: '%s'", addr_str); 105 | return 0; 106 | } 107 | 108 | // one node pinged 109 | return 1; 110 | } 111 | 112 | static void peerfile_import(void) 113 | { 114 | const char * filename = gconf->peerfile; 115 | if (filename == NULL) { 116 | return; 117 | } 118 | 119 | FILE *fp = fopen(filename, "r"); 120 | if (fp == NULL) { 121 | log_warning("PEERFILE: Cannot open file for peer import: %s (%s)", filename, strerror(errno)); 122 | return; 123 | } 124 | 125 | int num = 0; 126 | char linebuf[256]; 127 | while (fgets(linebuf, sizeof(linebuf), fp) != NULL && gconf->is_running) { 128 | linebuf[strcspn(linebuf, "\n\r")] = '\0'; 129 | 130 | if (linebuf[0] == '\0' || linebuf[0] == '#') { 131 | continue; 132 | } 133 | 134 | num += peerfile_import_peer(linebuf); 135 | } 136 | 137 | fclose(fp); 138 | 139 | log_info("PEERFILE: Imported %d peers from %s", num, filename); 140 | } 141 | 142 | static void peerfile_import_static(const struct peer *peers) 143 | { 144 | int num = 0; 145 | while (peers) { 146 | num += peerfile_import_peer(peers->addr_str); 147 | peers = peers->next; 148 | } 149 | 150 | if (num > 0) { 151 | log_info("PEERFILE: Imported %d static peers.", num); 152 | } 153 | } 154 | 155 | bool peerfile_add_peer(const char addr_str[]) 156 | { 157 | struct peer *new = (struct peer *) malloc(sizeof(struct peer)); 158 | new->addr_str = strdup(addr_str); 159 | 160 | // prepend to list 161 | new->next = g_peers; 162 | g_peers = new; 163 | 164 | return true; 165 | } 166 | 167 | static void peerfile_handle_peerfile(int _rc, int _sock) 168 | { 169 | // We know no peers 170 | if (peerfile_import_time <= time_now_sec() && kad_count_nodes(false) == 0) { 171 | // Ping peers from peerfile, if present 172 | peerfile_import(); 173 | 174 | // Import static peers 175 | peerfile_import_static(g_peers); 176 | 177 | // Try again in ~5 minutes 178 | peerfile_import_time = time_add_mins(5); 179 | } 180 | 181 | // We know good peers 182 | if (peerfile_export_time <= time_now_sec() && kad_count_nodes(true) != 0) { 183 | // Export peers 184 | peerfile_export(); 185 | 186 | // Try again in 24 hours 187 | peerfile_export_time = time_add_hours(24); 188 | } 189 | } 190 | 191 | void peerfile_setup(void) 192 | { 193 | peerfile_import_time = time_add_secs(10); 194 | peerfile_export_time = time_add_hours(24); 195 | net_add_handler(-1 , &peerfile_handle_peerfile); 196 | } 197 | 198 | void peerfile_free(void) 199 | { 200 | struct peer *next; 201 | struct peer *p; 202 | 203 | p = g_peers; 204 | while (p) { 205 | next = p->next; 206 | free(p->addr_str); 207 | free(p); 208 | p = next; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/peerfile.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _PEERFILE_H 3 | #define _PEERFILE_H 4 | 5 | 6 | /* 7 | * Ping nodes from a given peerfile as long as no nodes are known. 8 | * Good nodes need also be written back to a peerfile on shutdown. 9 | */ 10 | 11 | // Setup callbacks 12 | void peerfile_setup(void); 13 | void peerfile_free(void); 14 | 15 | // Write peers to peerfile 16 | void peerfile_export(void); 17 | 18 | // Add a static peer 19 | bool peerfile_add_peer(const char addr_str[]); 20 | 21 | 22 | #endif // _PEERFILE_H 23 | -------------------------------------------------------------------------------- /src/results.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "log.h" 8 | #include "main.h" 9 | #include "conf.h" 10 | #include "utils.h" 11 | #include "net.h" 12 | #include "kad.h" 13 | #include "results.h" 14 | 15 | 16 | /* 17 | * The DHT implementation in DHTd does not store 18 | * results (IP addresses) from hash id searches. 19 | * Therefore, results are collected and stored here. 20 | */ 21 | 22 | struct result_t { 23 | uint8_t ip[16]; 24 | uint8_t length; 25 | uint16_t port; 26 | struct result_t *next; 27 | }; 28 | 29 | struct search_t { 30 | uint8_t id[SHA1_BIN_LENGTH]; 31 | uint16_t numresults4; 32 | uint16_t numresults6; 33 | uint16_t maxresults; // IPv4 + IPv6 34 | struct result_t *results; 35 | struct search_t *next; 36 | }; 37 | 38 | // A ring buffer for all searches 39 | static struct search_t *g_searches = NULL; 40 | 41 | 42 | static struct search_t *find_search(const uint8_t id[]) 43 | { 44 | struct search_t *search = g_searches; 45 | while (search) { 46 | if (memcmp(&search->id, id, SHA1_BIN_LENGTH) == 0) { 47 | return search; 48 | } 49 | search = search->next; 50 | } 51 | 52 | return NULL; 53 | } 54 | 55 | static struct result_t *find_result(const struct search_t *search, const uint8_t ip[16], uint16_t length, uint16_t port) 56 | { 57 | struct result_t *result = search->results; 58 | while (result) { 59 | if (length == result->length && 0 == memcmp(ip, &result->ip, length) && port == result->port) { 60 | return result; 61 | } 62 | result = result->next; 63 | } 64 | 65 | return NULL; 66 | } 67 | 68 | static void on_new_search_result(const char *path, const uint8_t id[], const uint8_t *ip, uint8_t length, uint16_t port) 69 | { 70 | char command[1024]; 71 | 72 | // call script if configured 73 | int n = snprintf(command, sizeof(command), "%s %s %s &", 74 | path, str_id(id), str_addr2(ip, length, port) 75 | ); 76 | 77 | if (n > 0 && n < sizeof(command)) { 78 | system(command); 79 | } else { 80 | log_error("system() command too long"); 81 | } 82 | } 83 | 84 | struct dht_addr4_t { 85 | uint8_t addr[4]; 86 | uint16_t port; 87 | }; 88 | 89 | struct dht_addr6_t { 90 | uint8_t addr[16]; 91 | uint16_t port; 92 | }; 93 | 94 | static void result_add(struct search_t *search, const uint8_t id[], const uint8_t *ip, uint8_t length, uint16_t port) 95 | { 96 | struct result_t *result = find_result(search, ip, length, port); 97 | if (!result) { 98 | // add new result 99 | result = calloc(1, sizeof(struct result_t)); 100 | memcpy(&result->ip, ip, length); 101 | result->length = length; 102 | result->port = port; 103 | 104 | result->next = search->results; 105 | search->results = result; 106 | 107 | if (length == 4) { 108 | search->numresults4 += 1; 109 | } else { 110 | search->numresults6 += 1; 111 | } 112 | 113 | if (gconf->execute_path) { 114 | on_new_search_result(gconf->execute_path, id, ip, length, port); 115 | } 116 | } 117 | } 118 | 119 | void results_add(const uint8_t id[], int af, const void *data, size_t data_len) 120 | { 121 | struct search_t *search = find_search(id); 122 | if (!search) { 123 | // add new search 124 | search = calloc(1, sizeof(struct search_t)); 125 | memcpy(&search->id, id, SHA1_BIN_LENGTH); 126 | search->maxresults = MAX_RESULTS_PER_SEARCH; 127 | 128 | search->next = g_searches; 129 | g_searches = search; 130 | } 131 | 132 | // current results 133 | int numresults = search->numresults4 + search->numresults6; 134 | 135 | switch (af) { 136 | case AF_INET: { 137 | size_t got = (data_len / sizeof(struct dht_addr4_t)); 138 | size_t add = MIN(got, search->maxresults - numresults); 139 | struct dht_addr4_t *data4 = (struct dht_addr4_t *) data; 140 | for (size_t i = 0; i < add; ++i) { 141 | result_add(search, id, &data4[i].addr[0], 4, (uint16_t) data4[i].port); 142 | } 143 | break; 144 | } 145 | case AF_INET6: { 146 | size_t got = (data_len / sizeof(struct dht_addr6_t)); 147 | size_t add = MIN(got, search->maxresults - numresults); 148 | struct dht_addr6_t *data6 = (struct dht_addr6_t *) data; 149 | for (size_t i = 0; i < add; ++i) { 150 | result_add(search, id, &data6[i].addr[0], 16, (uint16_t) data6[i].port); 151 | } 152 | } 153 | } 154 | } 155 | 156 | unsigned results_count(const uint8_t id[], int af) 157 | { 158 | struct search_t *search = find_search(id); 159 | if (search) switch (af) { 160 | case AF_INET: return search->numresults4; 161 | case AF_INET6: return search->numresults6; 162 | default: return search->numresults4 + search->numresults6; 163 | } 164 | return 0; 165 | } 166 | 167 | bool results_print(FILE *fp, const uint8_t id[]) 168 | { 169 | struct search_t *search = find_search(id); 170 | 171 | if (search) { 172 | struct result_t *result = search->results; 173 | while (result) { 174 | fprintf(fp, "%s\n", str_addr2(&result->ip[0], result->length, result->port)); 175 | result = result->next; 176 | } 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | 183 | // Free a search_t struct 184 | static void search_free(struct search_t *search) 185 | { 186 | struct result_t *cur; 187 | struct result_t *next; 188 | 189 | cur = search->results; 190 | while (cur) { 191 | next = cur->next; 192 | free(cur); 193 | cur = next; 194 | } 195 | 196 | free(search); 197 | } 198 | 199 | void results_clear(const uint8_t id[]) 200 | { 201 | struct search_t* prev = NULL; 202 | struct search_t* search = g_searches; 203 | 204 | while (search) { 205 | if (memcmp(search->id, id, SHA1_BIN_LENGTH) == 0) { 206 | // remove search from list and free 207 | if (prev) { 208 | prev->next = search->next; 209 | } else { 210 | g_searches = search->next; 211 | } 212 | search_free(search); 213 | break; 214 | } 215 | prev = search; 216 | search = search->next; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/results.h: -------------------------------------------------------------------------------- 1 | #ifndef _RESULTS_H 2 | #define _RESULTS_H 3 | 4 | #define MAX_RESULTS_PER_SEARCH 500 5 | 6 | void results_add(const uint8_t id[], int af, const void *data, size_t data_len); 7 | bool results_print(FILE *fp, const uint8_t id[]); 8 | void results_clear(const uint8_t id[]); 9 | unsigned results_count(const uint8_t id[], int af); 10 | 11 | #endif // _RESULTS_H 12 | -------------------------------------------------------------------------------- /src/unix.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include /* dirname() */ 14 | 15 | #include "main.h" 16 | #include "conf.h" 17 | #include "log.h" 18 | #include "unix.h" 19 | 20 | 21 | static void shutdown_handler(int signo) 22 | { 23 | // exit on second stop request 24 | if (!gconf->is_running) { 25 | exit(1); 26 | } 27 | 28 | gconf->is_running = false; 29 | } 30 | 31 | void unix_signals(void) 32 | { 33 | struct sigaction sig_stop; 34 | struct sigaction sig_term; 35 | 36 | // STRG+C aka SIGINT => Stop the program 37 | sig_stop.sa_handler = shutdown_handler; 38 | sig_stop.sa_flags = 0; 39 | if ((sigemptyset(&sig_stop.sa_mask) == -1) || (sigaction(SIGINT, &sig_stop, NULL) != 0)) { 40 | log_error("Failed to set SIGINT handler: %s", strerror(errno)); 41 | exit(1); 42 | } 43 | 44 | // SIGTERM => Stop the program gracefully 45 | sig_term.sa_handler = shutdown_handler; 46 | sig_term.sa_flags = 0; 47 | if ((sigemptyset(&sig_term.sa_mask) == -1) || (sigaction(SIGTERM, &sig_term, NULL) != 0)) { 48 | log_error("Failed to set SIGTERM handler: %s", strerror(errno)); 49 | exit(1); 50 | } 51 | 52 | // ignore SIGPIPE 53 | signal(SIGPIPE, SIG_IGN); 54 | } 55 | 56 | bool unix_create_unix_socket(const char path[], int *sock_out) 57 | { 58 | struct sockaddr_un addr = {0}; 59 | int sock = -1; 60 | 61 | if (path == NULL || strlen(path) == 0) { 62 | goto err; 63 | } 64 | 65 | if (remove(path) == -1 && errno != ENOENT) { 66 | log_warning("remove() %s", strerror(errno)); 67 | } 68 | 69 | sock = socket(AF_LOCAL, SOCK_STREAM, 0); 70 | if (sock < 0) { 71 | log_error("socket(): %s", strerror(errno)); 72 | goto err; 73 | } 74 | 75 | addr.sun_family = AF_UNIX; 76 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 77 | 78 | if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 79 | log_error("bind() %s %s", path, strerror(errno)); 80 | goto err; 81 | } 82 | 83 | if (listen(sock, 5) == -1) { 84 | goto err; 85 | } 86 | 87 | *sock_out = sock; 88 | 89 | return true; 90 | 91 | err: 92 | 93 | if (sock >= 0) { 94 | unix_remove_unix_socket(path, sock); 95 | } 96 | 97 | return false; 98 | } 99 | 100 | void unix_remove_unix_socket(const char path[], int sock_in) 101 | { 102 | char *dir; 103 | 104 | dir = dirname(strdup(path)); 105 | 106 | close(sock_in); 107 | unlink(path); 108 | rmdir(dir); 109 | free(dir); 110 | } 111 | 112 | void unix_fork(void) 113 | { 114 | pid_t pid; 115 | pid_t sid; 116 | 117 | pid = fork(); 118 | 119 | if (pid < 0) { 120 | log_error("Failed to fork: %s", strerror(errno)); 121 | exit(1); 122 | } else if (pid != 0) { 123 | // Child process 124 | exit(0); 125 | } 126 | 127 | // Become session leader 128 | sid = setsid(); 129 | if (sid < 0) { 130 | exit(1); 131 | } 132 | 133 | // Clear out the file mode creation mask 134 | umask(0); 135 | } 136 | 137 | void unix_write_pidfile(int pid, const char pidfile[]) 138 | { 139 | FILE *file; 140 | 141 | file = fopen(pidfile, "r"); 142 | if (file) { 143 | fclose(file); 144 | log_error("PID file already exists: %s", pidfile); 145 | exit(1); 146 | } 147 | 148 | file = fopen(pidfile, "w"); 149 | if (file == NULL) { 150 | log_error("Failed to open PID file: %s", strerror(errno)); 151 | exit(1); 152 | } 153 | 154 | if (fprintf(file, "%i\n", pid) < 0) { 155 | log_error("Failed to write PID file: %s", strerror(errno)); 156 | unlink(pidfile); 157 | exit(1); 158 | } 159 | 160 | if (fclose(file) < 0) { 161 | log_error("Failed to close PID file: %s", strerror(errno)); 162 | unlink(pidfile); 163 | exit(1); 164 | } 165 | } 166 | 167 | void unix_dropuid0(void) 168 | { 169 | struct passwd *pw; 170 | 171 | // Return if no user is set 172 | if (gconf->user == NULL) { 173 | return; 174 | } 175 | 176 | // Return if we are not root 177 | if (getuid() != 0) { 178 | return; 179 | } 180 | 181 | // Process is running as root, drop privileges 182 | if ((pw = getpwnam(gconf->user)) == NULL) { 183 | log_error("Dropping uid 0 failed. Set a valid user."); 184 | exit(1); 185 | } 186 | 187 | if (setenv("HOME", pw->pw_dir, 1) != 0) { 188 | log_error("Setting new $HOME failed."); 189 | exit(1); 190 | } 191 | 192 | if (setgid(pw->pw_gid) != 0) { 193 | log_error("Unable to drop group privileges"); 194 | exit(1); 195 | } 196 | 197 | if (setuid(pw->pw_uid) != 0) { 198 | log_error("Unable to drop user privileges"); 199 | exit(1); 200 | } 201 | 202 | // Test permissions 203 | if (setuid(0) != -1 || setgid(0) != -1) { 204 | log_error("We still have root privileges"); 205 | exit(1); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/unix.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _UNIX_H_ 3 | #define _UNIX_H_ 4 | 5 | void unix_signals(void); 6 | bool unix_create_unix_socket(const char path[], int *sock_out); 7 | void unix_remove_unix_socket(const char path[], int sock_in); 8 | void unix_fork(void); 9 | void unix_write_pidfile(int pid, const char pidfile[]); 10 | void unix_dropuid0(void); 11 | 12 | #endif /* _UNIX_H_ */ 13 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "main.h" 15 | #include "log.h" 16 | #include "conf.h" 17 | #include "utils.h" 18 | 19 | 20 | // separate a string into a list of arguments (int argc, char **argv) 21 | int setargs(const char **argv, int argv_size, char *args) 22 | { 23 | int count = 0; 24 | 25 | // skip spaces 26 | while (isspace(*args)) { 27 | ++args; 28 | } 29 | 30 | while (*args) { 31 | if ((count + 1) < argv_size) { 32 | argv[count] = args; 33 | } else { 34 | log_error("CLI: too many arguments"); 35 | break; 36 | } 37 | 38 | // parse word 39 | while (*args && !isspace(*args)) { 40 | ++args; 41 | } 42 | 43 | if (*args) { 44 | *args++ = '\0'; 45 | } 46 | 47 | // skip spaces 48 | while (isspace(*args)) { 49 | ++args; 50 | } 51 | 52 | count++; 53 | } 54 | 55 | argv[MIN(count, argv_size - 1)] = NULL; 56 | 57 | return count; 58 | } 59 | 60 | bool parse_id(uint8_t id[], size_t idsize, const char query[], size_t querysize) 61 | { 62 | if (bytes_from_base16(id, idsize, query, querysize)) { 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | bool is_id(const char query[]) 70 | { 71 | uint8_t id[SHA1_BIN_LENGTH]; 72 | return parse_id(id, sizeof(id), query, strlen(query)); 73 | } 74 | 75 | // "[:]" 76 | bool parse_annoucement(uint8_t id[], int *port, const char query[], int default_port) 77 | { 78 | const char *beg = query; 79 | const char *colon = strchr(beg, ':'); 80 | size_t len = strlen(query); 81 | 82 | if (colon) { 83 | int n = parse_int(colon + 1, -1); 84 | if (!port_valid(n)) { 85 | return false; 86 | } 87 | *port = n; 88 | len = colon - beg; 89 | } else { 90 | *port = default_port; 91 | } 92 | 93 | return parse_id(id, SHA1_BIN_LENGTH, query, len); 94 | } 95 | 96 | // "[:]" 97 | bool is_announcement(const char query[]) 98 | { 99 | uint8_t id[SHA1_BIN_LENGTH]; 100 | int port; 101 | return parse_annoucement(id, &port, query, -1); 102 | } 103 | 104 | static size_t base16_len(size_t len) 105 | { 106 | return 2 * len; 107 | } 108 | 109 | bool bytes_from_base16(uint8_t dst[], size_t dstsize, const char src[], size_t srcsize) 110 | { 111 | size_t i; 112 | size_t xv = 0; 113 | 114 | if (base16_len(dstsize) != srcsize) { 115 | return false; 116 | } 117 | 118 | for (i = 0; i < srcsize; ++i) { 119 | const char c = src[i]; 120 | if (c >= '0' && c <= '9') { 121 | xv += c - '0'; 122 | } else if (c >= 'a' && c <= 'f') { 123 | xv += (c - 'a') + 10; 124 | } else if (c >= 'A' && c <= 'F') { 125 | xv += (c - 'A') + 10; 126 | } else { 127 | return false; 128 | } 129 | 130 | if (i % 2) { 131 | dst[i / 2] = xv; 132 | xv = 0; 133 | } else { 134 | xv *= 16; 135 | } 136 | } 137 | 138 | return true; 139 | } 140 | 141 | char *bytes_to_base16(char dst[], size_t dstsize, const uint8_t src[], size_t srcsize) 142 | { 143 | static const char hexchars[16] = "0123456789abcdef"; 144 | 145 | // + 1 for the '\0' 146 | if (dstsize != (base16_len(srcsize) + 1)) { 147 | return NULL; 148 | } 149 | 150 | for (size_t i = 0; i < srcsize; ++i) { 151 | dst[2 * i] = hexchars[src[i] / 16]; 152 | dst[2 * i + 1] = hexchars[src[i] % 16]; 153 | } 154 | 155 | dst[2 * srcsize] = '\0'; 156 | 157 | return dst; 158 | } 159 | 160 | /* 161 | * Sanitize a query string. 162 | * Convert to lowercase. 163 | */ 164 | int query_sanitize(char buf[], size_t buflen, const char query[]) 165 | { 166 | size_t len = strlen(query); 167 | 168 | if ((len + 1) > buflen) { 169 | // Output buffer too small 170 | return EXIT_FAILURE; 171 | } 172 | 173 | // Convert to lower case 174 | for (size_t i = 0; i <= len; ++i) { 175 | buf[i] = tolower(query[i]); 176 | } 177 | 178 | return EXIT_SUCCESS; 179 | } 180 | 181 | 182 | const option_t *find_option(const option_t options[], const char name[]) 183 | { 184 | const option_t *option = options; 185 | while (option->name && name) { 186 | if (0 == strcmp(name, option->name)) { 187 | return option; 188 | } 189 | option++; 190 | } 191 | 192 | return NULL; 193 | } 194 | 195 | // Create a random port != 0 196 | int port_random(void) 197 | { 198 | uint16_t port; 199 | 200 | do { 201 | bytes_random((uint8_t*) &port, sizeof(port)); 202 | } while (port == 0); 203 | 204 | return port; 205 | } 206 | 207 | bool port_valid(int port) 208 | { 209 | return port > 0 && port <= 65536; 210 | } 211 | 212 | int parse_int(const char *s, int err) 213 | { 214 | char *endptr = NULL; 215 | const char *end = s + strlen(s); 216 | ssize_t n = strtoul(s, &endptr, 10); 217 | if (endptr != s && endptr == end && n >= INT_MIN && n < INT_MAX) { 218 | return n; 219 | } else { 220 | return err; 221 | } 222 | } 223 | 224 | bool port_set(IP *addr, uint16_t port) 225 | { 226 | switch (addr->ss_family) { 227 | case AF_INET: 228 | ((IP4 *)addr)->sin_port = htons(port); 229 | return true; 230 | case AF_INET6: 231 | ((IP6 *)addr)->sin6_port = htons(port); 232 | return true; 233 | default: 234 | return false; 235 | } 236 | } 237 | 238 | // Fill buffer with random bytes 239 | int bytes_random(uint8_t buffer[], size_t size) 240 | { 241 | int fd = open("/dev/urandom", O_RDONLY); 242 | if (fd < 0) { 243 | log_error("Failed to open /dev/urandom"); 244 | exit(1); 245 | } 246 | 247 | int rc = read(fd, buffer, size); 248 | 249 | close(fd); 250 | 251 | return rc; 252 | } 253 | 254 | bool id_equal(const uint8_t id1[], const uint8_t id2[]) 255 | { 256 | return (memcmp(id1, id2, SHA1_BIN_LENGTH) == 0); 257 | } 258 | 259 | const char *str_id(const uint8_t id[]) 260 | { 261 | static char hexbuf[2 * SHA1_BIN_LENGTH + 1]; 262 | return bytes_to_base16(hexbuf, sizeof(hexbuf), id, SHA1_BIN_LENGTH); 263 | } 264 | 265 | const char *str_af(int af) { 266 | switch (af) { 267 | case AF_INET: 268 | return "IPv4"; 269 | case AF_INET6: 270 | return "IPv6"; 271 | case AF_UNSPEC: 272 | return "IPv4+IPv6"; 273 | default: 274 | return ""; 275 | } 276 | } 277 | 278 | const char *str_addr2(const void *ip, uint8_t length, uint16_t port) 279 | { 280 | static char addrbuf[FULL_ADDSTRLEN]; 281 | char buf[INET6_ADDRSTRLEN]; 282 | const char *fmt; 283 | 284 | switch (length) { 285 | case 16: 286 | inet_ntop(AF_INET6, ip, buf, sizeof(buf)); 287 | fmt = "[%s]:%d"; 288 | break; 289 | case 4: 290 | inet_ntop(AF_INET, ip, buf, sizeof(buf)); 291 | fmt = "%s:%d"; 292 | break; 293 | default: 294 | return ""; 295 | } 296 | 297 | sprintf(addrbuf, fmt, buf, port); 298 | 299 | return addrbuf; 300 | } 301 | 302 | const char *str_addr(const IP *addr) 303 | { 304 | switch (addr->ss_family) { 305 | case AF_INET6: { 306 | uint16_t port = ntohs(((IP6 *)addr)->sin6_port); 307 | return str_addr2(&((IP6 *)addr)->sin6_addr, 16, port); 308 | } 309 | case AF_INET: { 310 | uint16_t port = ntohs(((IP4 *)addr)->sin_port); 311 | return str_addr2(&((IP4 *)addr)->sin_addr, 4, port); 312 | } 313 | default: 314 | return ""; 315 | } 316 | } 317 | 318 | bool addr_is_localhost(const IP *addr) 319 | { 320 | // 127.0.0.1 321 | const uint32_t inaddr_loopback = htonl(INADDR_LOOPBACK); 322 | 323 | switch (addr->ss_family) { 324 | case AF_INET: 325 | return (memcmp(&((IP4 *)addr)->sin_addr, &inaddr_loopback, 4) == 0); 326 | case AF_INET6: 327 | return (memcmp(&((IP6 *)addr)->sin6_addr, &in6addr_loopback, 16) == 0); 328 | default: 329 | return false; 330 | } 331 | } 332 | 333 | bool addr_is_multicast(const IP *addr) 334 | { 335 | switch (addr->ss_family) { 336 | case AF_INET: 337 | return IN_MULTICAST(ntohl(((IP4*) addr)->sin_addr.s_addr)); 338 | case AF_INET6: 339 | return IN6_IS_ADDR_MULTICAST(&((IP6*) addr)->sin6_addr); 340 | default: 341 | return false; 342 | } 343 | } 344 | 345 | int addr_port(const IP *addr) 346 | { 347 | switch (addr->ss_family) { 348 | case AF_INET: 349 | return ntohs(((IP4 *)addr)->sin_port); 350 | case AF_INET6: 351 | return ntohs(((IP6 *)addr)->sin6_port); 352 | default: 353 | return 0; 354 | } 355 | } 356 | 357 | int addr_len(const IP *addr) 358 | { 359 | switch (addr->ss_family) { 360 | case AF_INET: 361 | return sizeof(IP4); 362 | case AF_INET6: 363 | return sizeof(IP6); 364 | default: 365 | return 0; 366 | } 367 | } 368 | 369 | const char *str_bytes(uint64_t bytes) 370 | { 371 | static char strbytesbuf[4][8]; 372 | static size_t strbytesbuf_i = 0; 373 | char *buf = strbytesbuf[++strbytesbuf_i % 4]; 374 | 375 | if (bytes < 1000) { 376 | snprintf(buf, 8, "%u B", (unsigned) bytes); 377 | } else if (bytes < 1000000) { 378 | snprintf(buf, 8, "%.1f K", bytes / 1000.0); 379 | } else if (bytes < 1000000000) { 380 | snprintf(buf, 8, "%.1f M", bytes / 1000000.0); 381 | } else if (bytes < 1000000000000) { 382 | snprintf(buf, 8, "%.1f G", bytes / 1000000000.0); 383 | } else if (bytes < 1000000000000000) { 384 | snprintf(buf, 8, "%.1f T", bytes / 1000000000000.0); 385 | } else if (bytes < 1000000000000000000) { 386 | snprintf(buf, 8, "%.1f P", bytes / 1000000000000000.0); 387 | } else { 388 | snprintf(buf, 8, "%.1f E", bytes / 1000000000000000000.0); 389 | } 390 | 391 | return buf; 392 | } 393 | 394 | const char *str_time(time_t time) 395 | { 396 | static char strdurationbuf[4][64]; 397 | static size_t strdurationbuf_i = 0; 398 | char *buf = strdurationbuf[++strdurationbuf_i % 4]; 399 | 400 | size_t years, days, hours, minutes, seconds; 401 | const char *prefix = ""; 402 | 403 | if (time < 0) { 404 | time = -time; 405 | // prepend minus sign 406 | prefix = "-"; 407 | } 408 | 409 | years = time / (365 * 24 * 60 * 60); 410 | time -= years * (365 * 24 * 60 * 60); 411 | days = time / (24 * 60 * 60); 412 | time -= days * (24 * 60 * 60); 413 | hours = time / (60 * 60); 414 | time -= hours * (60 * 60); 415 | minutes = time / 60; 416 | time -= minutes * 60; 417 | seconds = time; 418 | 419 | if (years > 0) { 420 | snprintf(buf, 64, "%s%zuy%zud", prefix, years, days); 421 | } else if (days > 0) { 422 | snprintf(buf, 64, "%s%zud%zuh", prefix, days, hours); 423 | } else if (hours > 0) { 424 | snprintf(buf, 64, "%s%zuh%zum", prefix, hours, minutes); 425 | } else if (minutes > 0) { 426 | snprintf(buf, 64, "%s%zum%zus", prefix, minutes, seconds); 427 | } else { 428 | snprintf(buf, 64, "%s%zus", prefix, seconds); 429 | } 430 | 431 | return buf; 432 | } 433 | 434 | static bool addr_parse_internal(IP *ret, const char addr_str[], const char port_str[], int af) 435 | { 436 | struct addrinfo hints; 437 | struct addrinfo *info = NULL; 438 | struct addrinfo *p = NULL; 439 | bool rc = false; 440 | 441 | memset(&hints, '\0', sizeof(struct addrinfo)); 442 | hints.ai_socktype = SOCK_STREAM; 443 | //hints.ai_flags = AI_NUMERICHOST; 444 | hints.ai_family = af; 445 | 446 | if (getaddrinfo(addr_str, port_str, &hints, &info) != 0) { 447 | return false; 448 | } 449 | 450 | p = info; 451 | while (p != NULL) { 452 | if ((af == AF_UNSPEC || af == AF_INET6) && p->ai_family == AF_INET6) { 453 | memcpy(ret, p->ai_addr, sizeof(IP6)); 454 | rc = true; 455 | break; 456 | } 457 | 458 | if ((af == AF_UNSPEC || af == AF_INET) && p->ai_family == AF_INET) { 459 | memcpy(ret, p->ai_addr, sizeof(IP4)); 460 | rc = true; 461 | break; 462 | } 463 | p = p->ai_next; 464 | } 465 | 466 | freeaddrinfo(info); 467 | 468 | return rc; 469 | } 470 | 471 | /* 472 | * Parse/Resolve various string representations of 473 | * IPv4/IPv6 addresses and optional port. 474 | * An address can also be a domain name. 475 | * A port can also be a service (e.g. 'www'). 476 | * 477 | * "
" 478 | * ":" 479 | * "[
]" 480 | * "[
]:" 481 | */ 482 | bool addr_parse(IP *addr_ret, const char full_addr_str[], const char default_port[], int af) 483 | { 484 | char addr_buf[256]; 485 | char *addr_beg; 486 | char *addr_tmp; 487 | char *last_colon; 488 | const char *addr_str = NULL; 489 | const char *port_str = NULL; 490 | size_t len; 491 | 492 | len = strlen(full_addr_str); 493 | if (len >= (sizeof(addr_buf) - 1)) { 494 | // address too long 495 | return false; 496 | } else { 497 | addr_beg = addr_buf; 498 | } 499 | 500 | memset(addr_buf, '\0', sizeof(addr_buf)); 501 | memcpy(addr_buf, full_addr_str, len); 502 | 503 | last_colon = strrchr(addr_buf, ':'); 504 | 505 | if (addr_beg[0] == '[') { 506 | // [] or []: 507 | addr_tmp = strrchr(addr_beg, ']'); 508 | 509 | if (addr_tmp == NULL) { 510 | // broken format 511 | return false; 512 | } 513 | 514 | *addr_tmp = '\0'; 515 | addr_str = addr_beg + 1; 516 | 517 | if (*(addr_tmp + 1) == '\0') { 518 | port_str = default_port; 519 | } else if (*(addr_tmp + 1) == ':') { 520 | port_str = addr_tmp + 2; 521 | } else { 522 | // port expected 523 | return false; 524 | } 525 | } else if (last_colon && last_colon == strchr(addr_buf, ':')) { 526 | // : 527 | addr_tmp = last_colon; 528 | *addr_tmp = '\0'; 529 | addr_str = addr_buf; 530 | port_str = addr_tmp + 1; 531 | } else { 532 | // 533 | addr_str = addr_buf; 534 | port_str = default_port; 535 | } 536 | 537 | return addr_parse_internal(addr_ret, addr_str, port_str, af); 538 | } 539 | 540 | // Compare two ip addresses, ignore port 541 | bool addr_equal(const IP *addr1, const IP *addr2) 542 | { 543 | if (addr1->ss_family != addr2->ss_family) { 544 | return 0; 545 | } else if (addr1->ss_family == AF_INET) { 546 | return 0 == memcmp(&((IP4 *)addr1)->sin_addr, &((IP4 *)addr2)->sin_addr, 4); 547 | } else if (addr1->ss_family == AF_INET6) { 548 | return 0 == memcmp(&((IP6 *)addr1)->sin6_addr, &((IP6 *)addr2)->sin6_addr, 16); 549 | } else { 550 | return false; 551 | } 552 | } 553 | 554 | bool socket_addr(int sock, IP *addr) 555 | { 556 | socklen_t len = sizeof(IP); 557 | return getsockname(sock, (struct sockaddr *) addr, &len) == 0; 558 | } 559 | 560 | time_t time_add_secs(uint32_t seconds) 561 | { 562 | return gconf->time_now + seconds; 563 | } 564 | 565 | time_t time_add_mins(uint32_t minutes) 566 | { 567 | return gconf->time_now + (60 * minutes); 568 | } 569 | 570 | time_t time_add_hours(uint32_t hours) 571 | { 572 | return gconf->time_now + (60 * 60 * hours); 573 | } 574 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _UTILS_H_ 3 | #define _UTILS_H_ 4 | 5 | #include 6 | #include 7 | 8 | 9 | // Number of elements in an array 10 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 11 | 12 | // Size of a struct element 13 | #define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f)) 14 | 15 | // Typical min/max methods 16 | #define MAX(x, y) ((x) >= (y) ? (x) : (y)) 17 | #define MIN(x, y) ((x) <= (y) ? (x) : (y)) 18 | 19 | // Make a symbol into a string literal 20 | #define STR_HELPER(x) #x 21 | #define STR(x) STR_HELPER(x) 22 | 23 | // IPv6 address length including port, e.g. [::1]:12345 24 | #define FULL_ADDSTRLEN (INET6_ADDRSTRLEN + 8) 25 | 26 | // Direct access to time in seconds 27 | #define time_now_sec() (gconf->time_now) 28 | 29 | typedef struct { 30 | const char *name; 31 | uint16_t num_args; 32 | uint16_t code; 33 | } option_t; 34 | 35 | const option_t *find_option(const option_t options[], const char name[]); 36 | int setargs(const char **argv, int argv_size, char *args); 37 | 38 | bool is_hex_id(const char query[]); 39 | bool parse_id(uint8_t id[], size_t idsize, const char query[], size_t querysize); 40 | 41 | // query is "[:]" 42 | bool is_announcement(const char query[]); 43 | bool parse_annoucement(uint8_t id[], int *port, const char query[], int default_port); 44 | 45 | bool bytes_from_base16(uint8_t dst[], size_t dstsize, const char src[], size_t srcsize); 46 | char *bytes_to_base16(char dst[], size_t dstsize, const uint8_t src[], size_t srcsize); 47 | 48 | int port_random(void); 49 | bool port_valid(int port); 50 | bool port_set(IP *addr, uint16_t port); 51 | 52 | int parse_int(const char *s, int err); 53 | 54 | int query_sanitize(char buf[], size_t buflen, const char query[]); 55 | int bytes_random(uint8_t buffer[], size_t size); 56 | bool id_equal(const uint8_t id1[], const uint8_t id2[]); 57 | 58 | const char *str_af(int af); 59 | const char *str_id(const uint8_t id[]); 60 | const char *str_addr(const IP *addr); 61 | const char *str_addr2(const void *ip, uint8_t length, uint16_t port); 62 | const char *str_bytes(uint64_t bytes); 63 | const char *str_time(time_t time); 64 | 65 | bool addr_is_localhost(const IP *addr); 66 | bool addr_is_multicast(const IP *addr); 67 | bool addr_parse(IP *addr, const char full_addr_str[], const char default_port[], int af); 68 | int addr_port(const IP *addr); 69 | int addr_len(const IP *addr); 70 | bool addr_equal(const IP *addr1, const IP *addr2); 71 | 72 | bool socket_addr(int sock, IP *addr); 73 | 74 | time_t time_add_secs(uint32_t seconds); 75 | time_t time_add_mins(uint32_t minutes); 76 | time_t time_add_hours(uint32_t hours); 77 | 78 | #endif // _UTILS_H_ 79 | -------------------------------------------------------------------------------- /src/windows.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "main.h" 9 | #include "conf.h" 10 | #include "log.h" 11 | 12 | 13 | static SERVICE_STATUS sStatus; 14 | static SERVICE_STATUS_HANDLE hServiceStatus = 0; 15 | 16 | static void (*svc_main_func)(); 17 | 18 | 19 | void windows_service_control(DWORD dwControl) 20 | { 21 | switch (dwControl) { 22 | case SERVICE_CONTROL_SHUTDOWN: 23 | case SERVICE_CONTROL_STOP: 24 | sStatus.dwCurrentState = SERVICE_STOP_PENDING; 25 | sStatus.dwCheckPoint = 0; 26 | sStatus.dwWaitHint = 3000; // Three seconds 27 | sStatus.dwWin32ExitCode = 0; 28 | gconf->is_running = false; 29 | default: 30 | sStatus.dwCheckPoint = 0; 31 | } 32 | SetServiceStatus(hServiceStatus, &sStatus); 33 | } 34 | 35 | void windows_service_main(int argc, char **argv) 36 | { 37 | hServiceStatus = RegisterServiceCtrlHandler(argv[0], (LPHANDLER_FUNCTION) windows_service_control); 38 | if (hServiceStatus == 0) { 39 | return; 40 | } 41 | 42 | sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 43 | sStatus.dwCurrentState = SERVICE_START_PENDING; 44 | sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; 45 | sStatus.dwWin32ExitCode = 0; 46 | sStatus.dwServiceSpecificExitCode = 0; 47 | sStatus.dwCheckPoint = 0; 48 | sStatus.dwWaitHint = 3000; // Allow us to wait three seconds 49 | sStatus.dwCurrentState = SERVICE_RUNNING; 50 | 51 | SetServiceStatus(hServiceStatus, &sStatus); 52 | 53 | // The main program 54 | svc_main_func(); 55 | 56 | // Cleanup 57 | sStatus.dwCurrentState = SERVICE_STOPPED; 58 | SetServiceStatus(hServiceStatus, &sStatus); 59 | } 60 | 61 | int windows_service_start(void (*func)()) 62 | { 63 | static SERVICE_TABLE_ENTRY services[] = { 64 | { (char*) PROGRAM_NAME, (LPSERVICE_MAIN_FUNCTIONA) windows_service_main }, 65 | { NULL, NULL } 66 | }; 67 | 68 | // Safe args for later call in windows_service_main() 69 | svc_main_func = func; 70 | 71 | if (!StartServiceCtrlDispatcher(services)) { 72 | log_warning("WIN: Can not start service: Error %d", GetLastError()); 73 | return 1; 74 | } else { 75 | return 0; 76 | } 77 | } 78 | 79 | /* 80 | * Similar to: 81 | * sc create dhtd type=own DisplayName=DHTd start=auto error=normal binPath=C:\...\dhtd.exe 82 | */ 83 | void windows_service_install(void) 84 | { 85 | char path[MAX_PATH]; 86 | 87 | GetModuleFileName(NULL, path, sizeof(path)); 88 | 89 | SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); 90 | SC_HANDLE hService = CreateService( 91 | hSCManager, 92 | PROGRAM_NAME, // Name of service 93 | PROGRAM_NAME, // Name to display 94 | SERVICE_ALL_ACCESS, // Desired access 95 | SERVICE_WIN32_OWN_PROCESS, // Service type 96 | SERVICE_AUTO_START, // Start type 97 | SERVICE_ERROR_NORMAL, // Error control type 98 | path, // Service binary 99 | NULL, // No load order group 100 | NULL, // No tag identifier 101 | "", // Dependencies 102 | 0, // LocalSystem account 103 | 0 // No password 104 | ); 105 | 106 | CloseServiceHandle(hService); 107 | CloseServiceHandle(hSCManager); 108 | 109 | log_info("WIN: Service installed."); 110 | } 111 | 112 | /* 113 | * Similar to: 114 | * sc delete DHTd 115 | */ 116 | void windows_service_remove(void) 117 | { 118 | SC_HANDLE hService = 0; 119 | SC_HANDLE hSCManager = OpenSCManager(0, 0, 0); 120 | hService = OpenService(hSCManager, PROGRAM_NAME, DELETE); 121 | DeleteService(hService); 122 | CloseServiceHandle(hService); 123 | CloseServiceHandle(hSCManager); 124 | log_info("WIN: Service removed."); 125 | } 126 | 127 | static BOOL WINAPI windows_console_handler(int event) 128 | { 129 | switch(event) { 130 | case CTRL_C_EVENT: 131 | case CTRL_BREAK_EVENT: 132 | gconf->is_running = false; 133 | log_info("Shutting down..."); 134 | return TRUE; 135 | default: 136 | return FALSE; 137 | } 138 | } 139 | 140 | // Install signal handlers to exit DHTd on CTRL+C 141 | void windows_signals(void) 142 | { 143 | if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE) windows_console_handler, TRUE)) { 144 | log_warning("WIN: Cannot set console handler. Error: %d", GetLastError()); 145 | } 146 | } 147 | 148 | int windows_exec(const char* cmd) 149 | { 150 | STARTUPINFO si; 151 | PROCESS_INFORMATION pi; 152 | 153 | ZeroMemory(&si, sizeof(si)); 154 | si.cb = sizeof(si); 155 | ZeroMemory(&pi, sizeof(pi)); 156 | 157 | // Start the child process 158 | if (!CreateProcess(NULL, // No module name (use command line) 159 | (char*) cmd, // Command line 160 | NULL, // Process handle not inheritable 161 | NULL, // Thread handle not inheritable 162 | FALSE, // Set handle inheritance to FALSE 163 | 0, // No creation flags 164 | NULL, // Use parent's environment block 165 | NULL, // Use parent's starting directory 166 | &si, // Pointer to STARTUPINFO structure 167 | &pi) // Pointer to PROCESS_INFORMATION structure 168 | ) { 169 | log_warning("CreateProcess failed: Error %d", GetLastError()); 170 | return 1; 171 | } 172 | 173 | // Wait until child process exits. 174 | WaitForSingleObject(pi.hProcess, INFINITE); 175 | 176 | // Close process and thread handles. 177 | CloseHandle(pi.hProcess); 178 | CloseHandle(pi.hThread); 179 | 180 | return 0; 181 | } 182 | -------------------------------------------------------------------------------- /src/windows.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _WINDOWS_H_ 3 | #define _WINDOWS_H_ 4 | 5 | // Setup a Windows Service 6 | void windows_service_install(void); 7 | void windows_service_remove(void); 8 | int windows_service_start(void (*func)()); 9 | 10 | void windows_signals(void); 11 | int windows_exec(const char* cmd); 12 | 13 | #endif // _WINDOWS_H_ 14 | --------------------------------------------------------------------------------