aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common.c171
-rw-r--r--common.h33
-rw-r--r--dbg.h1
-rwxr-xr-xtests/netsh.sh12
-rw-r--r--wg-dynamic-client.c556
-rw-r--r--wg-dynamic-server.c349
6 files changed, 1002 insertions, 120 deletions
diff --git a/common.c b/common.c
index 10883fe..9da90fd 100644
--- a/common.c
+++ b/common.c
@@ -9,10 +9,14 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include <stdarg.h>
#include <string.h>
#include <unistd.h>
+#include <sys/param.h>
#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <linux/rtnetlink.h>
#include "common.h"
#include "dbg.h"
@@ -47,6 +51,9 @@ static struct wg_dynamic_attr *parse_value(enum wg_dynamic_key key, char *value)
uintmax_t uresult;
union {
uint32_t leasetime;
+ uint32_t leasestart;
+ uint32_t err;
+ char errmsg[72];
struct wg_combined_ip ip;
} data = { 0 };
@@ -65,6 +72,14 @@ static struct wg_dynamic_attr *parse_value(enum wg_dynamic_key key, char *value)
return NULL;
break;
+ case WGKEY_LEASESTART:
+ len = sizeof data.leasestart;
+ uresult = strtoumax(value, &endptr, 10);
+ if (uresult > UINT32_MAX || *endptr != '\0')
+ return NULL;
+
+ data.leasestart = (uint32_t)uresult;
+ break;
case WGKEY_LEASETIME:
len = sizeof data.leasetime;
uresult = strtoumax(value, &endptr, 10);
@@ -73,6 +88,19 @@ static struct wg_dynamic_attr *parse_value(enum wg_dynamic_key key, char *value)
data.leasetime = (uint32_t)uresult;
break;
+ case WGKEY_ERRNO:
+ len = sizeof data.err;
+ uresult = strtoumax(value, &endptr, 10);
+ if (uresult > UINT32_MAX || *endptr != '\0')
+ return NULL;
+
+ data.err = (uint32_t)uresult;
+ break;
+ case WGKEY_ERRMSG:
+ strncpy(data.errmsg, value, sizeof data.errmsg);
+ len = MIN(sizeof data.errmsg,
+ strlen(value) + 1); /* Copying the NUL byte too. */
+ break;
default:
debug("Invalid key %d, aborting\n", key);
abort();
@@ -192,6 +220,9 @@ void free_wg_dynamic_request(struct wg_dynamic_request *req)
req->cmd = WGKEY_UNKNOWN;
req->version = 0;
+ free(req->buf);
+ req->buf = NULL;
+ req->buflen = 0;
req->first = NULL;
req->last = NULL;
}
@@ -249,8 +280,8 @@ static int parse_request(struct wg_dynamic_request *req, unsigned char *buf,
}
bool handle_request(int fd, struct wg_dynamic_request *req,
- void (*success)(int, struct wg_dynamic_request *req),
- void (*error)(int, int))
+ bool (*success)(int, struct wg_dynamic_request *),
+ bool (*error)(int, int))
{
ssize_t bytes;
int ret;
@@ -264,23 +295,141 @@ bool handle_request(int fd, struct wg_dynamic_request *req,
// TODO: handle EINTR
- debug("Reading from socket failed: %s\n",
+ debug("Reading from socket %d failed: %s\n", fd,
strerror(errno));
return true;
} else if (bytes == 0) {
- debug("Client disconnected unexpectedly\n");
+ debug("Peer disconnected unexpectedly\n");
return true;
}
ret = parse_request(req, buf, bytes);
- if (ret < 0) {
- error(fd, -ret);
- return true;
- } else if (ret == 0) {
- success(fd, req);
- return true;
- }
+ if (ret < 0)
+ return error(fd, -ret);
+ else if (ret == 0)
+ return success(fd, req);
}
return false;
}
+
+size_t send_message(int fd, unsigned char *buf, size_t *len)
+{
+ ssize_t bytes;
+ size_t offset = 0;
+
+ while (*len) {
+ bytes = write(fd, buf + offset, *len);
+ if (bytes < 0) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ break;
+
+ // TODO: handle EINTR
+
+ debug("Writing to socket %d failed: %s\n", fd,
+ strerror(errno));
+ *len = 0;
+ return 0;
+ }
+
+ *len -= bytes;
+ offset += bytes;
+ }
+
+ return offset;
+}
+
+void send_later(struct wg_dynamic_request *req, unsigned char *const buf,
+ size_t msglen)
+{
+ unsigned char *newbuf = malloc(msglen);
+ if (!newbuf)
+ fatal("Failed malloc()");
+ memcpy(newbuf, buf, msglen);
+
+ free(req->buf);
+ req->buf = newbuf;
+ req->buflen = msglen;
+}
+
+size_t print_to_buf(char *buf, size_t bufsize, size_t offset, char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ int n = vsnprintf(buf + offset, bufsize - offset, fmt, ap);
+ va_end(ap);
+ if (n < 0)
+ fatal("Failed snprintf");
+ if (n + offset >= bufsize)
+ fatal("Outbuffer too small");
+ return n;
+}
+
+uint32_t current_time()
+{
+ struct timespec tp;
+ if (clock_gettime(CLOCK_REALTIME, &tp))
+ fatal("clock_gettime(CLOCK_REALTIME)");
+ return tp.tv_sec;
+}
+
+void close_connection(int *fd, struct wg_dynamic_request *req)
+{
+ if (close(*fd))
+ debug("Failed to close socket\n");
+
+ *fd = -1;
+ free_wg_dynamic_request(req);
+}
+
+bool is_link_local(unsigned char *addr)
+{
+ /* TODO: check if the remaining 48 bits are 0 */
+ return IN6_IS_ADDR_LINKLOCAL(addr);
+}
+
+void iface_get_all_addrs(uint8_t family, mnl_cb_t data_cb, void *cb_data)
+{
+ struct mnl_socket *nl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ /* TODO: rtln-addr-dump from libmnl uses rtgenmsg here? */
+ struct ifaddrmsg *ifaddr;
+ int ret;
+ unsigned int seq, portid;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (nl == NULL)
+ fatal("mnl_socket_open");
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+ fatal("mnl_socket_bind");
+
+ /* You'd think that we could just request addresses from a specific
+ * interface, via NLM_F_MATCH or something, but we can't. See also:
+ * https://marc.info/?l=linux-netdev&m=132508164508217
+ */
+ seq = time(NULL);
+ portid = mnl_socket_get_portid(nl);
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = RTM_GETADDR;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlh->nlmsg_seq = seq;
+ ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
+ ifaddr->ifa_family = family;
+
+ if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+ fatal("mnl_socket_sendto");
+
+ do {
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret <= MNL_CB_STOP)
+ break;
+ ret = mnl_cb_run(buf, ret, seq, portid, data_cb, cb_data);
+ } while (ret > 0);
+
+ if (ret == -1)
+ fatal("mnl_cb_run/mnl_socket_recvfrom");
+
+ mnl_socket_close(nl);
+}
diff --git a/common.h b/common.h
index ca76dc4..c73350e 100644
--- a/common.h
+++ b/common.h
@@ -19,9 +19,14 @@
#define RECV_BUFSIZE 8192
+#define MAX_RESPONSE_SIZE 8192
+
static const char WG_DYNAMIC_ADDR[] = "fe80::";
static const uint16_t WG_DYNAMIC_PORT = 1337;
+#define WG_DYNAMIC_LEASETIME 10 /* NOTE: 10s is good for testing purposes */
+
+/* TODO: Move client specific items to its own CLIENT_ITEMS */
#define ITEMS \
E(WGKEY_UNKNOWN, "") /* must be the first entry */ \
/* CMD START */ \
@@ -31,7 +36,10 @@ static const uint16_t WG_DYNAMIC_PORT = 1337;
E(WGKEY_INCOMPLETE, "") \
E(WGKEY_IPV4, "ipv4") \
E(WGKEY_IPV6, "ipv6") \
- E(WGKEY_LEASETIME, "leasetime")
+ E(WGKEY_LEASESTART, "leasestart") \
+ E(WGKEY_LEASETIME, "leasetime") \
+ E(WGKEY_ERRNO, "errno") \
+ E(WGKEY_ERRMSG, "errmsg")
#define E(x, y) x,
enum wg_dynamic_key { ITEMS };
@@ -52,6 +60,8 @@ struct wg_dynamic_request {
enum wg_dynamic_key cmd;
uint32_t version;
wg_key pubkey;
+ unsigned char *buf;
+ size_t buflen;
struct wg_dynamic_attr *first, *last;
};
@@ -64,11 +74,26 @@ struct wg_combined_ip {
uint8_t cidr;
};
+struct wg_dynamic_lease {
+ struct wg_combined_ip ip4;
+ struct wg_combined_ip ip6;
+ uint32_t start;
+ uint32_t leasetime;
+ struct wg_dynamic_lease *next;
+};
+
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
void free_wg_dynamic_request(struct wg_dynamic_request *req);
bool handle_request(int fd, struct wg_dynamic_request *req,
- void (*success)(int, struct wg_dynamic_request *req),
- void (*error)(int, int));
-
+ bool (*success)(int, struct wg_dynamic_request *),
+ bool (*error)(int, int));
+size_t send_message(int fd, unsigned char *buf, size_t *len);
+void send_later(struct wg_dynamic_request *req, unsigned char *const buf,
+ size_t msglen);
+size_t print_to_buf(char *buf, size_t bufsize, size_t len, char *fmt, ...);
+uint32_t current_time();
+void close_connection(int *fd, struct wg_dynamic_request *req);
+bool is_link_local(unsigned char *addr);
+void iface_get_all_addrs(uint8_t family, mnl_cb_t data_cb, void *cb_data);
#endif
diff --git a/dbg.h b/dbg.h
index 53d0db1..fae583a 100644
--- a/dbg.h
+++ b/dbg.h
@@ -29,6 +29,7 @@ extern int DBG_LVL;
#define SUFFIX(S, M, ...) M S, __VA_ARGS__
#define log_err(...) fprintf(stderr, PREFIX(__VA_ARGS__))
+/* FIXME: This looks backwards -- DBG_LVL=2 gives info but not warn. */
#define log_warn(...) do { if (DBG_LVL > 2) log_err(__VA_ARGS__); } while (0)
#define log_info(...) do { if (DBG_LVL > 1) log_err(__VA_ARGS__); } while (0)
#define die(...) \
diff --git a/tests/netsh.sh b/tests/netsh.sh
index 27979c5..df97a24 100755
--- a/tests/netsh.sh
+++ b/tests/netsh.sh
@@ -55,7 +55,7 @@ client_public=$(wg pubkey <<< $client_private)
configure_peers() {
ip1 addr add fe80::/64 dev wg0
- ip2 addr add fe80::badc:0ffe:e0dd:f00d/64 dev wg0
+ ip2 addr add fe80::badc:0ffe:e0dd:f00d/128 dev wg0
n1 wg set wg0 \
private-key <(echo $server_private) \
@@ -69,6 +69,8 @@ configure_peers() {
peer $server_public \
allowed-ips 0.0.0.0/0,::/0
+ ip2 route add fe80::/128 dev wg0
+
ip1 link set up dev wg0
ip2 link set up dev wg0
}
@@ -79,4 +81,10 @@ n2 wg set wg0 peer "$server_public" endpoint [::1]:1
n2 ping6 -c 10 -f -W 1 fe80::%wg0
n1 ping6 -c 10 -f -W 1 fe80::badc:0ffe:e0dd:f00d%wg0
-n1 ./wg-dynamic-server wg0
+pp echo "PID: $$"
+
+if [ -z "$NETSH_GDB" ]; then
+ n1 ./wg-dynamic-server wg0
+else # Try NETSH_GDB="-ex run" tests./netsh.sh
+ n1 gdb $NETSH_GDB --args ./wg-dynamic-server wg0
+fi
diff --git a/wg-dynamic-client.c b/wg-dynamic-client.c
index 8554269..560c8d7 100644
--- a/wg-dynamic-client.c
+++ b/wg-dynamic-client.c
@@ -3,38 +3,554 @@
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
*/
-#include "netlink.h"
#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
+#include <signal.h>
+#include <poll.h>
+
+#include <arpa/inet.h>
+#include <libmnl/libmnl.h>
+#include <linux/rtnetlink.h>
+
+#include "common.h"
+#include "dbg.h"
+#include "netlink.h"
+
+#define LEASE_CHECK_INTERVAL 1000 /* 1s is convenient for testing */
+#define LOW_PORT_START 214
+
+int DBG_LVL = 3;
+
+static const char *progname;
+static const char *wg_interface;
+static wg_device *device = NULL;
+static struct pollfd pollfds[1];
+static struct in6_addr our_lladdr = { 0 };
+static struct wg_combined_ip our_gaddr4 = { 0 };
+static struct wg_combined_ip our_gaddr6 = { 0 };
+static struct wg_dynamic_lease our_lease = { 0 };
+
+struct mnl_cb_data {
+ uint32_t ifindex;
+ struct in6_addr *lladdr;
+ struct wg_combined_ip *gaddr4;
+ struct wg_combined_ip *gaddr6;
+};
+
+static void usage()
+{
+ die("usage: %s <wg-interface>\n", progname);
+}
+
+int data_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ /* skip unsupported attribute in user-space */
+ if (mnl_attr_type_valid(attr, IFA_MAX) < 0)
+ return MNL_CB_OK;
+
+ switch (type) {
+ case IFA_ADDRESS:
+ if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+ break;
+ }
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+int data_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[IFA_MAX + 1] = {};
+ struct ifaddrmsg *ifa = mnl_nlmsg_get_payload(nlh);
+ struct mnl_cb_data *cb_data = (struct mnl_cb_data *)data;
+ unsigned char *addr;
+
+ if (ifa->ifa_index != cb_data->ifindex)
+ return MNL_CB_OK;
+
+ mnl_attr_parse(nlh, sizeof(*ifa), data_attr_cb, tb);
+
+ if (!tb[IFA_ADDRESS])
+ return MNL_CB_OK;
+
+ addr = mnl_attr_get_payload(tb[IFA_ADDRESS]);
+ char out[INET6_ADDRSTRLEN];
+ inet_ntop(ifa->ifa_family, addr, out, sizeof(out));
+ debug("index=%d, family=%d, addr=%s\n", ifa->ifa_index, ifa->ifa_family,
+ out);
+
+ if (ifa->ifa_scope == RT_SCOPE_LINK) {
+ if (ifa->ifa_prefixlen != 128)
+ return MNL_CB_OK;
+ memcpy(cb_data->lladdr, addr, 16);
+ } else if (ifa->ifa_scope == RT_SCOPE_UNIVERSE) {
+ switch (ifa->ifa_family) {
+ case AF_INET:
+ cb_data->gaddr4->family = ifa->ifa_family;
+ memcpy(&cb_data->gaddr4->ip, addr, 4);
+ cb_data->gaddr4->cidr = ifa->ifa_prefixlen;
+ break;
+ case AF_INET6:
+ cb_data->gaddr6->family = ifa->ifa_family;
+ memcpy(&cb_data->gaddr6->ip, addr, 16);
+ cb_data->gaddr6->cidr = ifa->ifa_prefixlen;
+ break;
+ default:
+ die("Unknown address family: %u\n", ifa->ifa_family);
+ }
+ }
+
+ return MNL_CB_OK;
+}
+
+static void iface_update(uint16_t cmd, uint16_t flags, uint32_t ifindex,
+ const struct wg_combined_ip *addr)
+{
+ struct mnl_socket *nl;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ unsigned int seq, portid;
+ struct ifaddrmsg *ifaddr; /* linux/if_addr.h */
+ int ret;
+
+ nl = mnl_socket_open(NETLINK_ROUTE);
+ if (nl == NULL)
+ fatal("mnl_socket_open");
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+ fatal("mnl_socket_bind");
+
+ portid = mnl_socket_get_portid(nl);
+ seq = time(NULL);
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_seq = seq;
+ nlh->nlmsg_type = cmd;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
+ ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
+ ifaddr->ifa_family = addr->family;
+ ifaddr->ifa_prefixlen = addr->cidr;
+ ifaddr->ifa_scope = RT_SCOPE_UNIVERSE; /* linux/rtnetlink.h */
+ ifaddr->ifa_index = ifindex;
+ mnl_attr_put(nlh, IFA_LOCAL, addr->family == AF_INET ? 4 : 16,
+ &addr->ip);
+
+ if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+ fatal("mnl_socket_sendto");
+
+ do {
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret <= MNL_CB_STOP)
+ break;
+ ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL);
+ } while (ret > 0);
+
+ if (ret == -1)
+ fatal("mnl_cb_run/mnl_socket_recvfrom");
+
+ mnl_socket_close(nl);
+}
+
+static void iface_remove_addr(uint32_t ifindex,
+ const struct wg_combined_ip *addr)
+{
+ char ipstr[INET6_ADDRSTRLEN];
+ debug("removing %s/%u from interface %u\n",
+ inet_ntop(addr->family, &addr->ip, ipstr, sizeof ipstr),
+ addr->cidr, ifindex);
+ iface_update(RTM_DELADDR, 0, ifindex, addr);
+}
+
+static void iface_add_addr(uint32_t ifindex, const struct wg_combined_ip *addr)
+{
+ char ipstr[INET6_ADDRSTRLEN];
+ debug("adding %s/%u to interface %u\n",
+ inet_ntop(addr->family, &addr->ip, ipstr, sizeof ipstr),
+ addr->cidr, ifindex);
+ iface_update(RTM_NEWADDR, NLM_F_REPLACE | NLM_F_CREATE, ifindex, addr);
+}
+
+static bool get_and_validate_local_addrs(uint32_t ifindex,
+ struct in6_addr *lladdr,
+ struct wg_combined_ip *gaddr4,
+ struct wg_combined_ip *gaddr6)
+{
+ struct mnl_cb_data cb_data = {
+ .ifindex = ifindex,
+ .lladdr = lladdr,
+ .gaddr4 = gaddr4,
+ .gaddr6 = gaddr6,
+ };
+
+ iface_get_all_addrs(AF_INET, data_cb, &cb_data);
+ iface_get_all_addrs(AF_INET6, data_cb, &cb_data);
+
+ return !IN6_IS_ADDR_UNSPECIFIED(cb_data.lladdr);
+}
+
+#if 0
+static void dump_leases()
+{
+ char ip4str[INET6_ADDRSTRLEN], ip6str[INET6_ADDRSTRLEN];
+ struct wg_dynamic_lease *l = &our_lease;
+
+ if (l->start == 0) {
+ debug("lease NONE\n");
+ return;
+ }
+
+ debug("lease %u %u %s/%u %s/%u\n", l->start + l->leasetime,
+ l->start + l->leasetime - current_time(),
+ inet_ntop(AF_INET, &l->ip4.ip.ip4, ip4str, INET6_ADDRSTRLEN),
+ l->ip4.cidr,
+ inet_ntop(AF_INET6, &l->ip6.ip.ip6, ip6str, INET6_ADDRSTRLEN),
+ l->ip6.cidr);
+}
+#endif
+
+static int do_connect(int *fd)
+{
+ int res;
+ struct sockaddr_in6 our_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = our_lladdr,
+ .sin6_scope_id = device->ifindex,
+ };
+ struct sockaddr_in6 their_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(WG_DYNAMIC_PORT),
+ .sin6_scope_id = device->ifindex,
+ };
+
+ *fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (*fd < 0)
+ fatal("Creating a socket failed");
+
+ for (int port = LOW_PORT_START;; port++) {
+ our_addr.sin6_port = htons(port);
+ if (!bind(*fd, (struct sockaddr *)&our_addr, sizeof(our_addr)))
+ break;
+ if (errno != EADDRINUSE)
+ fatal("Binding socket failed");
+ if (port >= 1024)
+ die("No low ports available");
+ }
+
+ if (!inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &their_addr.sin6_addr))
+ fatal("inet_pton()");
+ if (connect(*fd, (struct sockaddr *)&their_addr,
+ sizeof(struct sockaddr_in6))) {
+ char out[INET6_ADDRSTRLEN];
+ if (!inet_ntop(their_addr.sin6_family, &their_addr.sin6_addr,
+ out, sizeof out))
+ fatal("inet_ntop()");
+ debug("Connecting to [%s]:%u failed: %s\n", out,
+ ntohs(their_addr.sin6_port), strerror(errno));
+ if (close(*fd))
+ debug("Closing socket failed: %s\n", strerror(errno));
+ *fd = -1;
+ return -1;
+ }
+
+ res = fcntl(*fd, F_GETFL, 0);
+ if (res < 0 || fcntl(*fd, F_SETFL, res | O_NONBLOCK) < 0)
+ fatal("Setting socket to nonblocking failed");
+
+ return 0;
+}
+
+static size_t connect_and_send(unsigned char *buf, size_t *len)
+{
+ size_t ret;
+ if (pollfds[0].fd < 0)
+ if (do_connect(&pollfds[0].fd))
+ return 0;
+ ret = send_message(pollfds[0].fd, buf, len);
+ return ret;
+}
+
+static bool request_ip(struct wg_dynamic_request *req,
+ const struct wg_dynamic_lease *lease)
+{
+ unsigned char buf[MAX_RESPONSE_SIZE + 1];
+ char addrstr[INET6_ADDRSTRLEN];
+ size_t msglen;
+ size_t written;
+
+ msglen = 0;
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen, "%s=%d\n",
+ WG_DYNAMIC_KEY[WGKEY_REQUEST_IP], 1);
+
+ if (lease && lease->ip4.ip.ip4.s_addr) {
+ if (!inet_ntop(AF_INET, &lease->ip4.ip.ip4, addrstr,
+ sizeof addrstr))
+ fatal("inet_ntop()");
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen,
+ "ipv4=%s/32\n", addrstr);
+ }
+ if (lease && !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)) {
+ if (!inet_ntop(AF_INET6, &lease->ip6.ip.ip6, addrstr,
+ sizeof addrstr))
+ fatal("inet_ntop()");
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen,
+ "ipv6=%s/128\n", addrstr);
+ }
+ /* nmsglen += print_to_buf((char *)buf, sizeof buf, msglen,
+ "leasetime=%u\n", fixme); */
+
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen, "\n");
+
+ written = connect_and_send(buf, &msglen);
+ if (msglen == 0)
+ return true;
+
+ debug("Socket %d blocking with %lu bytes to write, postponing\n",
+ pollfds[0].fd, msglen);
+ send_later(req, buf + written, msglen);
+ return false;
+}
+
+static int maybe_refresh_lease(uint32_t now, struct wg_dynamic_lease *lease,
+ struct wg_dynamic_request *req)
+{
+ if (now > lease->start + (lease->leasetime * 2) / 3) {
+ debug("Refreshing lease expiring on %u\n",
+ lease->start + lease->leasetime);
+ request_ip(req, lease);
+ return 0;
+ }
+
+ return 1;
+}
+
+static bool lease_is_valid(uint32_t now, struct wg_dynamic_lease *lease)
+{
+ return now < lease->start + lease->leasetime;
+}
+
+static void maybe_remove_lease(uint32_t now, struct wg_dynamic_lease *lease)
+{
+ if (!lease_is_valid(now, lease))
+ memset(lease, 0, sizeof *lease);
+}
+
+static void check_leases(struct wg_dynamic_request *req)
+{
+ uint32_t now = current_time();
+
+ if (!lease_is_valid(now, &our_lease))
+ request_ip(req, NULL);
+ else {
+ maybe_remove_lease(now, &our_lease);
+ maybe_refresh_lease(now, &our_lease, req);
+ }
+}
+
+static int handle_received_lease(const struct wg_dynamic_request *req)
+{
+ uint32_t ret;
+ struct wg_dynamic_attr *attr;
+ struct wg_dynamic_lease *lease = &our_lease;
+ uint32_t now = current_time();
+ uint32_t lease_start = 0;
+ uint32_t curleasetime = lease->start + lease->leasetime;
+
+ attr = req->first;
+ while (attr) {
+ switch (attr->key) {
+ case WGKEY_IPV4:
+ memcpy(&lease->ip4, attr->value,
+ sizeof(struct wg_combined_ip));
+ break;
+ case WGKEY_IPV6:
+ memcpy(&lease->ip6, attr->value,
+ sizeof(struct wg_combined_ip));
+ break;
+ case WGKEY_LEASESTART:
+ memcpy(&lease_start, attr->value, sizeof(uint32_t));
+ break;
+ case WGKEY_LEASETIME:
+ memcpy(&lease->leasetime, attr->value,
+ sizeof(uint32_t));
+ break;
+ case WGKEY_ERRNO:
+ memcpy(&ret, attr->value, sizeof(uint32_t));
+ if (ret) {
+ debug("Request IP failed with %ud from server\n",
+ ret);
+ return -ret;
+ }
+ break;
+ case WGKEY_ERRMSG:
+ /* TODO: do something with the error message */
+ break;
+ default:
+ debug("Ignoring invalid attribute for request_ip: %d\n",
+ attr->key);
+ }
+ attr = attr->next;
+ }
+
+ if (lease->leasetime == 0 ||
+ (lease->ip4.ip.ip4.s_addr == 0 &&
+ IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6)))
+ return -EINVAL;
+
+ if (abs(now - lease_start) < 15)
+ lease->start = lease_start;
+ else
+ lease->start = now;
+
+ debug("Replacing lease %u -> %u\n", curleasetime,
+ lease->start + lease->leasetime);
+
+ return 0;
+}
+
+static void cleanup()
+{
+ wg_free_device(device);
+ if (pollfds[0].fd >= 0)
+ if (close(pollfds[0].fd))
+ debug("Failed to close fd");
+}
+
+static bool handle_error(int fd, int ret)
+{
+ UNUSED(fd);
+ UNUSED(ret);
+
+ debug("Unable to parse response: %s\n", strerror(ret));
+
+ return true;
+}
+
+static void maybe_update_iface()
+{
+ if (memcmp(&our_gaddr4.ip, &our_lease.ip4.ip, sizeof our_gaddr4.ip) ||
+ our_gaddr4.cidr != our_lease.ip4.cidr) {
+ if (our_gaddr4.ip.ip4.s_addr)
+ iface_remove_addr(device->ifindex, &our_gaddr4);
+ iface_add_addr(device->ifindex, &our_lease.ip4);
+ memcpy(&our_gaddr4, &our_lease.ip4, sizeof our_gaddr4);
+ }
+ if (memcmp(&our_gaddr6.ip, &our_lease.ip6.ip, sizeof our_gaddr6.ip) ||
+ our_gaddr6.cidr != our_lease.ip6.cidr) {
+ if (!IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip.ip6))
+ iface_remove_addr(device->ifindex, &our_gaddr6);
+ iface_add_addr(device->ifindex, &our_lease.ip6);
+ memcpy(&our_gaddr6, &our_lease.ip6, sizeof our_gaddr6);
+ }
+}
+
+static bool handle_response(int fd, struct wg_dynamic_request *req)
+{
+ UNUSED(fd);
+
+#if 0
+ printf("Recieved response of type %s.\n", WG_DYNAMIC_KEY[req->cmd]);
+ struct wg_dynamic_attr *cur = req->first;
+ while (cur) {
+ printf(" with attr %s.\n", WG_DYNAMIC_KEY[cur->key]);
+ cur = cur->next;
+ }
+#endif
+
+ switch (req->cmd) {
+ case WGKEY_REQUEST_IP:
+ if (handle_received_lease(req) == 0)
+ maybe_update_iface();
+ break;
+ default:
+ debug("Unknown command: %d\n", req->cmd);
+ return true;
+ }
+
+ return true;
+}
int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused)))
{
- char *device_names, *device_name;
- size_t len;
+ struct wg_dynamic_request req = { 0 };
+ uint32_t now = current_time();
+
+ progname = argv[0];
+ if (argc != 2)
+ usage();
+
+ wg_interface = argv[1];
+
+ if (wg_get_device(&device, wg_interface))
+ fatal("Unable to access interface %s", wg_interface);
+
+ if (atexit(cleanup))
+ die("Failed to set exit function\n");
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
+ fatal("Unable to ignore SIGPIPE");
+
+ if (!get_and_validate_local_addrs(device->ifindex, &our_lladdr,
+ &our_gaddr4, &our_gaddr6))
+ die("%s needs to have an IPv6 link local address with prefixlen 128 assigned\n",
+ wg_interface);
+ // TODO: verify that we have a peer matching the requirements (fe80::/128)?
- device_names = wg_list_device_names();
- if (!device_names) {
- perror("Unable to get device names");
- return 1;
+ char lladr_str[INET6_ADDRSTRLEN];
+ debug("%s: %s\n", wg_interface,
+ inet_ntop(AF_INET6, &our_lladdr, lladr_str, sizeof lladr_str));
+
+ if (our_gaddr4.ip.ip4.s_addr ||
+ !IN6_IS_ADDR_UNSPECIFIED(&our_gaddr6.ip.ip6)) {
+ our_lease.start = now;
+ our_lease.leasetime = 15;
+ memcpy(&our_lease.ip4, &our_gaddr4,
+ sizeof(struct wg_combined_ip));
+ memcpy(&our_lease.ip6, &our_gaddr6,
+ sizeof(struct wg_combined_ip));
}
- wg_for_each_device_name(device_names, device_name, len) {
- wg_device *device;
- wg_peer *peer;
- wg_key_b64_string key;
- if (wg_get_device(&device, device_name) < 0) {
- perror("Unable to get device");
+ /* TODO: use a blocking socket instead of the unnecessary
+ * complexity of nonblocking */
+
+ pollfds[0] = (struct pollfd){.fd = -1, .events = POLLIN };
+ while (1) {
+ int nevents = poll(pollfds, 1, LEASE_CHECK_INTERVAL);
+
+ if (nevents == -1)
+ fatal("poll()");
+
+ if (nevents == 0) {
+ /* FIXME: if there's any risk for this path to
+ * be starving, maybe do this regardless of
+ * socket readiness? */
+ check_leases(&req);
continue;
}
- wg_key_to_base64(key, device->public_key);
- printf("%s has public key %s\n", device_name, key);
- wg_for_each_peer(device, peer) {
- wg_key_to_base64(key, peer->public_key);
- printf(" - peer %s\n", key);
+
+ if (pollfds[0].revents & POLLOUT) {
+ pollfds[0].revents &= ~POLLOUT;
+ debug("sending, trying again with %lu bytes\n",
+ req.buflen);
+ send_message(pollfds[0].fd, req.buf, &req.buflen);
+ if (!req.buflen)
+ close_connection(&pollfds[0].fd, &req);
+ }
+
+ if (pollfds[0].revents & POLLIN) {
+ pollfds[0].revents &= ~POLLIN;
+ if (handle_request(pollfds[0].fd, &req, handle_response,
+ handle_error))
+ close_connection(&pollfds[0].fd, &req);
+ else if (req.buf)
+ pollfds[0].events |= POLLOUT;
}
- wg_free_device(device);
}
- free(device_names);
+
return 0;
}
diff --git a/wg-dynamic-server.c b/wg-dynamic-server.c
index b63c72d..fc8098c 100644
--- a/wg-dynamic-server.c
+++ b/wg-dynamic-server.c
@@ -9,6 +9,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include <string.h>
#include <time.h>
@@ -17,7 +18,7 @@
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
-
+#include <netdb.h>
#include <libmnl/libmnl.h>
#include <linux/rtnetlink.h>
@@ -29,6 +30,7 @@
static const char *progname;
static const char *wg_interface;
static struct in6_addr well_known;
+/* static in_addr_t inaddr_any = INADDR_ANY; */
static wg_device *device = NULL;
static struct radix_trie allowedips_trie;
@@ -36,6 +38,7 @@ static struct pollfd pollfds[MAX_CONNECTIONS + 1];
struct mnl_cb_data {
uint32_t ifindex;
+ struct in6_addr addr;
bool valid_ip_found;
};
@@ -44,12 +47,6 @@ static void usage()
die("usage: %s <wg-interface>\n", progname);
}
-static bool is_link_local(unsigned char *addr)
-{
- /* TODO: check if the remaining 48 bits are 0 */
- return addr[0] == 0xFE && addr[1] == 0x80;
-}
-
static int data_attr_cb(const struct nlattr *attr, void *data)
{
const struct nlattr **tb = data;
@@ -105,56 +102,26 @@ static int data_cb(const struct nlmsghdr *nlh, void *data)
static bool validate_link_local_ip(uint32_t ifindex)
{
- struct mnl_socket *nl;
- char buf[MNL_SOCKET_BUFFER_SIZE];
- struct nlmsghdr *nlh;
- /* TODO: rtln-addr-dump from libmnl uses rtgenmsg here? */
- struct ifaddrmsg *ifaddr;
- int ret;
- unsigned int seq, portid;
struct mnl_cb_data cb_data = {
- .ifindex = ifindex,
- .valid_ip_found = false,
+ .ifindex = ifindex, .valid_ip_found = false,
};
- nl = mnl_socket_open(NETLINK_ROUTE);
- if (nl == NULL)
- fatal("mnl_socket_open");
-
- if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
- fatal("mnl_socket_bind");
-
- /* You'd think that we could just request addresses from a specific
- * interface, via NLM_F_MATCH or something, but we can't. See also:
- * https://marc.info/?l=linux-netdev&m=132508164508217
- */
- seq = time(NULL);
- portid = mnl_socket_get_portid(nl);
- nlh = mnl_nlmsg_put_header(buf);
- nlh->nlmsg_type = RTM_GETADDR;
- nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
- nlh->nlmsg_seq = seq;
- ifaddr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));
- ifaddr->ifa_family = AF_INET6;
-
- if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
- fatal("mnl_socket_sendto");
-
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- while (ret > 0) {
- ret = mnl_cb_run(buf, ret, seq, portid, data_cb, &cb_data);
- if (ret <= MNL_CB_STOP)
- break;
- ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
- }
- if (ret == -1)
- fatal("mnl_cb_run/mnl_socket_recvfrom");
-
- mnl_socket_close(nl);
+ iface_get_all_addrs(AF_INET6, data_cb, &cb_data);
return cb_data.valid_ip_found;
}
+static int get_avail_pollfds()
+{
+ for (int nfds = 1;; ++nfds) {
+ if (nfds >= MAX_CONNECTIONS + 1)
+ return -1;
+
+ if (pollfds[nfds].fd < 0)
+ return nfds;
+ }
+}
+
static bool valid_peer_found(wg_device *device)
{
wg_peer *peer;
@@ -180,17 +147,6 @@ static bool valid_peer_found(wg_device *device)
return false;
}
-static int get_avail_pollfds()
-{
- for (int nfds = 1;; ++nfds) {
- if (nfds >= MAX_CONNECTIONS + 1)
- return -1;
-
- if (pollfds[nfds].fd < 0)
- return nfds;
- }
-}
-
static void rebuild_allowedips_trie()
{
int ret;
@@ -242,6 +198,7 @@ static int accept_connection(int sockfd, wg_key *dest)
wg_key *pubkey;
struct sockaddr_storage addr;
socklen_t size = sizeof addr;
+
#ifdef __linux__
fd = accept4(sockfd, (struct sockaddr *)&addr, &size, SOCK_NONBLOCK);
if (fd < 0)
@@ -255,6 +212,27 @@ static int accept_connection(int sockfd, wg_key *dest)
if (res < 0 || fcntl(fd, F_SETFL, res | O_NONBLOCK) < 0)
fatal("Setting socket to nonblocking failed");
#endif
+
+ char addrstring[INET6_ADDRSTRLEN];
+ char serv[10];
+ if (getnameinfo((struct sockaddr *)&addr, sizeof addr, addrstring,
+ sizeof addrstring, serv, sizeof serv,
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ fatal("getnameinfo");
+
+ if (addr.ss_family != AF_INET6) {
+ debug("%s: rejecting client for not using an IPv6 address\n",
+ addrstring);
+ return -EINVAL;
+ }
+ unsigned long port = strtoul(serv, NULL, 10);
+ if (port >= 1024) {
+ debug("%s: rejecting client for not using a low port: %lu\n",
+ addrstring, port);
+ return -EINVAL;
+ }
+ //debug("Client connection from [%s]:%lu\n", addrstring, port);
+
pubkey = addr_to_pubkey(&addr);
if (!pubkey) {
/* our copy of allowedips is outdated, refresh */
@@ -266,14 +244,11 @@ static int accept_connection(int sockfd, wg_key *dest)
return -ENOENT;
}
}
- memcpy(dest, pubkey, sizeof *pubkey);
+ memcpy(dest, pubkey, sizeof *dest);
wg_key_b64_string key;
- char out[INET6_ADDRSTRLEN];
wg_key_to_base64(key, *pubkey);
- inet_ntop(addr.ss_family, &((struct sockaddr_in6 *)&addr)->sin6_addr,
- out, sizeof(out));
- debug("%s has pubkey: %s\n", out, key);
+ debug("%s has pubkey: %s\n", addrstring, key);
return fd;
}
@@ -295,28 +270,225 @@ static void accept_incoming(int sockfd, struct wg_dynamic_request *reqs)
}
}
-static void close_connection(int *fd, struct wg_dynamic_request *req)
+static int allocate_from_pool(struct wg_dynamic_request *const req,
+ struct wg_dynamic_lease *lease)
+{
+ struct wg_dynamic_attr *attr;
+ struct wg_combined_ip default_v4 = {.family = AF_INET, .cidr = 32 },
+ default_v6 = {.family = AF_INET6, .cidr = 128 };
+
+ /* NOTE: handing out whatever the client asks for */
+ /* TODO: choose an ip address from pool of available
+ * addresses, together with an appropriate lease time */
+ /* NOTE: the pool is to be drawn from what routes are pointing
+ * to the wg interface, and kept up to date as the routing
+ * table changes */
+
+ if (!inet_pton(AF_INET, "192.168.47.11", &default_v4.ip.ip4))
+ fatal("inet_pton()");
+ memcpy(&lease->ip4, &default_v4, sizeof(struct wg_combined_ip));
+
+ if (!inet_pton(AF_INET6, "fd00::4711", &default_v6.ip.ip6))
+ fatal("inet_pton()");
+ memcpy(&lease->ip6, &default_v6, sizeof(struct wg_combined_ip));
+
+ lease->start = current_time();
+ lease->leasetime = WG_DYNAMIC_LEASETIME;
+
+ attr = req->first;
+ while (attr) {
+ switch (attr->key) {
+ case WGKEY_IPV4:
+ break; /* FIXME */
+ memcpy(&lease->ip4, attr->value,
+ sizeof(struct wg_combined_ip));
+ break;
+ case WGKEY_IPV6:
+ break; /* FIXME */
+ memcpy(&lease->ip6, attr->value,
+ sizeof(struct wg_combined_ip));
+ break;
+ case WGKEY_LEASETIME:
+ memcpy(&lease->leasetime, attr->value,
+ sizeof(uint32_t));
+ break;
+ default:
+ debug("Ignoring invalid attribute for request_ip: %d\n",
+ attr->key);
+ }
+
+ attr = attr->next;
+ }
+
+ return 0;
+}
+
+static bool send_error(int fd, int ret)
+{
+ UNUSED(fd);
+ debug("Error: %s\n", strerror(ret));
+ return true;
+}
+
+static bool serialise_lease(char *buf, size_t bufsize, size_t *offset,
+ const struct wg_dynamic_lease *lease)
+{
+ char addrbuf[INET6_ADDRSTRLEN];
+ bool ret = false;
+
+ if (lease->ip4.family) { /* FIXME: memcmp(&lease->ip4, &inaddr_any, 4) instead? */
+ if (!inet_ntop(AF_INET, &lease->ip4.ip.ip4, addrbuf,
+ sizeof addrbuf))
+ fatal("inet_ntop()");
+ *offset += print_to_buf(buf, bufsize, *offset, "ipv4=%s/%d\n",
+ addrbuf, lease->ip4.cidr);
+ ret = true;
+ }
+ if (lease->ip6.family) { /* FIXME: !IN6_IS_ADDR_UNSPECIFIED(&lease->ip6) instead? */
+ if (!inet_ntop(AF_INET6, &lease->ip6.ip.ip6, addrbuf,
+ sizeof addrbuf))
+ fatal("inet_ntop()");
+ *offset += print_to_buf(buf, bufsize, *offset, "ipv6=%s/%d\n",
+ addrbuf, lease->ip6.cidr);
+ ret = true;
+ }
+
+ if (ret) {
+ *offset += print_to_buf(buf, bufsize, *offset,
+ "leasestart=%u\n", lease->start);
+ *offset += print_to_buf(buf, bufsize, *offset, "leasetime=%u\n",
+ lease->leasetime);
+ }
+
+ return ret;
+}
+
+static struct wg_peer *current_peer(struct wg_dynamic_request *req)
+{
+ struct wg_peer *peer;
+
+ wg_for_each_peer (device, peer) {
+ if (!memcmp(peer->public_key, req->pubkey, sizeof(wg_key)))
+ return peer;
+ }
+
+ return NULL;
+}
+
+static void insert_allowed_ip(struct wg_peer *peer,
+ const struct wg_combined_ip *ip)
{
- if (close(*fd))
- debug("Failed to close socket");
+ struct wg_allowedip *newip;
+
+ newip = calloc(1, sizeof(struct wg_allowedip));
+ if (!newip)
+ fatal("calloc()");
- *fd = -1;
- free_wg_dynamic_request(req);
+ newip->family = ip->family;
+ switch (newip->family) {
+ case AF_INET:
+ memcpy(&newip->ip4, &ip->ip.ip4, sizeof(struct in_addr));
+ break;
+ case AF_INET6:
+ memcpy(&newip->ip6, &ip->ip.ip6, sizeof(struct in6_addr));
+ break;
+ }
+ newip->cidr = ip->cidr;
+
+ if (!peer->first_allowedip)
+ peer->first_allowedip = newip;
+ else
+ peer->last_allowedip->next_allowedip = newip;
+ peer->last_allowedip = newip;
}
-static void send_response(int fd, struct wg_dynamic_request *req)
+static int add_allowed_ips(struct wg_peer *peer,
+ const struct wg_dynamic_lease *lease)
{
+ if (lease->ip4.ip.ip4.s_addr)
+ insert_allowed_ip(peer, &lease->ip4);
+ if (!IN6_IS_ADDR_UNSPECIFIED(&lease->ip6.ip.ip6))
+ insert_allowed_ip(peer, &lease->ip6);
+
+ return wg_set_device(device);
+}
+
+static bool send_response(int fd, struct wg_dynamic_request *req)
+{
+ int ret;
+ char *errmsg = "OK";
+ unsigned char buf[MAX_RESPONSE_SIZE + 1];
+ size_t msglen;
+ size_t written;
+ struct wg_dynamic_lease lease = { 0 };
+ struct wg_peer *peer;
+
+#if 0
printf("Recieved request of type %s.\n", WG_DYNAMIC_KEY[req->cmd]);
struct wg_dynamic_attr *cur = req->first;
while (cur) {
printf(" with attr %s.\n", WG_DYNAMIC_KEY[cur->key]);
cur = cur->next;
}
-}
+#endif
-static void send_error(int fd, int ret)
-{
- debug("Error: %s\n", strerror(ret));
+ peer = current_peer(req);
+ if (!peer)
+ die("Unable to find peer\n");
+
+ ret = 0;
+ msglen = 0;
+ switch (req->cmd) {
+ case WGKEY_REQUEST_IP:
+ msglen = print_to_buf((char *)buf, sizeof buf, 0, "%s=%d\n",
+ WG_DYNAMIC_KEY[req->cmd], 1);
+ ret = allocate_from_pool(req, &lease);
+ if (ret) {
+ debug("IP address allocation failing with %d\n", ret);
+ ret = 0x01;
+ errmsg = "Out of IP addresses";
+ break;
+ }
+
+ ret = add_allowed_ips(peer, &lease);
+ if (ret) {
+ debug("Unable to add address(es) to peer: %s\n",
+ strerror(-ret));
+ ret = 0x2;
+ errmsg = "Internal error";
+ break;
+ }
+
+ if (!serialise_lease((char *)buf, sizeof buf, &msglen,
+ &lease)) {
+ die("Nothing to hand out, despite succeeding allocate_from_pool()\n");
+ ret = 0x3;
+ errmsg = "Internal error";
+ break;
+ }
+
+ break;
+
+ default:
+ debug("Unknown command: %d\n", req->cmd);
+ return true;
+ }
+
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen, "errno=%d\n", ret);
+ if (ret)
+ msglen += print_to_buf((char *)buf, sizeof buf, msglen, "errmsg=%s\n", errmsg);
+ if (msglen == sizeof buf)
+ fatal("Outbuffer too small");
+ buf[msglen++] = '\n';
+
+ written = send_message(fd, buf, &msglen);
+ if (msglen == 0)
+ return true;
+
+ debug("Socket %d blocking on write with %lu bytes left, postponing\n",
+ fd, msglen);
+ send_later(req, buf + written, msglen);
+ return false;
}
static void setup_socket(int *fd)
@@ -367,13 +539,11 @@ int main(int argc, char *argv[])
int *sockfd = &pollfds[0].fd;
progname = argv[0];
- inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known);
+ if (!inet_pton(AF_INET6, WG_DYNAMIC_ADDR, &well_known))
+ fatal("inet_pton()");
for (int i = 0; i < MAX_CONNECTIONS + 1; ++i) {
- pollfds[i] = (struct pollfd){
- .fd = -1,
- .events = POLLIN,
- };
+ pollfds[i] = (struct pollfd){.fd = -1, .events = POLLIN };
}
if (argc != 2)
@@ -408,13 +578,26 @@ int main(int argc, char *argv[])
}
for (int i = 1; i < MAX_CONNECTIONS + 1; ++i) {
- if (!(pollfds[i].revents & POLLIN))
+ if (!(pollfds[i].revents & POLLOUT))
+ continue;
+
+ pollfds[i].revents &= ~POLLOUT;
+ send_message(pollfds[i].fd, reqs[i - 1].buf,
+ &reqs[i - 1].buflen);
+ if (!reqs[i - 1].buflen)
+ close_connection(&pollfds[i].fd, &reqs[i - 1]);
+ }
+
+ for (int i = 1; i < MAX_CONNECTIONS + 1; ++i) {
+ if (pollfds[i].fd < 0 || !pollfds[i].revents & POLLIN)
continue;
- pollfds[i].revents = 0;
+ pollfds[i].revents &= ~POLLIN;
if (handle_request(pollfds[i].fd, &reqs[i - 1],
send_response, send_error))
close_connection(&pollfds[i].fd, &reqs[i - 1]);
+ else if (reqs[i - 1].buf)
+ pollfds[i].events |= POLLOUT;
}
}