UDP Redirect
Loading...
Searching...
No Matches
udp-redirect.c File Reference
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <math.h>
#include <netdb.h>
#include <time.h>
Include dependency graph for udp-redirect.c:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  settings
struct  statistics

Macros

#define UDP_REDIRECT_VERSION   "2.2.0"
#define STATISTICS_DELAY_SECONDS   60
#define NETWORK_BUFFER_SIZE   65535
#define EOK   0
 Readability: errno value for OK.
#define MAX_ERRNO   256
#define ERRNO_IGNORE_INIT(X)
#define ERRNO_IGNORE_SET(X, Y)
#define ERRNO_IGNORE_CHECK(X, Y)
#define PARSE_PORT(src, dest, label)
#define DEBUG(debug_level_local, debug_level_message, fmt, ...)
#define HUMAN_READABLE_FORMAT   "%.1lf%c"
#define HRF   HUMAN_READABLE_FORMAT
#define HUMAN_READABLE(X)
#define HUMAN_READABLE_SIZES   { ' ', 'K', 'M', 'G', 'T', 'P', 'E' }
#define HUMAN_READABLE_SIZES_COUNT   7

Enumerations

enum  DEBUG_LEVEL { DEBUG_LEVEL_ERROR = 0 , DEBUG_LEVEL_INFO = 1 , DEBUG_LEVEL_VERBOSE = 2 , DEBUG_LEVEL_DEBUG = 3 }
 The available debug levels. More...

Functions

static int socket_setup (const int debug_level, const char *desc, const char *xaddr, const int xport, const char *xif, int xfamily, struct sockaddr_storage *xsock_name)
static char * resolve_host (int debug_level, const char *host)
static int parse_addr (const char *str, int port, struct sockaddr_storage *out)
static const char * addr_tostring (const struct sockaddr_storage *sa, char *buf, size_t len)
static int addr_port (const struct sockaddr_storage *sa)
static socklen_t addr_len (const struct sockaddr_storage *sa)
static int addr_is_unset (const struct sockaddr_storage *sa)
static int addr_equal (const struct sockaddr_storage *a, const struct sockaddr_storage *b)
void settings_initialize (struct settings *s)
void usage (const char *argv0, const char *message)
void statistics_initialize (struct statistics *st)
double int_to_human_value (double value)
char int_to_human_char (double value)
void statistics_display (int debug_level, struct statistics *st, time_t now)
int main (int argc, char *argv[])
static double int_to_human_scale (double value, int *count_out)

Variables

static struct option longopts []

Detailed Description

Author
Dan Podeanu pdan@.nosp@m.esyn.nosp@m.c.org
Version
2.2.0

LICENSE

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

DESCRIPTION

A single-file, high-performance UDP packet redirector supporting IPv4 and IPv6. Useful when layer-level redirection (e.g., firewall rules) is impractical — common use cases include Wireguard VPN, DNS, and game servers.

Design overview:

  • Two non-blocking UDP sockets: a listen socket (faces the local client) and a send socket (faces the remote endpoint).
  • A poll(2)-based main loop with a 1-second timeout drives all I/O; no threads or dynamic allocation occur inside the loop.
  • Packets arriving on the listen socket are forwarded to the connect endpoint; replies arriving on the send socket are forwarded back to the most-recently-seen listen-side source.
  • Optional strict-mode flags limit which sources are accepted on each side.
  • Optional 60-second windowed statistics are printed to stderr.

Targets: Linux x86-64 and macOS/Darwin arm64.

Macro Definition Documentation

◆ DEBUG

#define DEBUG ( debug_level_local,
debug_level_message,
fmt,
... )
Value:
do { \
if ((debug_level_local) >= (debug_level_message)) { \
fprintf(stderr, "%s:%d:%lld:%s(): " fmt "\n", __FILE__, \
__LINE__, (long long)(time(NULL)), __func__, ##__VA_ARGS__); \
} \
} while (0)

Standard debug macro requiring a locally defined debug level. Adapted from the excellent https://github.com/jleffler/soq/blob/master/src/libsoq/debug.h

Note that VA_ARGS is a GCC extension; used for convenience to support DEBUG without format arguments.

◆ EOK

#define EOK   0

Readability: errno value for OK.

◆ ERRNO_IGNORE_CHECK

#define ERRNO_IGNORE_CHECK ( X,
Y )
Value:
((Y) >= 0 && (Y) < MAX_ERRNO && (X)[(Y)] == 1)
#define MAX_ERRNO
Definition udp-redirect.c:72

Check if errno is in declared set

◆ ERRNO_IGNORE_INIT

#define ERRNO_IGNORE_INIT ( X)
Value:
memset((X), 0, MAX_ERRNO * sizeof(unsigned char))

Initialize errno ignore set

◆ ERRNO_IGNORE_SET

#define ERRNO_IGNORE_SET ( X,
Y )
Value:
if ((Y) >= 0 && (Y) < MAX_ERRNO) (X)[(Y)] = 1

Set errno ignore boolean for a specific errno

◆ HRF

#define HRF   HUMAN_READABLE_FORMAT

Shortcut for the above, for shorter printf

◆ HUMAN_READABLE

#define HUMAN_READABLE ( X)
Value:
char int_to_human_char(double value)
Definition udp-redirect.c:1159
double int_to_human_value(double value)
Definition udp-redirect.c:1149

Hardcoded invocations of int to human readable, compatible with HUMAN_READABLE_FORMAT in printf

◆ HUMAN_READABLE_FORMAT

#define HUMAN_READABLE_FORMAT   "%.1lf%c"

Hardcoded printf format for a human readable value made of a float and a char

◆ HUMAN_READABLE_SIZES

#define HUMAN_READABLE_SIZES   { ' ', 'K', 'M', 'G', 'T', 'P', 'E' }

The human readable size suffixes

◆ HUMAN_READABLE_SIZES_COUNT

#define HUMAN_READABLE_SIZES_COUNT   7

The number of human readable size suffixes

◆ MAX_ERRNO

#define MAX_ERRNO   256

Maximum known errno, used to ignore harmless sendto / recvfrom errors

◆ NETWORK_BUFFER_SIZE

#define NETWORK_BUFFER_SIZE   65535

The size of the network buffer used for receiving / sending packets

◆ PARSE_PORT

#define PARSE_PORT ( src,
dest,
label )
Value:
do { \
char *_endptr; \
long _val; \
errno = EOK; \
_val = strtol((src), &_endptr, 10); \
if (errno != EOK || _endptr == (src) || *_endptr != '\0' || _val < 0 || _val > 65535) { \
DEBUG(debug_level, DEBUG_LEVEL_ERROR, "Invalid %s: %s", (label), (src)); \
exit(EXIT_FAILURE); \
} \
(dest) = (int)_val; \
} while (0)
@ DEBUG_LEVEL_ERROR
Error messages.
Definition udp-redirect.c:93
#define EOK
Readability: errno value for OK.
Definition udp-redirect.c:67

Parse a port number from a string (optarg). Sets dest to the parsed value, or prints label and exits on error. Validates that the string is a pure decimal integer in [0, 65535].

◆ STATISTICS_DELAY_SECONDS

#define STATISTICS_DELAY_SECONDS   60

The delay in seconds between displaying statistics

◆ UDP_REDIRECT_VERSION

#define UDP_REDIRECT_VERSION   "2.2.0"

The udp-redirect version

Enumeration Type Documentation

◆ DEBUG_LEVEL

The available debug levels.

Enumerator
DEBUG_LEVEL_ERROR 

Error messages.

DEBUG_LEVEL_INFO 

Informational messages.

DEBUG_LEVEL_VERBOSE 

Verbose messages.

DEBUG_LEVEL_DEBUG 

Debug messages.

Function Documentation

◆ addr_equal()

int addr_equal ( const struct sockaddr_storage * a,
const struct sockaddr_storage * b )
static

Return 1 if both sockaddr_storage values have the same family, address, and port.

Here is the caller graph for this function:

◆ addr_is_unset()

int addr_is_unset ( const struct sockaddr_storage * sa)
static

Return 1 if the address is the zero-initialised "unset" sentinel (ss_family == 0).

Here is the caller graph for this function:

◆ addr_len()

socklen_t addr_len ( const struct sockaddr_storage * sa)
static

Return the wire size of a sockaddr_storage appropriate for its address family.

Here is the caller graph for this function:

◆ addr_port()

int addr_port ( const struct sockaddr_storage * sa)
static

Return the port from a sockaddr_storage in host byte order.

Here is the caller graph for this function:

◆ addr_tostring()

const char * addr_tostring ( const struct sockaddr_storage * sa,
char * buf,
size_t len )
static

Format the address portion of a sockaddr_storage into buf using inet_ntop.

Returns
buf on success, "?" on failure.
Here is the caller graph for this function:

◆ int_to_human_char()

char int_to_human_char ( double value)

Convert a value to human readable (i.e., 1500 = 1.5K). Divide by 1000, not 1024.

Parameters
[in]valueThe value to be converted
Returns
The character (K, M, G, etc.) portion of the human readable value.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ int_to_human_scale()

double int_to_human_scale ( double value,
int * count_out )
static

Shared scaling helper: divides value by 1000 repeatedly until it is <= 1000 or the maximum prefix is reached.

Parameters
[in]valueThe raw value to scale.
[out]count_outThe number of divisions performed (index into the human-readable suffix array).
Returns
The scaled value.
Here is the caller graph for this function:

◆ int_to_human_value()

double int_to_human_value ( double value)

Convert a value to human readable (i.e., 1500 = 1.5K). Divide by 1000, not 1024.

Parameters
[in]valueThe value to be converted
Returns
The numeric portion of the human readable value.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int main ( int argc,
char * argv[] )

Main program function.

Parameters
[in]argcThe count of arguments, including the program name.
[in]argvThe program arguments
Returns
The program return code.

Accept the packet IF:

  • There's no previous endpoint, OR
  • There is a previous endpoint, but we are not in strict mode, OR
  • The previous endpoint matches the current endpoint

Accept the packet IF:

  • The listen socket has received a packet, so we know the endpoint, AND
  • The packet was received from the connect endpoint, OR
  • We are not in strict mode
Here is the call graph for this function:

◆ parse_addr()

int parse_addr ( const char * str,
int port,
struct sockaddr_storage * out )
static

Parse an IPv4 or IPv6 address string into a sockaddr_storage. Sets ss_family, the address field, and the port.

Parameters
[in]strDotted-quad IPv4 or colon-separated IPv6 address string.
[in]portPort number in host byte order.
[out]outZeroed sockaddr_storage to fill.
Returns
AF_INET or AF_INET6 on success, -1 if the string is not a valid address.
Here is the caller graph for this function:

◆ resolve_host()

char * resolve_host ( int debug_level,
const char * host )
static

Resolve a host to an IP address.

Parameters
[in]debug_levelThe debug level to be used for the DEBUG() macro
[in]hostThe host to resolve
Returns
A newly heap-allocated NUL-terminated string containing the resolved IP address (IPv4 dotted-quad or IPv6 colon-separated). The caller owns the buffer and must free() it when done. On failure the function prints a diagnostic and calls exit(EXIT_FAILURE).
Here is the caller graph for this function:

◆ settings_initialize()

void settings_initialize ( struct settings * s)

Initialize settings.

Parameters
[out]sThe settings structure to initialize.
Here is the caller graph for this function:

◆ socket_setup()

int socket_setup ( const int debug_level,
const char * desc,
const char * xaddr,
const int xport,
const char * xif,
int xfamily,
struct sockaddr_storage * xsock_name )
static

Creates a UDP socket on the specified address, port and interface, returning the socket and the socket name (if either arguments were NULL or 0).

Parameters
[in]debug_levelThe debug level to be used for the DEBUG() macro
[in]descThe caller description, added to debug messages
[in]xaddrThe address for the socket to be created, or NULL for ANY
[in]xportThe port for the socket to be created, or 0 for random (decided by bind())
[in]xifThe OS interface name to bind to, or NULL for all interfaces.
[in]xfamilyAF_INET or AF_INET6; used only when xaddr is NULL.
[out]xsock_nameThe name of the socket created.
Returns
The socket file descriptor as integer.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ statistics_display()

void statistics_display ( int debug_level,
struct statistics * st,
time_t now )

Display the stored statistics

Parameters
[in]debug_levelThe debug level to be used for the DEBUG() macro
[in]stThe statistics structure
[in]nowThe current time
Here is the caller graph for this function:

◆ statistics_initialize()

void statistics_initialize ( struct statistics * st)

Initialize statistics.

Parameters
[out]stThe statistics structure to initialize.
Here is the caller graph for this function:

◆ usage()

void usage ( const char * argv0,
const char * message )

Displays the program usage and exit with error.

Parameters
[in]argv0The program name as started, e.g., /usr/local/bin/udp-redirect
[in]messageThe error message, or NULL
Here is the caller graph for this function:

Variable Documentation

◆ longopts

struct option longopts[]
static

Command line options.