diff options
| author | PancakeTAS <pancake@mgnet.work> | 2026-06-13 14:47:53 +0200 |
|---|---|---|
| committer | PancakeTAS <pancake@mgnet.work> | 2026-06-14 16:26:34 +0200 |
| commit | 5781d6a312db5389a089abac127339a9ff5de696 (patch) | |
| tree | 327366db23026636ba9b81700fb6e90eab432228 /src | |
| parent | (initial commit) (diff) | |
feat: Implement peer-to-peer(ish) socket handshake
This isn't entirely "peer-to-peer", because one side requires a publicly accessible port.
The primary usecase for this is to establish connections to an endpoint, when there are several non-deterministic paths available on the client side.
Diffstat (limited to 'src')
| -rw-r--r-- | src/p2p.c | 132 | ||||
| -rw-r--r-- | src/p2p.h | 22 |
2 files changed, 154 insertions, 0 deletions
diff --git a/src/p2p.c b/src/p2p.c new file mode 100644 index 0000000..dc726b3 --- /dev/null +++ b/src/p2p.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "p2p.h" + +#include <errno.h> +#include <netinet/in.h> +#include <stdint.h> +#include <sys/poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +// Create a UDP socket with DF set. +static int create_socket() { + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + return -1; + } + + int value = IP_PMTUDISC_DO; + if (setsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value)) < 0) { + close(sockfd); + return -1; + } + + return sockfd; +} + +int p2p_bind(uint16_t port, in_addr_t ip) { + int sockfd = create_socket(); + if (sockfd < 0) { + goto err; + } + + struct sockaddr_in local_addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = { .s_addr = INADDR_ANY }, + }; + if (bind(sockfd, (struct sockaddr*) &local_addr, sizeof(local_addr)) < 0) { + goto err_close; + } + + while (true) { + // Wait for handshake from peer + struct sockaddr_in remote_addr; + socklen_t remote_addr_len = sizeof(remote_addr); + + uint8_t handshake[1]; + ssize_t handshake_len = recvfrom(sockfd, handshake, sizeof(handshake), 0, + (struct sockaddr*) &remote_addr, &remote_addr_len); + if (handshake_len < 0) { + goto err_close; + } + + if (handshake_len != 1 || *handshake != '?') { + // Malformed handshake, ignore + continue; + } + + if (remote_addr.sin_addr.s_addr != ip) { + // Wrong peer, reject handshake + sendto(sockfd, "N", 1, 0, (struct sockaddr*) &remote_addr, remote_addr_len); + continue; + } + + // Accept handshake and lock into peer + if (connect(sockfd, (struct sockaddr*) &remote_addr, remote_addr_len) < 0) { + goto err_close; + } + + if (send(sockfd, "Y", 1, 0) < 0) { + goto err_close; + } + + break; + } + + return sockfd; +err_close: + close(sockfd); +err: + return -1; +} + +int p2p_connect(uint16_t port, in_addr_t ip, int timeout) { + int sockfd = create_socket(); + if (sockfd < 0) { + goto err; + } + + struct sockaddr_in remote_addr = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = { .s_addr = ip }, + }; + if (connect(sockfd, (struct sockaddr*) &remote_addr, sizeof(remote_addr)) < 0) { + goto err_close; + } + + // Perform handshake + if (send(sockfd, "?", 1, 0) < 0) { + goto err_close; + } + + struct pollfd pfd = { + .fd = sockfd, + .events = POLLIN, + }; + int ret = poll(&pfd, 1, timeout); + if (ret < 0) { + goto err_close; + } else if (ret == 0) { + errno = ETIMEDOUT; + goto err_close; + } + + uint8_t handshake[1]; + ssize_t handshake_len = recv(sockfd, handshake, sizeof(handshake), 0); + if (handshake_len < 0) { + goto err_close; + } else if (handshake_len != 1 || *handshake != 'Y') { + errno = ECONNREFUSED; + goto err_close; + } + + return sockfd; +err_close: + close(sockfd); +err: + return -1; +} diff --git a/src/p2p.h b/src/p2p.h new file mode 100644 index 0000000..20fee9b --- /dev/null +++ b/src/p2p.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include <netinet/in.h> +#include <stdint.h> + +// +// This module implements a peer-to-peer(ish) UDP connection where one side binds +// a port and waits for the other side to connect from a specific IP address. +// +// Both sockets have IP_PMTUDISC_DO set, meaning packets larger than the +// path MTU will be dropped or fail with EMSGSIZE. +// + +/// Bind to a UDP port and wait for handshake from the specified ip. +/// Returns sockfd on success, -1 on failure (errno is set). +int p2p_bind(uint16_t port, in_addr_t ip); + +/// Connect to a peer-to-peer UDP socket. Timeout is in milliseconds. +/// Returns sockfd on success, -1 on failure (errno is set). +int p2p_connect(uint16_t port, in_addr_t ip, int timeout); |
