⛅ Sync 2026-07-22 03:00:29

This commit is contained in:
github-actions[bot] committed 2026-07-22 03:00:29 +08:00
1 parent 473d483fb6
commit 408cef6151
28 files changed
+15055 -1

No files matched your search

+310
View File
@@ -0,0 +1,310 @@
// SPDX-License-Identifier: GPL-2.0
/*
* mwan3ct - targeted conntrack flush helper for mwan3
*
* Uses libnetfilter_conntrack's NFCT_Q_FLUSH_FILTER to perform
* kernel-side filtered conntrack entry deletion by mark and/or
* status bits.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
/*
* id2mask - expand a sequential bit index into a sparse mask
*
* Maps each set bit in 'id' (counting from bit 0) to the
* corresponding set bit position in 'mask', producing a value
* that occupies only the bit positions defined by 'mask'.
*/
static uint32_t id2mask(uint32_t id, uint32_t mask)
{
uint32_t result = 0;
uint32_t bit = 1;
for (uint32_t m = mask; m; m &= m - 1) {
if (id & bit)
result |= m & (-m);
bit <<= 1;
}
return result;
}
/*
* flush_filtered - flush conntrack entries matching mark and/or status
*
* Builds a kernel-side filter from the supplied mark and status
* value/mask pairs, then issues a single NFCT_Q_FLUSH_FILTER query.
* ENOENT (no matching entries) is treated as success.
*/
static int flush_filtered(struct nfct_handle *h,
uint32_t mark_val, uint32_t mark_mask,
uint32_t status_val, uint32_t status_mask,
int have_mark, int have_status)
{
struct nfct_filter_dump *filter = NULL;
int ret;
filter = nfct_filter_dump_create();
if (!filter) {
fprintf(stderr, "mwan3ct: nfct_filter_dump_create: %s\n",
strerror(errno));
return -1;
}
if (have_mark) {
struct nfct_filter_dump_mark mark = {
.val = mark_val,
.mask = mark_mask,
};
nfct_filter_dump_set_attr(filter, NFCT_FILTER_DUMP_MARK,
&mark);
}
if (have_status) {
struct nfct_filter_dump_mark status = {
.val = status_val,
.mask = status_mask,
};
nfct_filter_dump_set_attr(filter, NFCT_FILTER_DUMP_STATUS,
&status);
}
ret = nfct_query(h, NFCT_Q_FLUSH_FILTER, filter);
nfct_filter_dump_destroy(filter);
if (ret == -1 && errno != ENOENT)
return -1;
return 0;
}
/*
* flush_any_marked - flush conntrack entries for all mark values in a mask
*
* Iterates over every non-zero combination of bits within 'mask'
* and issues a filtered flush for each. Used when the caller wants
* to remove all mwan3-marked entries regardless of which interface
* they belong to.
*/
static int flush_any_marked(struct nfct_handle *h, uint32_t mask,
uint32_t status_val, uint32_t status_mask,
int have_status)
{
int bitcount = __builtin_popcount(mask);
uint32_t max_id;
if (bitcount > 16) {
errno = EINVAL;
return -1;
}
max_id = (1U << bitcount) - 1;
for (uint32_t id = 1; id <= max_id; id++) {
uint32_t mark_val = id2mask(id, mask);
int ret = flush_filtered(h, mark_val, mask, status_val,
status_mask, 1, have_status);
if (ret < 0)
return ret;
}
return 0;
}
/*
* parse_u32 - parse a string as a uint32_t with full validation
*
* Rejects negative values, overflow, and empty input. On success
* *endp points past the last consumed character, allowing the caller
* to check for trailing garbage. Returns 0 on success, -1 on failure.
*/
static int parse_u32(const char *s, char **endp, uint32_t *out)
{
unsigned long tmp;
if (*s == '-')
return -1;
errno = 0;
tmp = strtoul(s, endp, 0);
if (*endp == s || errno)
return -1;
if ((uint32_t)tmp != tmp)
return -1;
*out = (uint32_t)tmp;
return 0;
}
/*
* parse_val_mask - parse a "value/mask" string into two uint32 values
*
* Accepts "val/mask" or just "val", in which case the mask defaults
* to the value itself. Returns 0 on success, -1 on parse failure.
*/
static int parse_val_mask(const char *arg, uint32_t *val, uint32_t *mask)
{
char *end = NULL;
if (parse_u32(arg, &end, val))
return -1;
if (*end == '/') {
if (parse_u32(end + 1, &end, mask))
return -1;
if (*end != '\0')
return -1;
} else if (*end == '\0') {
*mask = *val;
} else {
return -1;
}
return 0;
}
/*
* usage - print command usage to stderr
*/
static void usage(void)
{
fprintf(stderr,
"Usage: mwan3ct flush [--mark <val>/<mask>] "
"[--mark-any <mask>] [--status <val>/<mask>]\n");
}
/*
* main - parse arguments and dispatch the requested flush operation
*
* Supports three mutually exclusive filter modes: --mark (exact
* value/mask match), --mark-any (iterate all non-zero mark values
* within a mask), and --status (conntrack status bit filter).
* --mark and --status, or --mark-any and --status, may be combined.
*/
int main(int argc, char *argv[])
{
uint32_t mark_val = 0, mark_mask = 0;
uint32_t status_val = 0, status_mask = 0;
int have_mark = 0, have_mark_any = 0, have_status = 0;
struct nfct_handle *h = NULL;
int ret;
if (argc >= 2 && (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0)) {
usage();
return EXIT_SUCCESS;
}
if (argc < 2 || strcmp(argv[1], "flush") != 0) {
usage();
return EXIT_FAILURE;
}
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "--mark") == 0 && i + 1 < argc) {
if (have_mark) {
fprintf(stderr,
"mwan3ct: duplicate --mark\n");
return EXIT_FAILURE;
}
if (parse_val_mask(argv[++i], &mark_val, &mark_mask)) {
fprintf(stderr, "mwan3ct: bad --mark value\n");
return EXIT_FAILURE;
}
have_mark = 1;
} else if (strcmp(argv[i], "--mark-any") == 0 && i + 1 < argc) {
char *end;
if (have_mark_any) {
fprintf(stderr,
"mwan3ct: duplicate --mark-any\n");
return EXIT_FAILURE;
}
if (parse_u32(argv[++i], &end, &mark_mask) ||
*end != '\0' || !mark_mask) {
fprintf(stderr,
"mwan3ct: bad --mark-any mask\n");
return EXIT_FAILURE;
}
have_mark_any = 1;
} else if (strcmp(argv[i], "--status") == 0 && i + 1 < argc) {
if (have_status) {
fprintf(stderr,
"mwan3ct: duplicate --status\n");
return EXIT_FAILURE;
}
if (parse_val_mask(argv[++i],
&status_val, &status_mask)) {
fprintf(stderr,
"mwan3ct: bad --status value\n");
return EXIT_FAILURE;
}
have_status = 1;
} else {
usage();
return EXIT_FAILURE;
}
}
if (have_mark && have_mark_any) {
fprintf(stderr,
"mwan3ct: --mark and --mark-any "
"are mutually exclusive\n");
return EXIT_FAILURE;
}
if (!have_mark && !have_mark_any && !have_status) {
fprintf(stderr, "mwan3ct: at least one filter required\n");
usage();
return EXIT_FAILURE;
}
if (have_mark && !mark_mask) {
fprintf(stderr, "mwan3ct: --mark mask must be non-zero\n");
return EXIT_FAILURE;
}
if (have_mark && (mark_val & ~mark_mask)) {
fprintf(stderr,
"mwan3ct: --mark value has bits outside mask\n");
return EXIT_FAILURE;
}
if (have_status && !status_mask) {
fprintf(stderr,
"mwan3ct: --status mask must be non-zero\n");
return EXIT_FAILURE;
}
if (have_status && (status_val & ~status_mask)) {
fprintf(stderr,
"mwan3ct: --status value has bits outside mask\n");
return EXIT_FAILURE;
}
h = nfct_open(CONNTRACK, 0);
if (!h) {
fprintf(stderr, "mwan3ct: nfct_open: %s\n", strerror(errno));
return EXIT_FAILURE;
}
if (have_mark_any)
ret = flush_any_marked(h, mark_mask, status_val,
status_mask, have_status);
else
ret = flush_filtered(h, mark_val, mark_mask,
status_val, status_mask,
have_mark, have_status);
nfct_close(h);
if (ret < 0) {
fprintf(stderr, "mwan3ct: flush failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
+123
View File
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0
/*
* mwan3ipcheck - validate and classify IP addresses and CIDR notation
*
* Uses inet_pton() to determine whether a string is a valid IPv4 or
* IPv6 address, optionally with a CIDR prefix length.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
static int classify(const char *arg)
{
char buf[64];
unsigned char addr[sizeof(struct in6_addr)];
const char *pfxstr = NULL;
int family;
size_t len;
len = strlen(arg);
if (len == 0 || len >= sizeof(buf))
return 0;
memcpy(buf, arg, len + 1);
char *slash = strchr(buf, '/');
if (slash) {
*slash = '\0';
pfxstr = slash + 1;
if (*pfxstr == '\0')
return 0;
}
if (inet_pton(AF_INET, buf, addr) == 1)
family = AF_INET;
else if (inet_pton(AF_INET6, buf, addr) == 1)
family = AF_INET6;
else
return 0;
if (pfxstr) {
char *end;
long pfx;
int max_pfx = (family == AF_INET) ? 32 : 128;
if (pfxstr[0] == '0' && pfxstr[1] != '\0')
return 0;
pfx = strtol(pfxstr, &end, 10);
if (*end != '\0' || end == pfxstr)
return 0;
if (pfx < 0 || pfx > max_pfx)
return 0;
}
return family;
}
static int classify_list(const char *arg)
{
char buf[512];
char *token = NULL, *saveptr = NULL;
int common_family = 0;
size_t len = strlen(arg);
if (len == 0 || len >= sizeof(buf))
return 0;
memcpy(buf, arg, len + 1);
for (token = strtok_r(buf, ",", &saveptr);
token;
token = strtok_r(NULL, ",", &saveptr)) {
while (*token && isspace((unsigned char)*token))
token++;
size_t tlen = strlen(token);
while (tlen > 0 && isspace((unsigned char)token[tlen - 1]))
token[--tlen] = '\0';
if (tlen == 0)
continue;
int family = classify(token);
if (family == 0)
return 0;
if (common_family == 0)
common_family = family;
else if (family != common_family)
return -1;
}
if (common_family == 0)
return 0;
return common_family;
}
int main(int argc, char *argv[])
{
int result;
if (argc != 2)
result = 0;
else
result = classify_list(argv[1]);
if (result == AF_INET)
puts("ipv4");
else if (result == AF_INET6)
puts("ipv6");
else if (result == -1) {
puts("mixed");
return EXIT_FAILURE;
} else {
puts("invalid");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
+260
View File
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2020 Aaron Goodman <aaronjg@alumni.stanford.edu>. All Rights Reserved.
*/
/*
* sockopt_wrap.c provides a shared library that intercepts syscalls to various
* networking functions to bind the sockets a source IP address and network device
* and to set the firewall mark on otugoing packets. Parameters are set using the
* DEVICE, SRCIP, FWMARK environment variables.
*
* Additionally the FAMILY environment variable can be set to either 'ipv4' or
* 'ipv6' to cause sockets opened with ipv6 or ipv4 to fail, respectively.
*
* Each environment variable is optional, and if not set, the library will not
* enforce the particular parameter.
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <linux/if_packet.h>
#include <net/if.h>
static int (*next_socket)(int domain, int type, int protocol);
static int (*next_setsockopt)(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
static int (*next_bind)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
static int (*next_close)(int fd);
static ssize_t (*next_send)(int sockfd, const void *buf, size_t len, int flags);
static ssize_t (*next_sendto)(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
static ssize_t (*next_sendmsg)(int sockfd, const struct msghdr *msg, int flags);
static int (*next_connect)(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
static int device=0;
static struct sockaddr_in source4 = {0};
#ifdef CONFIG_IPV6
static struct sockaddr_in6 source6 = {0};
#endif
static struct sockaddr * source = 0;
static int sockaddr_size = 0;
static int is_bound [1024] = {0};
#define next_func(x)\
void set_next_##x(){\
if (next_##x) return;\
next_##x = dlsym(RTLD_NEXT, #x);\
dlerror_handle();\
return;\
}
void dlerror_handle()
{
char *msg;
if ((msg = dlerror()) != NULL) {
fprintf(stderr, "socket: dlopen failed : %s\n", msg);
fflush(stderr);
exit(EXIT_FAILURE);
}
}
next_func(bind);
next_func(close);
next_func(setsockopt);
next_func(socket);
next_func(send);
next_func(sendto);
next_func(sendmsg);
next_func(connect);
void dobind(int sockfd)
{
if (source && sockfd < 1024 && !is_bound[sockfd]) {
set_next_bind();
if (next_bind(sockfd, source, sockaddr_size)) {
perror("mwan3 sockopt_wrap: failed to bind to source address");
next_close(sockfd);
/* Do not exit — let the caller's syscall fail with EBADF so
* the tracked process exits with a normal error code. This
* can happen when SRC_IP becomes stale (e.g. after a DHCP
* address change) and must not terminate the ping abruptly
* with no log from mwan3track. */
return;
}
is_bound[sockfd] = 1;
}
}
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
set_next_connect();
dobind(sockfd);
return next_connect(sockfd, addr, addrlen);
}
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
set_next_send();
dobind(sockfd);
return next_send(sockfd, buf, len, flags);
}
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen)
{
set_next_sendto();
dobind(sockfd);
return next_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
}
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
set_next_sendmsg();
dobind(sockfd);
return next_sendmsg(sockfd, msg, flags);
}
int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
set_next_bind();
if (device && addr->sa_family == AF_PACKET) {
((struct sockaddr_ll*)addr)->sll_ifindex=device;
}
else if (source && addr->sa_family == AF_INET) {
((struct sockaddr_in*)addr)->sin_addr = source4.sin_addr;
}
#ifdef CONFIG_IPV6
else if (source && addr->sa_family == AF_INET6) {
((struct sockaddr_in6*)addr)->sin6_addr = source6.sin6_addr;
}
#endif
if (sockfd < 1024)
is_bound[sockfd] = 1;
return next_bind(sockfd, addr, addrlen);
}
int close (int sockfd)
{
set_next_close();
if (sockfd < 1024)
is_bound[sockfd]=0;
return next_close(sockfd);
}
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
{
set_next_setsockopt();
if (level == SOL_SOCKET && (optname == SO_MARK || optname == SO_BINDTODEVICE))
return 0;
return next_setsockopt(sockfd, level, optname, optval, optlen);
}
int socket(int domain, int type, int protocol)
{
int handle;
const char *socket_str = getenv("DEVICE");
const char *srcip_str = getenv("SRCIP");
const char *fwmark_str = getenv("FWMARK");
const char *family_str = getenv("FAMILY");
const int iface_len = socket_str ? strnlen(socket_str, IFNAMSIZ) : 0;
int has_family = family_str && *family_str != 0;
int has_srcip = srcip_str && *srcip_str != 0;
const int fwmark = fwmark_str ? (int)strtol(fwmark_str, NULL, 0) : 0;
set_next_close();
set_next_socket();
set_next_send();
set_next_setsockopt();
set_next_sendmsg();
set_next_sendto();
set_next_connect();
if(has_family) {
#ifdef CONFIG_IPV6
if(domain == AF_INET && strncmp(family_str,"ipv6",4) == 0)
return -1;
#endif
if(domain == AF_INET6 && strncmp(family_str,"ipv4",4) == 0)
return -1;
}
if (domain != AF_INET
#ifdef CONFIG_IPV6
&& domain != AF_INET6
#endif
) {
return next_socket(domain, type, protocol);
}
if (iface_len > 0) {
if (iface_len == IFNAMSIZ) {
fprintf(stderr, "mwan3 sockopt_wrap: interface name too long\n");
fflush(stderr);
return -1;
}
}
if (has_srcip) {
int s;
void * addr_buf;
if (domain == AF_INET) {
addr_buf = &source4.sin_addr;
sockaddr_size=sizeof source4;
memset(&source4, 0, sockaddr_size);
source4.sin_family = domain;
source = (struct sockaddr*)&source4;
}
#ifdef CONFIG_IPV6
else {
addr_buf = &source6.sin6_addr;
sockaddr_size=sizeof source6;
memset(&source6, 0, sockaddr_size);
source6.sin6_family=domain;
source = (struct sockaddr*)&source6;
}
#endif
s = inet_pton(domain, srcip_str, addr_buf);
if (s == 0) {
fprintf(stderr, "socket: ip address invalid format for family %s\n",
domain == AF_INET ? "AF_INET" : domain == AF_INET6 ?
"AF_INET6" : "unknown");
return -1;
}
if (s < 0) {
perror("inet_pton");
exit(EXIT_FAILURE);
}
}
handle = next_socket(domain, type, protocol);
if (handle == -1 ) {
return handle;
}
if (iface_len > 0) {
device=if_nametoindex(socket_str);
if (next_setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE,
socket_str, iface_len + 1)) {
perror("mwan3 sockopt_wrap: failed to bind to interface");
next_close(handle);
return -1;
}
}
if (fwmark > 0) {
if (next_setsockopt(handle, SOL_SOCKET, SO_MARK,
&fwmark, sizeof fwmark)) {
perror("mwan3 sockopt_wrap: failed to set firewall mark");
next_close(handle);
return -1;
}
}
return handle;
}