#include #include #include #include #include #include #include #include #include #include #include "curve25519.h" #include "siphash.h" #include "sha512.h" #include "dht.h" #include "udht.h" #include "pex-msg.h" static struct uloop_timeout periodic_timer, peer_timer, status_timer, disconnect_timer; static struct uloop_fd dht_fd; static int dht_unix_fd; static LIST_HEAD(bootstrap_peers); static LIST_HEAD(networks); static struct blob_buf b; static uint8_t local_id[20]; static const char *node_file; static const char *unix_path; static bool udht_connected; static struct { unsigned int tick; unsigned int peer_count; bool bootstrap_added; bool dht_ready; } state; struct network_entry { struct list_head list; uint8_t auth_key[CURVE25519_KEY_SIZE]; uint8_t id[20]; struct uloop_timeout search_timer; int search_count; int seq; }; struct peer_entry { struct list_head list; struct sockaddr_storage sa; int sa_len; }; void dht_hash(void *hash_return, int hash_size, const void *v1, int len1, const void *v2, int len2, const void *v3, int len3) { siphash_key_t key = {}; if (hash_size != 8) abort(); key.key[0] = siphash(v1, len1, &key); key.key[1] = siphash(v2, len2, &key); siphash_to_le64(hash_return, v3, len3, &key); } int dht_sendto(int sockfd, const void *buf, int len, int flags, const struct sockaddr *to, int tolen) { struct iovec iov[2] = { { .iov_base = (void *)to }, { .iov_base = (void *)buf, .iov_len = len }, }; struct msghdr msg = { .msg_iov = iov, .msg_iovlen = ARRAY_SIZE(iov), }; int ret; if (to->sa_family == AF_INET) iov[0].iov_len = sizeof(struct sockaddr_in); else if (to->sa_family == AF_INET6) iov[0].iov_len = sizeof(struct sockaddr_in6); else return -1; ret = sendmsg(sockfd, &msg, flags); if (ret < 0) { perror("send"); if (errno == ECONNRESET || errno == EDESTADDRREQ || errno == ENOTCONN || errno == ECONNREFUSED) uloop_timeout_set(&disconnect_timer, 1); } return ret; } int dht_blacklisted(const struct sockaddr *sa, int salen) { return 0; } int dht_random_bytes(void *buf, size_t size) { int fd, rc, save; fd = open("/dev/urandom", O_RDONLY); if(fd < 0) return -1; rc = read(fd, buf, size); save = errno; close(fd); errno = save; return rc; } static void udht_start_search(void) { struct network_entry *n; if (!state.dht_ready) return; list_for_each_entry(n, &networks, list) { if (n->search_timer.pending) continue; uloop_timeout_set(&n->search_timer, 1); } } static void udht_send_v4_node(const void *id, const void *data) { struct network_entry *n; struct { struct sockaddr sa; struct pex_msg_local_control local; } msg = { .sa = { .sa_family = AF_LOCAL }, .local = { .ep.in = { .sin_family = AF_INET, .sin_addr = *(const struct in_addr *)data, .sin_port = *(const uint16_t *)(data + 4), }, .timeout = 15 * 60, } }; list_for_each_entry(n, &networks, list) { if (memcmp(n->id, id, sizeof(n->id)) != 0) continue; memcpy(&msg.local.auth_id, n->auth_key, sizeof(msg.local.auth_id)); goto found; } found: send(dht_unix_fd, &msg, sizeof(msg), 0); } static void udht_cb(void *closure, int event, const unsigned char *info_hash, const void *data, size_t data_len) { char addrbuf[INET6_ADDRSTRLEN]; int i; if (!udht_connected) return; if (event == DHT_EVENT_SEARCH_DONE) { printf("Search done.\n"); udht_start_search(); } else if (event == DHT_EVENT_SEARCH_DONE6) { printf("IPv6 search done.\n"); } else if (event == DHT_EVENT_VALUES) { printf("Received %d values.\n", (int)(data_len / 6)); for (i = 0; i < data_len / 6; i++) { fprintf(stderr, "Node: %s:%d\n", inet_ntop(AF_INET, data, addrbuf, sizeof(addrbuf)), ntohs(*(uint16_t *)(data + 4))); udht_send_v4_node(info_hash, data); data += 6; } } else if (event == DHT_EVENT_VALUES6) printf("Received %d IPv6 values.\n", (int)(data_len / 18)); else printf("Unknown DHT event %d.\n", event); } static void udht_search_timer_cb(struct uloop_timeout *t) { struct network_entry *n = container_of(t, struct network_entry, search_timer); char id_str[42]; int i; for (i = 0; i < sizeof(n->id); i++) snprintf(&id_str[i * 2], sizeof(id_str) - i * 2, "%02x", n->id[i]); fprintf(stderr, "Start search for network, id=%s\n", id_str); dht_search(n->id, UNETD_GLOBAL_PEX_PORT, AF_INET, udht_cb, NULL); if (++n->search_count > 2) uloop_timeout_set(&n->search_timer, 30 * 1000); } static void udht_timer_cb(struct uloop_timeout *t) { time_t tosleep = 1; dht_periodic(NULL, 0, NULL, 0, &tosleep, udht_cb, NULL); if (!tosleep) tosleep = 1; uloop_timeout_set(t, tosleep * 1000); } static void udht_fd_cb(struct uloop_fd *fd, unsigned int events) { static char buf[4096]; struct sockaddr *sa = (struct sockaddr *)buf; time_t tosleep = 1; int len; while (1) { socklen_t fromlen; len = recv(fd->fd, buf, sizeof(buf) - 1, 0); if (len < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) break; perror("recvfrom"); uloop_timeout_set(&disconnect_timer, 1); return; } if (len <= sizeof(struct sockaddr)) continue; if (sa->sa_family == AF_INET) fromlen = sizeof(struct sockaddr_in); else if (sa->sa_family == AF_INET6) fromlen = sizeof(struct sockaddr_in6); else continue; if (len <= fromlen) continue; buf[len] = 0; dht_periodic(buf + fromlen, len - fromlen, sa, fromlen, &tosleep, udht_cb, NULL); if (!tosleep) tosleep = 1; uloop_timeout_set(&periodic_timer, tosleep * 1000); } } static int udht_open_socket(const char *unix_path) { uint8_t fd_buf[CMSG_SPACE(sizeof(int))] = { 0 }; static struct sockaddr sa = { .sa_family = AF_LOCAL, }; static struct iovec iov = { .iov_base = &sa, .iov_len = sizeof(sa), }; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = fd_buf, .msg_controllen = CMSG_LEN(sizeof(int)), }; struct cmsghdr *cmsg; int sfd[2]; int fd; fd = usock(USOCK_UNIX | USOCK_UDP, unix_path, NULL); if (fd < 0) return -1; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sfd) < 0) close(fd); dht_unix_fd = fd; dht_fd.fd = sfd[1]; dht_fd.cb = udht_fd_cb; uloop_fd_add(&dht_fd, ULOOP_READ); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cmsg) = sfd[0]; sendmsg(dht_unix_fd, &msg, 0); close(sfd[0]); return 0; } static void udht_close_socket(void) { uloop_fd_delete(&dht_fd); close(dht_fd.fd); close(dht_unix_fd); } static void udht_id_hash(uint8_t *dest, const void *data, int len) { struct sha512_state s; uint8_t hash[SHA512_HASH_SIZE]; sha512_init(&s); sha512_add(&s, data, len); sha512_final(&s, hash); memcpy(dest, hash, 20); } static void udht_add_peer(const void *data, int len) { const struct sockaddr *sa = data; struct peer_entry *p; p = calloc(1, sizeof(*p)); memcpy(&p->sa, sa, len); p->sa_len = len; list_add_tail(&p->list, &bootstrap_peers); if (!peer_timer.pending) uloop_timeout_set(&peer_timer, 1); } static void udht_add_bootstrap_peer(void) { const struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_DGRAM, .ai_flags = AI_ADDRCONFIG, }; struct addrinfo *res, *cur; static const char * const bootstrap_hosts[] = { "router.bittorrent.com", "router.utorrent.com", }; int i; for (i = 0; i < ARRAY_SIZE(bootstrap_hosts); i++) { if (getaddrinfo(bootstrap_hosts[i], "6881", &hints, &res) < 0) continue; for (cur = res; cur; cur = cur->ai_next) udht_add_peer(cur->ai_addr, cur->ai_addrlen); freeaddrinfo(res); } state.bootstrap_added = true; } static void udht_peer_timer_cb(struct uloop_timeout *t) { struct peer_entry *p; struct sockaddr_in *sin; char buf[INET6_ADDRSTRLEN]; if (list_empty(&bootstrap_peers)) { if (!state.peer_count && !state.bootstrap_added) udht_add_bootstrap_peer(); return; } p = list_first_entry(&bootstrap_peers, struct peer_entry, list); list_del(&p->list); sin = (struct sockaddr_in *)&p->sa; fprintf(stderr, "Ping node %s\n", inet_ntop(sin->sin_family, &sin->sin_addr, buf, sizeof(buf))); dht_ping_node((struct sockaddr *)&p->sa, p->sa_len); free(p); if (state.peer_count++ < 8) uloop_timeout_set(t, 2000); else uloop_timeout_set(t, 15000); } void udht_network_add(const uint8_t *auth_key, int seq) { struct network_entry *n; list_for_each_entry(n, &networks, list) { if (memcmp(n->auth_key, auth_key, sizeof(n->auth_key)) != 0) continue; goto out; } n = calloc(1, sizeof(*n)); n->search_timer.cb = udht_search_timer_cb; memcpy(n->auth_key, auth_key, sizeof(n->auth_key)); udht_id_hash(n->id, n->auth_key, sizeof(n->auth_key)); list_add_tail(&n->list, &networks); if (state.dht_ready) uloop_timeout_set(&n->search_timer, 1); out: n->seq = seq; } void udht_network_flush(int seq) { struct network_entry *n, *tmp; list_for_each_entry_safe(n, tmp, &networks, list) { if (seq >= 0 && (n->seq < 0 || n->seq == seq)) continue; list_del(&n->list); uloop_timeout_cancel(&n->search_timer); free(n); } } static void udht_status_check(struct uloop_timeout *t) { int good = 0, dubious = 0, incoming = 0; static int prev_good, prev_dubious, prev_incoming; state.tick++; uloop_timeout_set(t, 1000); dht_nodes(AF_INET, &good, &dubious, NULL, &incoming); if (good != prev_good || dubious != prev_dubious || incoming != prev_incoming) fprintf(stderr, "DHT status: good=%d, dubious=%d, incoming=%d\n", good, dubious, incoming); prev_good = good; prev_dubious = dubious; prev_incoming = incoming; if (state.dht_ready) return; if (good < 4 || good + dubious < 8) { if (state.tick > 45 && !state.bootstrap_added) udht_add_bootstrap_peer(); return; } state.dht_ready = true; fprintf(stderr, "DHT is ready\n"); udht_start_search(); } static void udht_load_nodes(const char *filename) { struct blob_attr *data, *cur; size_t len; FILE *f; int rem; f = fopen(filename, "r"); if (!f) return; data = malloc(sizeof(struct blob_attr)); if (fread(data, sizeof(struct blob_attr), 1, f) != 1) goto out; len = blob_pad_len(data); if (len <= sizeof(struct blob_attr)) goto out; if (len >= 256 * 1024) goto out; data = realloc(data, len); if (fread(data + 1, len - sizeof(struct blob_attr), 1, f) != 1) goto out; blob_for_each_attr(cur, data, rem) { void *entry = blob_data(cur); if (blob_len(cur) == 6) { struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = *(struct in_addr *)entry, .sin_port = *(uint16_t *)(entry + 4), }; udht_add_peer(&sin, sizeof(sin)); } else { continue; } } out: free(data); } static void udht_save_nodes(const char *filename) { struct sockaddr_in sin[128]; struct sockaddr_in6 sin6[128]; int n_sin = ARRAY_SIZE(sin); int n_sin6 = ARRAY_SIZE(sin6); FILE *f; int i; if (!filename) return; if (dht_get_nodes(sin, &n_sin, sin6, &n_sin6) <= 0) return; if (n_sin < 8) return; blob_buf_init(&b, 0); for (i = 0; i < n_sin; i++) { struct { struct in_addr addr; uint16_t port; } __attribute__((packed)) data = { .addr = sin[i].sin_addr, .port = sin[i].sin_port, }; blob_put(&b, 4, &data, sizeof(data)); } f = fopen(filename, "w"); if (!f) return; fwrite(b.head, blob_pad_len(b.head), 1, f); fclose(f); } static int usage(const char *progname) { fprintf(stderr, "Usage: %s [] \n" "Options:\n" " -d Enable debug mode\n" " -n Set node filename\n" " -N Add network key\n" "\n", progname); return 1; } static void udht_disconnect(struct uloop_timeout *t) { struct peer_entry *p, *tmp; if (!udht_connected) return; list_for_each_entry_safe(p, tmp, &bootstrap_peers, list) { list_del(&p->list); free(p); } uloop_timeout_cancel(&disconnect_timer); udht_connected = false; udht_network_flush(-1); uloop_timeout_cancel(&peer_timer); uloop_timeout_cancel(&status_timer); uloop_timeout_cancel(&periodic_timer); udht_save_nodes(node_file); dht_uninit(); memset(&state, 0, sizeof(state)); udht_close_socket(); #ifndef UBUS_SUPPORT uloop_end(); #endif } int udht_reconnect(void) { udht_disconnect(&disconnect_timer); if (udht_open_socket(unix_path) < 0) return -1; if (dht_init(dht_unix_fd, -1, local_id, NULL) < 0) { udht_close_socket(); return -1; } udht_connected = true; fprintf(stderr, "DHT connected\n"); udht_load_nodes(node_file); uloop_timeout_set(&peer_timer, 1); uloop_timeout_set(&status_timer, 1000); return 0; } int main(int argc, char **argv) { const char *progname = argv[0]; uint8_t auth_key[CURVE25519_KEY_SIZE]; int ch; while ((ch = getopt(argc, argv, "dN:n:u:")) != -1) { switch (ch) { case 'N': if (b64_decode(optarg, auth_key, CURVE25519_KEY_SIZE) != CURVE25519_KEY_SIZE) { fprintf(stderr, "Invalid network key\n"); return 1; } udht_network_add(auth_key, -1); break; case 'n': node_file = optarg; break; case 'd': dht_debug = stderr; break; case 'u': unix_path = optarg; break; default: return usage(progname); } } argv += optind; argc -= optind; if (argc != 1) return usage(progname); udht_id_hash(local_id, argv[0], strlen(argv[0])); status_timer.cb = udht_status_check; periodic_timer.cb = udht_timer_cb; peer_timer.cb = udht_peer_timer_cb; disconnect_timer.cb = udht_disconnect; uloop_init(); #ifdef UBUS_SUPPORT udht_ubus_init(); #else if (udht_reconnect() < 0) { fprintf(stderr, "Failed to connect to unetd\n"); return 1; } #endif uloop_run(); uloop_done(); udht_disconnect(&disconnect_timer); blob_buf_free(&b); return 0; }