mv to codeberg

This commit is contained in:
2025-12-01 17:16:30 +01:00
parent 35390269ff
commit 320a9eedb7
23 changed files with 2 additions and 3240 deletions

View File

@@ -1,32 +0,0 @@
matrix:
platform:
- linux/amd64
labels:
platform: ${platform}
steps:
build:
when:
event: [push]
image: alpine:latest
commands:
- apk update
- apk add --no-cache bash build-base gdb pkgconfig meson ninja perl bind-tools
- meson setup --reconfigure build
- ninja -C build
test:
when:
event: [push]
image: alpine:latest
commands:
- apk update
- apk add --no-cache bash bind-tools
- build/udpxd -l 127.0.0.1:53 -t 8.8.8.8:53 &
- dig +nocmd +noall +answer google.de a @127.0.0.1
- killall udpxd
#- build/udpxd -l [::1]:53 -t [2001:4860:4860::8888]:53 &
#- dig +nocmd +noall +answer google.de a @::1
#- killall udpxd

View File

@@ -1,54 +0,0 @@
#!/bin/bash
# This is my own simple codeberg generic releaser. It takes to
# binaries to be uploaded as arguments and takes every other args from
# env. Works on tags or normal commits (push), tags must start with v.
set -e
die() {
echo $*
exit 1
}
if test -z "$DEPLOY_TOKEN"; then
die "token DEPLOY_TOKEN not set"
fi
git fetch --all
# determine current tag or commit hash
version="$CI_COMMIT_TAG"
previous=""
log=""
if test -z "$version"; then
version="${CI_COMMIT_SHA:0:6}"
log=$(git log -1 --oneline)
else
previous=$(git tag -l | grep -E "^v" | tac | grep -A1 "$version" | tail -1)
log=$(git log -1 --oneline "${previous}..${version}" | sed 's|^|- |g')
fi
# release body
printf "# Changes\n\n %s\n" "$log" > body.txt
# create the release
https --ignore-stdin --check-status -b -A bearer -a "$DEPLOY_TOKEN" POST \
"https://codeberg.org/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/releases" \
tag_name="$version" name="Release $version" body=@body.txt > release.json
# we need the id to upload files
ID=$(jq -r .id < release.json)
if test -z "$ID"; then
cat release.json
die "failed to create release"
fi
# actually upload
for file in "$@"; do
https --ignore-stdin --check-status -A bearer -a "$DEPLOY_TOKEN" -f POST \
"https://codeberg.org/api/v1/repos/${CI_REPO_OWNER}/${CI_REPO_NAME}/releases/$ID/assets" \
"name=${file}" "attachment@${file}"
done

View File

@@ -1,31 +0,0 @@
# build release
labels:
platform: linux/amd64
steps:
compile:
when:
event: [tag,manual]
image: alpine:latest
commands:
- apk update
- apk add --no-cache bash build-base gdb pkgconfig meson ninja perl git
- meson setup --reconfigure --prefer-static -Dc_link_args="-static -ldl" --buildtype=release build
- ninja -C build
- meson dist -C build --formats xztar,gztar,zip --allow-dirty
- file build/udpxd
- mv build/udpxd udpxd-linux-amd64-$CI_COMMIT_TAG
- mv build/meson-dist/* .
release:
image: alpine:latest
when:
event: [tag,manual]
environment:
DEPLOY_TOKEN:
from_secret: DEPLOY_TOKEN
commands:
- apk update
- apk add --no-cache bash httpie jq git
- .woodpecker/release.sh udpxd-*

View File

@@ -1,54 +0,0 @@
#
# This file is part of udpxd.
#
# Copyright (C) 2015-2016 T.v.Dein.
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# You can contact me by mail: <tom AT vondein DOT org>.
.PHONY: all clean man install deprecation
# warning: do not set -O to 2, see TODO
CFLAGS = -Wall -Wextra -Werror -O1 -g
LDFLAGS=
OBJS = host.o client.o net.o udpxd.o log.o
DST = udpxd
PREFIX = /usr/local
UID = root
GID = 0
MAN = udpxd.1
all: deprecation $(DST)
$(DST): deprecation $(OBJS)
$(CC) $(OBJS) -o $(DST)
%.o: deprecation %.c
$(CC) -c $(CFLAGS) $*.c -o $*.o
clean: deprecation
rm -rf *.o $(DST) build .cache
man: deprecation
pod2man udpxd.pod > udpxd.1
install: deprecation $(DST)
install -d -o $(UID) -g $(GID) $(PREFIX)/sbin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(DST) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(MAN) $(PREFIX)/man/man1/
deprecation:
@echo "+++ DEPRECATION NOTE: Use meson & ninja to build please! +++"

View File

@@ -1,6 +1,8 @@
[![status-badge](https://ci.codeberg.org/api/badges/15646/status.svg)](https://ci.codeberg.org/repos/15646) [![status-badge](https://ci.codeberg.org/api/badges/15646/status.svg)](https://ci.codeberg.org/repos/15646)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/udpxd/raw/branch/main/LICENSE) [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/udpxd/raw/branch/main/LICENSE)
> [!CAUTION]
> This software is now being maintained on [Codeberg](https://codeberg.org/scip/udpxd/).
## UDPXD - A general purpose UDP relay/port forwarder/proxy ## UDPXD - A general purpose UDP relay/port forwarder/proxy

7
TODO
View File

@@ -1,7 +0,0 @@
MUST:
- if compiled with -O2, gcc mangles the dst sockaddr_in pointers in some weird ways
MAYBE:
- ctrl client to view current "sessions", refresh etc
- config file
- support multiple different setups with one process

View File

@@ -1,82 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "client.h"
#include "log.h"
void client_del(client_t *client) {
HASH_DEL(clients, client);
}
void client_add(client_t *client) {
HASH_ADD_INT(clients, socket, client);
}
client_t *client_find_fd(int fd) {
client_t *client = NULL;
HASH_FIND_INT(clients, &fd, client);
return client; /* maybe NULL! */
}
client_t *client_find_src(host_t *src) {
client_t *current = NULL;
client_iter(clients, current) {
if(strcmp(current->src->ip, src->ip) == 0 && current->src->port == src->port)
return current;
}
return NULL;
}
void client_seen(client_t *client) {
client->lastseen = (long)time(0);
}
client_t *client_new(int fd, host_t *src, host_t *dst) {
client_t *client = malloc(sizeof(client_t));
client->socket = fd;
client->src = src;
client->dst = dst;
client_seen(client);
return client;
}
void client_close(client_t *client) {
client_del(client);
close(client->socket);
host_clean(client->src);
host_clean(client->dst);
free(client);
}
void client_clean(int asap) {
uint32_t now = (long)time(0);
uint32_t diff;
client_t *current;
client_iter(clients, current) {
diff = now - current->lastseen;
if(diff >= MAXAGE || asap) {
verbose("closing socket %s:%d for client %s:%d (aged out after %d seconds)\n",
current->src->ip, current->src->port, current->dst->ip, current->dst->port, MAXAGE);
client_close(current);
}
}
}

View File

@@ -1,87 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#ifndef _HAVE_CLIENT_H
#define _HAVE_CLIENT_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef byte
typedef uint8_t byte;
#endif
#include "uthash.h"
#include "host.h"
#define MAXAGE 30 /* seconds after which to close outgoing sockets and forget client src */
struct _client_t {
int socket; /* bind socket for outgoing traffic */
host_t *src; /* client src (ip+port) from incoming socket */
host_t *dst; /* client dst (ip+port) to outgoing socket */
uint64_t lastseen; /* when did we recv last time from it */
UT_hash_handle hh;
};
typedef struct _client_t client_t;
extern client_t *clients;
extern int VERBOSE;
extern int FORKED;
/* wrapper for HASH_ITER */
/** Iterate over the list of clients.
Sample use:
@code
client_t *current = NULL;
client_iter(clientlist, current) {
dosomething(current)
}
@endcode
*/
#define client_iter(clients, client) \
client_t *__c = NULL; \
HASH_ITER(hh, clients, client, __c)
void client_del(client_t *client);
void client_add(client_t *client);
void client_seen(client_t *client);
void client_close(client_t *client);
void client_clean(int asap);
client_t *client_find_fd(int fd);
client_t *client_find_src(host_t *src);
client_t *client_new(int fd, host_t *src, host_t *dst);
#endif

173
host.c
View File

@@ -1,173 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "host.h"
/* fill a generic struct depending of what we already know,
which is easier to pass between functions,
maybe v4 or v6, filled from existing structs or from strings,
which create the sockaddr* structs */
host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 *v6) {
host_t *host = malloc(sizeof(host_t));
host->sock = NULL;
host->is_v6 = 0;
host->port = port;
if(ip != NULL) {
if(is_v6(ip)) {
struct sockaddr_in6 *tmp = malloc(sizeof(struct sockaddr_in6));
memset(tmp, 0, sizeof(struct sockaddr_in6));
inet_pton(AF_INET6, ip, (struct in6_addr*)&tmp->sin6_addr);
tmp->sin6_family = AF_INET6;
tmp->sin6_port = htons( port );
unsigned int scope = get_v6_scope(ip);
if (is_linklocal((struct in6_addr*)&tmp->sin6_addr))
tmp->sin6_scope_id = scope;
else
tmp->sin6_scope_id = 0;
host->is_v6 = 1;
host->sock = (struct sockaddr*)tmp;
host->size = sizeof(struct sockaddr_in6);
if(tmp->sin6_scope_id != 0) {
host->ip = malloc(INET6_ADDRSTRLEN + 9); /* plus [ % ] \0 , scope*/
sprintf(host->ip, "[%s%%%d]", ip, scope);
}
else {
host->ip = malloc(INET6_ADDRSTRLEN + 3); /* plus [ ] \0 */
sprintf(host->ip, "[%s]", ip);
}
}
else {
struct sockaddr_in *tmp = malloc(sizeof(struct sockaddr_in));
memset(tmp, 0, sizeof(struct sockaddr_in));
tmp->sin_family = AF_INET;
tmp->sin_addr.s_addr = inet_addr( ip );
tmp->sin_port = htons( port );
host->sock = (struct sockaddr*)tmp;
host->size = sizeof(struct sockaddr_in);
host->ip = malloc(INET_ADDRSTRLEN+1);
memcpy(host->ip, ip, INET_ADDRSTRLEN);
}
}
else if(v4 != NULL) {
struct sockaddr_in *tmp = malloc(sizeof(struct sockaddr_in));
memcpy(tmp, v4, sizeof(struct sockaddr_in));
host->ip = malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, (struct in_addr *)&tmp->sin_addr, host->ip, INET_ADDRSTRLEN);
host->port = ntohs(tmp->sin_port);
host->sock = (struct sockaddr*)tmp;
host->size = sizeof(struct sockaddr_in);
}
else if(v6 != NULL) {
struct sockaddr_in6 *tmp = malloc(sizeof(struct sockaddr_in6));
memcpy(tmp, v6, sizeof(struct sockaddr_in6));
char *myip = malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, (struct in6_addr *)&tmp->sin6_addr, myip, INET6_ADDRSTRLEN);
host->port = ntohs(tmp->sin6_port);
host->sock = (struct sockaddr*)tmp;
host->is_v6 = 1;
host->size = sizeof(struct sockaddr_in6);
if(tmp->sin6_scope_id != 0) {
host->ip = malloc(INET6_ADDRSTRLEN + 9); /* plus [ % ] \0 , scope*/
sprintf(host->ip, "[%s%%%d]", myip, tmp->sin6_scope_id);
}
else {
host->ip = malloc(INET6_ADDRSTRLEN + 3); /* plus [ ] \0 */
sprintf(host->ip, "[%s]", myip);
}
free(myip);
}
else {
fprintf(stderr, "call invalid!\n");
exit(1); /* shall not happen */
}
return host;
}
int is_v6(char *ip) {
char *IS = strchr(ip, ':');
if(IS == NULL)
return 0;
else
return 1;
}
/* via http://stackoverflow.com/questions/13504934/binding-sockets-to-ipv6-addresses
return the interface index (aka scope) of an ipv6 address, which is required
in order to bind to it.
*/
unsigned get_v6_scope(const char *ip){
struct ifaddrs *addrs, *addr;
char ipAddress[NI_MAXHOST];
uint32_t scope=0;
int i;
// walk over the list of all interface addresses
getifaddrs(&addrs);
for(addr=addrs;addr;addr=addr->ifa_next){
if (addr->ifa_addr && addr->ifa_addr->sa_family==AF_INET6){ // only interested in ipv6 ones
getnameinfo(addr->ifa_addr,sizeof(struct sockaddr_in6),ipAddress,sizeof(ipAddress),NULL,0,NI_NUMERICHOST);
// result actually contains the interface name, so strip it
for(i=0;ipAddress[i];i++){
if(ipAddress[i]=='%'){
ipAddress[i]='\0';
break;
}
}
// if the ip matches, convert the interface name to a scope index
if(strcmp(ipAddress,ip)==0){
scope=if_nametoindex(addr->ifa_name);
break;
}
}
}
freeifaddrs(addrs);
return scope;
}
/* this is the contents of the makro IN6_IS_ADDR_LINKLOCAL,
which doesn't compile, when used directly, for whatever reasons */
int is_linklocal(struct in6_addr *a) {
return ((a->s6_addr[0] == 0xfe) && ((a->s6_addr[1] & 0xc0) == 0x80));
}
void host_dump(host_t *host) {
fprintf(stderr, "host - ip: %s\n", host->ip);
fprintf(stderr, " port: %d\n", host->port);
fprintf(stderr, " isv6: %d\n", host->is_v6);
fprintf(stderr, " size: %ld\n", (long int)host->size);
fprintf(stderr, " src: %p\n", host->sock);
}
void host_clean(host_t *host) {
free(host->sock);
free(host->ip);
free(host);
}

56
host.h
View File

@@ -1,56 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#ifndef _HAVE_HOST_H
#define _HAVE_HOST_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h> // if_nametoindex()
#include <ifaddrs.h>
struct _host_t {
int is_v6;
struct sockaddr *sock;
size_t size;
char *ip;
int port;
};
typedef struct _host_t host_t;
unsigned get_v6_scope(const char *ip);
int is_linklocal(struct in6_addr *a);
host_t *get_host(char *ip, int port, struct sockaddr_in *v4, struct sockaddr_in6 *v6);
int is_v6(char *ip);
void host_dump(host_t *host);
void host_clean(host_t *host);
#endif

48
log.c
View File

@@ -1,48 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "log.h"
void verbose(const char * fmt, ...) {
if(VERBOSE) {
char *msg = NULL;
va_list ap;
va_start(ap, fmt);
if(vasprintf(&msg, fmt, ap) >= 0) {
if(FORKED) {
syslog(LOG_INFO, "%s", msg);
}
else {
fprintf(stderr, "%s", msg);
}
free(msg);
va_end(ap);
}
else {
fprintf(stderr, "Fatal: could not store log message!\n");
exit(1);
}
}
}

40
log.h
View File

@@ -1,40 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#ifndef _HAVE_LOG_H
#define _HAVE_LOG_H
#define _WITH_DPRINTF
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <syslog.h>
extern int VERBOSE;
extern int FORKED;
void verbose(const char * fmt, ...);
#endif

View File

@@ -1,89 +0,0 @@
# -*-python-*-
project(
'udpxd',
'c',
license: 'GPL',
version: '0.0.4',
meson_version: '>=1.3',
default_options: [
'warning_level=2',
'werror=true',
],
)
add_project_arguments(
[
'-Wno-unused-parameter',
'-Wno-unused-result',
'-Wno-missing-braces',
'-Wno-format-zero-length',
'-Wvla',
'-Wno-sign-compare',
'-Wno-narrowing',
'-Wno-stringop-truncation'
],
language: 'c',
)
c = meson.get_compiler('c')
conf = configuration_data()
# check for funcs.
foreach func : ['getopt', 'malloc', 'fprintf', 'strncpy', 'strlen', 'strtok', 'strchr', 'signal',
'select', 'free', 'perror', 'getsockname', 'setegid', 'seteuid', 'syslog',
'va_start', 'va_end', 'inet_ntop', 'getifaddrs', 'getnameinfo', 'ntohs', 'memcpy', 'memset', 'sprintf' ]
conf.set('HAVE_'+func.to_upper(), c.has_function(func, prefix : '#include <unistd.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <sys/socket.h>\n#include <syslog.h>\n#include <stdarg.h>\n#include <arpa/inet.h>\n#include <sys/types.h>\n#include <ifaddrs.h>\n#include <netdb.h>\n'))
endforeach
# check commandline options
prefix = get_option('prefix')
if get_option('buildtype') == 'debug'
conf.set('DEBUG', '1')
endif
# setup conf map
version = '@0@'.format(meson.project_version())
conf.set('prefix', prefix)
conf.set('VERSION', version)
# write out the config header
m = configure_file(
input : 'platform.h.in',
output : 'platform.h',
configuration : conf,
)
# code
udpxd_sources = files(
'client.c',
'host.c',
'log.c',
'net.c',
'udpxd.c'
)
executable(
'udpxd',
[udpxd_sources],
install: true
)
# build manual page
pod2man = find_program('pod2man', native: true)
if pod2man.found()
res = run_command(pod2man.full_path(), 'udpxd.pod', 'udpxd.1', check:true)
if res.returncode() == 0
install_man('udpxd.1')
endif
endif

View File

@@ -1 +0,0 @@
# custom build options

447
net.c
View File

@@ -1,447 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "net.h"
#include "client.h"
#include "host.h"
#include "log.h"
/* called each time when the loop restarts to feed select() correctly */
int fill_set(fd_set *fds) {
int max = 0;
client_t *current = NULL;
client_iter(clients, current) {
if (current->socket < (int)FD_SETSIZE) {
if (current->socket > max)
max = current->socket;
FD_SET(current->socket, fds);
}
else {
fprintf(stderr, "skipped client, socket too large!\n");
}
}
return max;
}
/* return file handle ready to read */
int get_sender(fd_set *fds) {
int i = 0;
while(!FD_ISSET(i, fds))
i++;
return i;
}
/* bind to a socket, either for listen() or for outgoing src ip binding */
int bindsocket( host_t *sock_h) {
int fd;
int err = 0;
if(sock_h->is_v6) {
fd = socket( PF_INET6, SOCK_DGRAM, IPPROTO_UDP );
}
else {
fd = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP );
}
if( ! ( fd >= 0 && -1 != bind( fd, (struct sockaddr*)sock_h->sock, sock_h->size ) ) ) {
err = 1;
}
if(err) {
fprintf( stderr, "Cannot bind address ([%s]:%d)\n", sock_h->ip, sock_h->port );
perror(NULL);
return -1;
}
return fd;
}
/*
returns:
-1: error in any case
0: parent not forked (fork disabled)
1: parent after successful fork
2: child after successful fork
*/
int daemonize(char *pidfile) {
if(FORKED) {
// fork
pid_t pid, sid;
FILE *fd;
pid = fork();
if (pid < 0) {
perror("fork error");
return 1;
}
if (pid > 0) {
/* leave parent */
if((fd = fopen(pidfile, "w")) == NULL) {
perror("failed to write pidfile");
return -1;
}
else {
fprintf(fd, "%d\n", pid);
fclose(fd);
}
return 1;
}
/* child */
sid = setsid();
if (sid < 0) {
perror("set sid error");
return 1;
}
umask(0);
openlog("udpxd", LOG_NOWAIT|LOG_PID, LOG_USER);
return 2;
}
return 0;
}
int drop_privileges(char *user, char *chrootdir) {
struct passwd *pw = getpwnam(user);
uid_t me = getuid();
if(!FORKED)
return 0;
if ((chdir("/")) < 0) {
perror("failed to chdir to /");
return 1;
}
if(me == 0) {
/* drop privileges */
if(chroot(chrootdir) != 0) {
perror("failed to chroot");
return 1;
}
if(pw == NULL) {
perror("user not found");
return 1;
}
if(setegid(pw->pw_gid) != 0) {
perror("could not set egid");
return 1;
}
if(setgid(pw->pw_gid) != 0) {
perror("could not set gid");
return 1;
}
if(setuid(pw->pw_uid) != 0) {
perror("could not set uid");
return 1;
}
if(seteuid(pw->pw_uid) != 0) {
perror("could not set euid");
return 1;
}
if (setuid(0) != -1) {
fprintf(stderr, "error, managed to regain root privileges after dropping them!\n");
return 1;
}
}
return 0;
}
int start_listener (char *inip, char *inpt, char *srcip, char *srcpt, char *dstip,
char *dstpt, char *pidfile, char *chrootdir, char *user) {
host_t *listen_h, *dst_h, *bind_h;
int dm = daemonize(pidfile);
switch(dm) {
case -1:
return 1; /* parent, fork error */
break;
case 0:
break; /* parent, not forking */
case 1:
return 0; /* parent, fork ok, leave */
break;
case 2:
break; /* child, fork ok, continue */
}
listen_h = get_host(inip, atoi(inpt), NULL, NULL);
dst_h = get_host(dstip, atoi(dstpt), NULL, NULL);
bind_h = NULL;
if(srcip != NULL) {
bind_h = get_host(srcip, atoi(srcpt), NULL, NULL);
}
else {
if(dst_h->is_v6)
bind_h = get_host("::0", 0, NULL, NULL);
else
bind_h = get_host("0.0.0.0", 0, NULL, NULL);
}
int listen = bindsocket(listen_h);
if(listen == -1)
return 1;
if(VERBOSE) {
verbose("Listening on %s:%s, forwarding to %s:%s",
listen_h->ip, inpt, dst_h->ip, dstpt);
if(srcip != NULL)
verbose(", binding to %s\n", bind_h->ip);
else
verbose("\n");
}
if(drop_privileges(user, chrootdir) != 0) {
host_clean(bind_h);
host_clean(listen_h);
host_clean(dst_h);
return 1;
}
if (dm) {
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
}
main_loop(listen, listen_h, bind_h, dst_h);
host_clean(bind_h);
host_clean(listen_h);
host_clean(dst_h);
closelog();
return 0;
}
/* handle new or known incoming requests */
void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h) {
int len;
unsigned char buffer[MAX_BUFFER_SIZE];
void *src;
client_t *client;
host_t *src_h;
int output;
size_t size = listen_h->size;
src = malloc(size);
len = recvfrom( inside, buffer, sizeof( buffer ), 0,
(struct sockaddr*)src, (socklen_t *)&size );
if(len > 0) {
if(listen_h->is_v6)
src_h = get_host(NULL, 0, NULL, (struct sockaddr_in6 *)src);
else
src_h = get_host(NULL, 0, (struct sockaddr_in *)src, NULL);
/* do we know it ? */
client = client_find_src(src_h);
if(client != NULL) {
/* yes, we know it, send req out via existing bind socket */
verbose("Client %s:%d is known, forwarding %d bytes to %s:%d ",
src_h->ip, src_h->port, len, dst_h->ip, dst_h->port);
verb_prbind(bind_h);
if(sendto(client->socket, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) {
fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port);
perror(NULL);
}
else {
client_seen(client);
}
host_clean(src_h);
}
else {
/* unknown client, open new out socket */
verbose("Client %s:%d is unknown, forwarding %d bytes to %s:%d ",
src_h->ip, src_h->port, len, dst_h->ip, dst_h->port);
verb_prbind(bind_h);
if (bind_h->port)
client_clean(1);
output = bindsocket(bind_h);
if (output >= 0) {
/* send req out */
if(sendto(output, buffer, len, 0, (struct sockaddr*)dst_h->sock, dst_h->size) < 0) {
fprintf(stderr, "unable to forward to %s:%d\n", dst_h->ip, dst_h->port);
perror(NULL);
}
else {
size = listen_h->size;
host_t *ret_h;
if(listen_h->is_v6) {
struct sockaddr_in6 *ret = malloc(size);
getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size);
ret_h = get_host(NULL, 0, NULL, ret);
free(ret);
client = client_new(output, src_h, ret_h);
}
else {
struct sockaddr_in *ret = malloc(size);
getsockname(output, (struct sockaddr*)ret, (socklen_t *)&size);
ret_h = get_host(NULL, 0, ret, NULL);
free(ret);
client = client_new(output, src_h, ret_h);
}
client_add(client);
}
}
else {
host_clean(src_h);
}
}
}
free(src);
}
/* handle answer from the outside */
void handle_outside(int inside, int outside, host_t *outside_h) {
int len;
unsigned char buffer[MAX_BUFFER_SIZE];
void *src;
client_t *client;
size_t size = outside_h->size;
src = malloc(size);
len = recvfrom( outside, buffer, sizeof( buffer ), 0, (struct sockaddr*)src, (socklen_t *)&size );
free(src);
if(len > 0) {
/* do we know it? */
client = client_find_fd(outside);
if(client != NULL) {
/* yes, we know it */
/* FIXME: check src vs. client->src ? */
if(sendto(inside, buffer, len, 0,
(struct sockaddr*)client->src->sock, client->src->size) < 0) {
perror("unable to send back to client"); /* FIXME: add src+port */
client_close(client);
}
}
else {
fprintf(stderr, "weird, no matching client found!\n");
}
}
else {
fprintf(stderr, "weird, recvfrom returned 0 bytes!\n");
}
}
/* stores system specific information, used by longjmp(), see below */
jmp_buf JumpBuffer;
/* runs forever, handles incoming requests on the inside and answers on the outside */
int main_loop(int listensocket, host_t *listen_h, host_t *bind_h, host_t *dst_h) {
int max, sender;
fd_set fds;
/* we want to properly tear down running sessions when interrupted,
int_handler() will be called on INT or TERM signals */
signal(SIGINT, int_handler);
signal(SIGTERM, int_handler);
for(;;) {
/*
Normally returns 0, that is, if it's the first instruction after
entering the loop. However, it will return 1, when called from
longjmp(), which will be called by int_handler() if a SIGINT- or
TERM arrives. In that case we leave the loop, tear down
everything and exit.
*/
if (setjmp(JumpBuffer) == 1) {
break;
}
FD_ZERO(&fds);
max = fill_set(&fds);
FD_SET(listensocket, &fds);
if (listensocket > max)
max = listensocket;
select(max + 1, &fds, NULL, NULL, NULL);
if (FD_ISSET(listensocket, &fds)) {
/* incoming client on the inside, get src, bind output fd, add
to list if known, otherwise just handle it */
handle_inside(listensocket, listen_h, bind_h, dst_h);
}
else {
/* remote answer came in on an output fd, proxy back to the inside */
sender = get_sender(&fds);
handle_outside(listensocket, sender, dst_h);
}
/* close old outputs, if any */
client_clean(0);
}
/* we came here via signal handler, clean up */
close(listensocket);
client_clean(1);
return 0;
}
/*
Handle SIGINT- and TERM, call longjmp(), which jumps right into the
main loop, where it causes the loop to be left.
*/
void int_handler(int sig) {
signal(sig, SIG_IGN);
longjmp(JumpBuffer, 1);
}
void verb_prbind (host_t *bind_h) {
if(VERBOSE) {
if(strcmp(bind_h->ip, "0.0.0.0") != 0 || strcmp(bind_h->ip, "[::0]") != 0) {
verbose("from %s:%d\n", bind_h->ip, bind_h->port);
}
else {
verbose("\n");
}
}
}

74
net.h
View File

@@ -1,74 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#ifndef _HAVE_NET_H
#define _HAVE_NET_H
#include <setjmp.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pwd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "client.h"
#define MAX_BUFFER_SIZE 65535
extern client_t *clients;
extern int VERBOSE;
extern int FORKED;
void handle_inside(int inside, host_t *listen_h, host_t *bind_h, host_t *dst_h);
void handle_outside(int inside, int outside, host_t *outside_h);
int main_loop(int listensocket, host_t *listen_h, host_t *bind_h,
host_t *dst_h);
int start_listener(char *inip, char *inpt, char *srcip, char *srcpt,
char *dstip, char *dstpt, char *pidfile, char *chrootdir,
char *user);
int daemonize(char *pidfile);
int drop_privileges(char *user, char *chrootdir);
int fill_set(fd_set *fds);
int get_sender(fd_set *fds);
int bindsocket(host_t *sock_h);
void int_handler(int sig);
void verb_prbind(host_t *bind_h);
#define _IS_LINK_LOCAL(a) \
do { \
IN6_IS_ADDR_LINKLOCAL(a); \
} while (0)
#endif

View File

@@ -1,101 +0,0 @@
/* platform.h.in. Generated from configure.ac by autoheader. */
#define PACKAGE "udpxd"
#define UDPXD_VERSION "@VERSION@"
#mesondefine HAVE_GETOPT
#ifndef HAVE_GETOPT
error "HAVE_GETOPT not defined"
#endif
#mesondefine HAVE_MALLOC
#ifndef HAVE_MALLOC
error "HAVE_MALLOC not defined"
#endif
#mesondefine HAVE_FPRINTF
#ifndef HAVE_FPRINTF
error "HAVE_FPRINTF not defined"
#endif
#mesondefine HAVE_STRNCPY
#ifndef HAVE_STRNCPY
error "HAVE_STRNCPY not defined"
#endif
#mesondefine HAVE_STRLEN
#ifndef HAVE_STRLEN
error "HAVE_STRLEN not defined"
#endif
#mesondefine HAVE_STRTOK
#ifndef HAVE_STRTOK
error "HAVE_STRTOK not defined"
#endif
#mesondefine HAVE_STRCHR
#ifndef HAVE_STRCHR
error "HAVE_STRCHR not defined"
#endif
#mesondefine HAVE_SIGNAL
#ifndef HAVE_SIGNAL
error "HAVE_SIGNAL not defined"
#endif
#mesondefine HAVE_SELECT
#ifndef HAVE_SELECT
error "HAVE_SELECT not defined"
#endif
#mesondefine HAVE_FREE
#ifndef HAVE_FREE
error "HAVE_FREE not defined"
#endif
#mesondefine HAVE_PERROR
#ifndef HAVE_PERROR
error "HAVE_PERROR not defined"
#endif
#mesondefine HAVE_GETSOCKNAME
#ifndef HAVE_GETSOCKNAME
error "HAVE_GETSOCKNAME not defined"
#endif
#mesondefine HAVE_SETEGID
#ifndef HAVE_SETEGID
error "HAVE_SETEGID not defined"
#endif
#mesondefine HAVE_SETEUID
#ifndef HAVE_SETEUID
error "HAVE_SETEUID not defined"
#endif
#mesondefine HAVE_SYSLOG
#ifndef HAVE_SYSLOG
error "HAVE_SYSLOG not defined"
#endif
#mesondefine HAVE_VA_START
#ifndef HAVE_VA_START
error "HAVE_VA_START not defined"
#endif
#mesondefine HAVE_VA_END
#ifndef HAVE_VA_END
error "HAVE_VA_END not defined"
#endif
#mesondefine HAVE_INET_NTOP
#ifndef HAVE_INET_NTOP
error "HAVE_INET_NTOP not defined"
#endif
#mesondefine HAVE_GETIFADDRS
#ifndef HAVE_GETIFADDRS
error "HAVE_GETIFADDRS not defined"
#endif
#mesondefine HAVE_GETNAMEINFO
#ifndef HAVE_GETNAMEINFO
error "HAVE_GETNAMEINFO not defined"
#endif
#mesondefine HAVE_NTOHS
#ifndef HAVE_NTOHS
error "HAVE_NTOHS not defined"
#endif
#mesondefine HAVE_MEMCPY
#ifndef HAVE_MEMCPY
error "HAVE_MEMCPY not defined"
#endif
#mesondefine HAVE_MEMSET
#ifndef HAVE_MEMSET
error "HAVE_MEMSET not defined"
#endif
#mesondefine HAVE_SPRINTF
#ifndef HAVE_SPRINTF
error "HAVE_SPRINTF not defined"
#endif

301
udpxd.1
View File

@@ -1,301 +0,0 @@
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" Set up some character translations and predefined strings. \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
. ds -- \(*W-
. ds PI pi
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
. ds L" ""
. ds R" ""
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds -- \|\(em\|
. ds PI \(*p
. ds L" ``
. ds R" ''
. ds C`
. ds C'
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is >0, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.\"
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{\
. if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{\
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
. \" fudge factors for nroff and troff
.if n \{\
. ds #H 0
. ds #V .8m
. ds #F .3m
. ds #[ \f1
. ds #] \fP
.\}
.if t \{\
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
. ds #V .6m
. ds #F 0
. ds #[ \&
. ds #] \&
.\}
. \" simple accents for nroff and troff
.if n \{\
. ds ' \&
. ds ` \&
. ds ^ \&
. ds , \&
. ds ~ ~
. ds /
.\}
.if t \{\
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.\}
. \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
. \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
. \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
\{\
. ds : e
. ds 8 ss
. ds o a
. ds d- d\h'-1'\(ga
. ds D- D\h'-1'\(hy
. ds th \o'bp'
. ds Th \o'LP'
. ds ae ae
. ds Ae AE
.\}
.rm #[ #] #H #V #F C
.\" ========================================================================
.\"
.IX Title "UDPXD 1"
.TH UDPXD 1 "2025-12-01" "perl v5.34.0" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.nh
.SH "NAME"
udpxd \- A general purpose UDP relay/port forwarder/proxy
.SH "SYNOPSIS"
.IX Header "SYNOPSIS"
.Vb 1
\& Usage: udpxd [\-lbdfpvhV]
\&
\& Options:
\& \-\-listen \-l <ip:port> listen for incoming requests
\& \-\-bind \-b <ip[:port]> bind ip used for outgoing requests
\& specify port for promiscuous mode
\& \-\-to \-t <ip:port> destination to forward requests to
\& \-\-daemon \-d daemon mode, fork into background
\& \-\-pidfile \-p <file> pidfile, default: /var/run/udpxd.pid
\& \-\-user \-u <user> run as user (only in daemon mode)
\& \-\-chroot \-c <path> chroot to <path> (only in daemon mode)
\& \-\-help \-h \-? print help message
\& \-\-version \-V print program version
\& \-\-verbose \-v enable verbose logging
.Ve
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
udpxd can be used to forward or proxy \s-1UDP\s0 client traffic
to another port on another system. It also supports binding
to a specific ip address which will be used as the source
for outgoing packets.
.PP
It listens on the ip address and port specified with \fB\-l\fR
and waits for incoming udp packets. If one arrives, it sends
it to the destination specified with \fB\-t\fR. Responses will
be sent back accordingly.
.PP
If \fB\-b\fR has not been specified, udpxd uses the operating
systems default (e.g. routing) as the source where it sends
requests packets out. If \fB\-b\fR has been specified, then it
binds to the given ip address and uses this as the source
address.
.PP
In any case, udpxd behaves like a proxy. The receiving end
(\fB\-t\fR) only sees the source ip address of the outgoing
interface of the system running udpxd or the address specified
with \fB\-b\fR.
.PP
The options \fB\-l\fR and \fB\-t\fR are mandatory.
.PP
If the option \fB\-d\fR has been specified, udpxd forks into
the background and becomes a daemon. It writes it pidfile to
\&\f(CW\*(C`/var/run/udpxd.pid\*(C'\fR, which can be changed with the \fB\-p\fR
option. If started as root, it also drops privileges to the
user \f(CW\*(C`nobody\*(C'\fR or the user specified with \fB\-u\fR and chroots
to \f(CW\*(C`/var/empty\*(C'\fR or the directory specified with \fB\-c\fR. udpxd
will log to syslog facility user.info if \fB\-v\fR is specified and
if running in daemon mode.
.PP
\&\fBCaution: if not running in daemon mode, udpxd does not drop
its privileges and will continue to run as root (if started as
root).\fR
.PP
Udpxd supports ip version 4 and 6, it doesn't support hostnames,
\&\fB\-l\fR, \fB\-t\fR and \fB\-b\fR must be ip addresses. In order to specify an ipv6
address and a port, use:
.PP
.Vb 1
\& \-l [::1]:53
.Ve
.PP
that is, surround the ipv6 address with brackets.
.PP
Port forwardings can be mixed:
.PP
.Vb 6
\& listen | forward to
\& \-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-
\& ipv4 | ipv4
\& ipv6 | ipv4
\& ipv4 | ipv6
\& ipv6 | ipv6
.Ve
.SH "EXAMPLES"
.IX Header "EXAMPLES"
Let's say you operate a multihomed unix system named 'foo'
with two interfaces: eth0 on the inside, eth1 on the outside:
.PP
.Vb 3
\& foo:
\& eth0: 192.168.1.1
\& eth1: 10.0.0.1
.Ve
.PP
And let's say, you have a client in network 10.0.0.0/24 who whiches to reach
an ntp server in network 192.168.1.0/24; and you dont operate a
firewall, nat or routing on 'foo'. Run udpxd like this:
.PP
.Vb 1
\& udpxd \-l 10.0.0.1:123 \-t 192.168.1.199:123
.Ve
.PP
Now, if a client with the source ip address 10.0.0.110 sends
a ntp request to 10.0.0.1:123, udpxd will forward that
packet to 192.168.1.199:123 with the source ip address
192.168.1.1 (because this is where the route points to: eth0).
Responses from the ntp server will reach udpxd, which in turn
sends them back to the client, where they arrive with the source
address (and port) where udpxd is listening.
.PP
As you can see, udpxd can be used to implement hiding nat for
udp services in user space.
.PP
Another example would be, if 'foo' has multiple ip addresses
on eth0 (aliases) and you don't want to use the primary address
of the interface for outgoing packets.
.PP
.Vb 3
\& foo, again:
\& eth0: 192.168.1.1,192.168.1.45
\& eth0: 10.0.0.1
.Ve
.PP
In order to use 192.168.1.45 as the source ip address, use the
\&\fB\-b\fR parameter:
.PP
.Vb 1
\& udpxd \-l 10.0.0.1:123 \-t 192.168.1.199:123 \-b 192.168.1.45
.Ve
.PP
In this case for the client everything looks as before, but the
ntp server on the other end will see ntp requests coming from
192.168.1.45 instead.
.PP
Here we listen on the ip v6 loopback address and forward traffic
to another ip v6 destination address:
.PP
.Vb 1
\& udpxd \-l [::1]:53 \-t [2001:4860:4860::8888]:53
.Ve
.PP
Or, we could listen on an ip v4 address and forward to an ip v6
address:
.PP
.Vb 1
\& udpxd \-l 192.168.1.1:53 \-t [2001:4860:4860::8888]:53
.Ve
.SH "FILES"
.IX Header "FILES"
\&\fB/var/run/udpxd.pid\fR: created if running in daemon mode (\fB\-d\fR).
.SH "BUGS"
.IX Header "BUGS"
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
<https://codeberg.org/scip/udpxd/issues>.
.SH "LICENSE"
.IX Header "LICENSE"
This software is licensed under the \s-1GNU GENERAL PUBLIC LICENSE\s0 version 3.
.PP
Copyright (c) 2015\-2017 by T. v. Dein.
.PP
This software uses \fButhash\fR (bundled), which is
Copyright (c) 2003\-2013 by Troy D. Hanson.
.SH "AUTHORS"
.IX Header "AUTHORS"
T.v.Dein \fBtom \s-1AT\s0 vondein \s-1DOT\s0 org\fR

250
udpxd.c
View File

@@ -1,250 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#include "udpxd.h"
#include "net.h"
#include "client.h"
/* global client list */
client_t *clients = NULL;
int VERBOSE = 0;
int FORKED = 0;
/* parse ip:port */
int parse_ip(char *src, char *ip, char *pt) {
char *ptr = NULL;
if (strchr(optarg, '[')) {
/* v6 */
ptr = strtok(&src[1], "]");
if(strlen(ptr) > INET6_ADDRSTRLEN) {
fprintf(stderr, "ip v6 address is too long!\n");
return 1;
}
strncpy(ip, ptr, strlen(ptr)+1);
ptr = strtok(NULL, "]");
if(ptr)
ptr = &ptr[1]; /* remove : */
}
else if(strchr(optarg, ':')) {
/* v4 */
ptr = strtok(src, ":");
if(strlen(ptr) > INET_ADDRSTRLEN) {
fprintf(stderr, "ip v4 address is too long!\n");
return 1;
}
strncpy(ip, ptr, strlen(ptr)+1);
ptr = strtok(NULL, ":");
}
else {
fprintf(stderr, "Invalid ip/port specification!\n");
return 1;
}
if(ptr != NULL) {
/* got a port */
if(strlen(ptr) > 5) {
fprintf(stderr, "port is too long!\n");
return 1;
}
else {
if(atoi(ptr) > 65535) {
fprintf(stderr, "maximum port number possible: 65535!\n");
return 1;
}
strncpy(pt, ptr, strlen(ptr)+1);
}
}
else {
fprintf(stderr, "Port is missing!\n");
return 1;
}
return 0;
}
void usage() {
fprintf(stderr,
"Usage: udpxd [-lbdfpvhV]\n\n"
"Options:\n"
"--listen -l <ip:port> listen for incoming requests\n"
"--bind -b <ip[:port]> bind ip used for outgoing requests\n"
" specify port for promiscuous mode\n"
"--to -t <ip:port> destination to forward requests to\n"
"--daemon -d daemon mode, fork into background\n"
"--pidfile -p <file> pidfile, default: /var/run/udpxd.pid\n"
"--user -u <user> run as user (only in daemon mode)\n"
"--chroot -c <path> chroot to <path> (only in daemon mode)\n"
"--help -h -? print help message\n"
"--version -V print program version\n"
"--verbose -v enable verbose logging\n\n"
"Options -l and -t are mandatory.\n\n"
"This is udpxd version %s.\n", UDPXD_VERSION
);
}
int main ( int argc, char* argv[] ) {
int opt, err;
char *inip, *inpt, *srcip, *srcpt, *dstip, *dstpt;
char pidfile[MAX_BUFFER_SIZE];
char user[128];
char chroot[MAX_BUFFER_SIZE];
err = 0;
static struct option longopts[] = {
{ "listen", required_argument, NULL, 'l' },
{ "bind", required_argument, NULL, 'b' },
{ "to", required_argument, NULL, 't' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
{ "verbose", no_argument, NULL, 'v' },
{ "daemon", no_argument, NULL, 'd' },
{ "pidfile", required_argument, NULL, 'p' },
{ "user", required_argument, NULL, 'u' },
{ "chroot", required_argument, NULL, 'c' },
};
if( argc < 2 ) {
usage();
return 1;
}
srcip = srcpt = dstip = inip = dstpt = inpt = NULL;
/* set defaults */
strncpy(pidfile, "/var/run/udpxd.pid", 19);
strncpy(user, "nobody", 7);
strncpy(chroot, "/var/empty", 11);
while ((opt = getopt_long(argc, argv, "l:b:t:u:c:vdVh?", longopts, NULL)) != -1) {
switch (opt) {
case 'V':
fprintf(stderr, "This is %s version %s\n", argv[0], UDPXD_VERSION);
return 1;
break;
case 'd':
FORKED = 1;
break;
case 'h':
case '?':
usage();
return 1;
break;
case 'v':
VERBOSE = 1;
break;
case 'l':
inip = malloc(INET6_ADDRSTRLEN+1);
inpt = malloc(6);
if (parse_ip(optarg, inip, inpt) != 0) {
fprintf(stderr, "Parameter -l has the format <ip-address:port>!\n");
err = 1;
}
break;
case 't':
dstip = malloc(INET6_ADDRSTRLEN+1);
dstpt = malloc(6);
if (parse_ip(optarg, dstip, dstpt) != 0) {
fprintf(stderr, "Parameter -t has the format <ip-address:port>!\n");
err = 1;
}
break;
case 'b':
srcip = malloc(INET6_ADDRSTRLEN+1+5); // +5 is for port
srcpt = malloc(6);
if(strlen(optarg) > INET6_ADDRSTRLEN+5) {
fprintf(stderr, "Bind ip address is too long!\n");
err = 1;
}
else {
if (strchr(optarg, ':') == NULL || parse_ip(optarg, srcip, srcpt) != 0) {
strncpy(srcip, optarg, INET6_ADDRSTRLEN+5);
srcip[INET6_ADDRSTRLEN+5-1] = '\0';
strncpy(srcpt, "0", 2);
}
}
break;
case 'p':
strncpy(pidfile, optarg, MAX_BUFFER_SIZE);
pidfile[MAX_BUFFER_SIZE-1] = '\0';
break;
case 'u':
strncpy(user, optarg, 128);
user[128-1] = '\0';
break;
case 'c':
strncpy(chroot, optarg, MAX_BUFFER_SIZE);
chroot[MAX_BUFFER_SIZE-1] = '\0';
break;
default:
usage();
return 1;
break;
}
}
if(inip == NULL) {
fprintf(stderr, "-l parameter is required!\n");
usage();
err = 1;
}
if(dstip == NULL) {
fprintf(stderr, "-t parameter is required!\n");
usage();
err = 1;
}
if(srcip != NULL && dstip != NULL) {
if(is_v6(srcip) != is_v6(dstip)) {
fprintf(stderr, "Bind ip and destination ip must be both v4 or v6 and can't be mixed!\n");
err = 1;
}
}
if(! err) {
err = start_listener (inip, inpt, srcip, srcpt, dstip, dstpt, pidfile, chroot, user);
}
if(srcip != NULL)
free(srcip);
if(srcpt != NULL)
free(srcpt);
if(dstip != NULL)
free(dstip);
if(inip != NULL)
free(inip);
if(inpt != NULL)
free(inpt);
if(dstpt != NULL)
free(dstpt);
return err;
}

44
udpxd.h
View File

@@ -1,44 +0,0 @@
/*
This file is part of udpxd.
Copyright (C) 2015-2016 T.v.Dein.
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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact me by mail: <tom AT vondein DOT org>.
*/
#ifndef _HAVE_UDPXD_H
#define _HAVE_UDPXD_H
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "platform.h"
void usage();
int parse_ip(char *src, char *ip, char *pt);
#endif

153
udpxd.pod
View File

@@ -1,153 +0,0 @@
=head1 NAME
udpxd - A general purpose UDP relay/port forwarder/proxy
=head1 SYNOPSIS
Usage: udpxd [-lbdfpvhV]
Options:
--listen -l <ip:port> listen for incoming requests
--bind -b <ip[:port]> bind ip used for outgoing requests
specify port for promiscuous mode
--to -t <ip:port> destination to forward requests to
--daemon -d daemon mode, fork into background
--pidfile -p <file> pidfile, default: /var/run/udpxd.pid
--user -u <user> run as user (only in daemon mode)
--chroot -c <path> chroot to <path> (only in daemon mode)
--help -h -? print help message
--version -V print program version
--verbose -v enable verbose logging
=head1 DESCRIPTION
udpxd can be used to forward or proxy UDP client traffic
to another port on another system. It also supports binding
to a specific ip address which will be used as the source
for outgoing packets.
It listens on the ip address and port specified with B<-l>
and waits for incoming udp packets. If one arrives, it sends
it to the destination specified with B<-t>. Responses will
be sent back accordingly.
If B<-b> has not been specified, udpxd uses the operating
systems default (e.g. routing) as the source where it sends
requests packets out. If B<-b> has been specified, then it
binds to the given ip address and uses this as the source
address.
In any case, udpxd behaves like a proxy. The receiving end
(B<-t>) only sees the source ip address of the outgoing
interface of the system running udpxd or the address specified
with B<-b>.
The options B<-l> and B<-t> are mandatory.
If the option B<-d> has been specified, udpxd forks into
the background and becomes a daemon. It writes it pidfile to
C</var/run/udpxd.pid>, which can be changed with the B<-p>
option. If started as root, it also drops privileges to the
user C<nobody> or the user specified with B<-u> and chroots
to C</var/empty> or the directory specified with B<-c>. udpxd
will log to syslog facility user.info if B<-v> is specified and
if running in daemon mode.
B<Caution: if not running in daemon mode, udpxd does not drop
its privileges and will continue to run as root (if started as
root).>
Udpxd supports ip version 4 and 6, it doesn't support hostnames,
B<-l>, B<-t> and B<-b> must be ip addresses. In order to specify an ipv6
address and a port, use:
-l [::1]:53
that is, surround the ipv6 address with brackets.
Port forwardings can be mixed:
listen | forward to
-------+-----------
ipv4 | ipv4
ipv6 | ipv4
ipv4 | ipv6
ipv6 | ipv6
=head1 EXAMPLES
Let's say you operate a multihomed unix system named 'foo'
with two interfaces: eth0 on the inside, eth1 on the outside:
foo:
eth0: 192.168.1.1
eth1: 10.0.0.1
And let's say, you have a client in network 10.0.0.0/24 who whiches to reach
an ntp server in network 192.168.1.0/24; and you dont operate a
firewall, nat or routing on 'foo'. Run udpxd like this:
udpxd -l 10.0.0.1:123 -t 192.168.1.199:123
Now, if a client with the source ip address 10.0.0.110 sends
a ntp request to 10.0.0.1:123, udpxd will forward that
packet to 192.168.1.199:123 with the source ip address
192.168.1.1 (because this is where the route points to: eth0).
Responses from the ntp server will reach udpxd, which in turn
sends them back to the client, where they arrive with the source
address (and port) where udpxd is listening.
As you can see, udpxd can be used to implement hiding nat for
udp services in user space.
Another example would be, if 'foo' has multiple ip addresses
on eth0 (aliases) and you don't want to use the primary address
of the interface for outgoing packets.
foo, again:
eth0: 192.168.1.1,192.168.1.45
eth0: 10.0.0.1
In order to use 192.168.1.45 as the source ip address, use the
B<-b> parameter:
udpxd -l 10.0.0.1:123 -t 192.168.1.199:123 -b 192.168.1.45
In this case for the client everything looks as before, but the
ntp server on the other end will see ntp requests coming from
192.168.1.45 instead.
Here we listen on the ip v6 loopback address and forward traffic
to another ip v6 destination address:
udpxd -l [::1]:53 -t [2001:4860:4860::8888]:53
Or, we could listen on an ip v4 address and forward to an ip v6
address:
udpxd -l 192.168.1.1:53 -t [2001:4860:4860::8888]:53
=head1 FILES
B</var/run/udpxd.pid>: created if running in daemon mode (B<-d>).
=head1 BUGS
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
L<https://codeberg.org/scip/udpxd/issues>.
=head1 LICENSE
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3.
Copyright (c) 2015-2017 by T. v. Dein.
This software uses B<uthash> (bundled), which is
Copyright (c) 2003-2013 by Troy D. Hanson.
=head1 AUTHORS
T.v.Dein B<tom AT vondein DOT org>
=cut

1096
uthash.h

File diff suppressed because it is too large Load Diff

20
vg.sh
View File

@@ -1,20 +0,0 @@
#!/bin/sh
# small valgrind helper to execute unit tests with valgrind
vg="valgrind --leak-check=full --show-reachable=yes"
cmd=$1
shift
if test -z "$cmd"; then
echo "Usage: $0 <commandline ..>"
exit 1
else
if echo "$cmd" | egrep "^\.\." > /dev/null 2>&1; then
cd tests
fi
fi
$vg $cmd $* > log 2>&1
less log
rm -f log