3 Commits

Author SHA1 Message Date
320a9eedb7 mv to codeberg 2025-12-01 17:16:30 +01:00
35390269ff fix html entities 2025-12-01 17:12:37 +01:00
T. von Dein
5cf6e6b19d move to codeberg (#11)
- switch to meson+ninja
- enhance documentaion
2025-12-01 17:09:54 +01:00
19 changed files with 204 additions and 2996 deletions

View File

@@ -1,24 +0,0 @@
language: c
os:
- linux
before_install:
- sudo apt-get update -qq
- sudo apt-get install dnsutils
compiler:
- clang
- gcc
script:
- make
- sudo make install
- sudo udpxd -l 127.0.0.1:53 -t 8.8.8.8:53 &
- dig +nocmd +noall +answer google.de a @127.0.0.1
- sudo killall udpxd
#
# ipv6 not supported anymore since Travis-ci moved to
# GCE: https://blog.travis-ci.com/2015-11-27-moving-to-a-more-elastic-future
# whatever this might have to do with "future".. However, Appveyor still
# supports it, so I suspect, disabling it here is probably ok.
#
# - dig +nocmd +noall +answer google.de a @::1
# - sudo udpxd -l [::1]:53 -t [2001:4860:4860::8888]:53 &

View File

@@ -1,49 +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>.
# 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: $(DST)
$(DST): $(OBJS)
$(CC) $(OBJS) -o $(DST)
%.o: %.c
$(CC) -c $(CFLAGS) $*.c -o $*.o
clean:
rm -f *.o $(DST)
man:
pod2man udpxd.pod > udpxd.1
install: $(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/

228
README.md
View File

@@ -1,15 +1,197 @@
[![Build Status](https://travis-ci.org/TLINDEN/udpxd.svg?branch=master)](https://travis-ci.org/TLINDEN/udpxd)
[![Build status](https://ci.appveyor.com/api/projects/status/omvdru5ysa2swcd1?svg=true)](https://ci.appveyor.com/project/TLINDEN/udpxd)
[![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)
> [!CAUTION]
> This software is now being maintained on [Codeberg](https://codeberg.org/scip/udpxd/).
## UDPXD - A general purpose UDP relay/port forwarder/proxy
This is the README file for the network program udpxd.
## Introduction
There are cases, when you need to change the source ip address of a
client, but can't do it in the kernel. Either because you don't have a
firewall running or because it doesn't work, or because there's no
firewall available.
In such cases the usual aproach is to use a port forwarder. This is a
small piece of software, which opens a port on the inside, visible to
the client and establishes a new connection to the intended
destination, which is otherwise unreachable for the client. In fact, a
port forwarder implements hiding nat or masquerading in
userspace. Tools like this are also usefull for testing things, to
simulate requests when the tool you need to use for the test are
unable to set their source ip address (binding address).
For TCP there are LOTs of solutions available, e.g.
[tcpxd](http://quozl.us.netrek.org/tcpxd/). Unfortunately there are
not so many solutions available if you want to do port forwarding with
UDP. One tool I found was
[udp-redirect](http://www.brokestream.com/udp_redirect.html). But it
is just a dirty hack and didn't work for me. Also it doesn't track
clients and is therefore not able to handle multiple clients
simultaneusly.
So I decided to enhance it. The - current - result is `udpxd`, a
"general purpose UDP relay/port forwarder/proxy". It supports multiple
concurrent clients, it is able to bind to a specific source ip address
and it is small and simple.
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 supports ip version 4 and 6.
## Quick Start
How does it work: I'll explain it on a concrete example. On the server
where this website is running there are multiple ip addresses
configured on the outside interface, because I'm using jails to
separate services. One of those jail ip addresses is 78.47.130.33. Now
if I want to send a DNS query to the Hetzner nameserver 213.133.98.98
from the root system (not inside the jail with the .33 ip address!),
then the packet will go out with the first interface address of the
system. Let's say I don't want that (either for testing or because the
remote end does only allow me to use the .33 address). In this
szenario I could use **udpxd** like this:
```default
udpxd -l 172.16.0.3:53 -b 78.47.130.33 -t 213.133.98.98:53
```
The ip address 172.16.0.3 is configured on the loopback interface
lo0. Now I can use dig to send a dns query to 172.16.0.3:53:
```default
dig +nocmd +noall +answer google.de a @172.16.0.3
google.de. 135 IN A 173.194.116.151
google.de. 135 IN A 173.194.116.152
google.de. 135 IN A 173.194.116.159
google.de. 135 IN A 173.194.116.143
```
When we look with tcpdump on our external interface, we see:
```default
IP 78.47.130.33.24239 > 213.133.98.98.53: 4552+ A? google.de. (27)
IP 213.133.98.98.53 > 78.47.130.33.24239: 4552 4/0/0 A 173.194.116.152,
A 173.194.116.159, A 173.194.116.143, A 173.194.116.151 (91)
```
And this is how the same request looks on the loopback interface:
```default
IP 172.16.0.3.24239 > 172.16.0.3.53: 4552+ A? google.de. (27)
IP 172.16.0.3.53 > 172.16.0.3.24239: 4552 4/0/0 A 173.194.116.152,
A 173.194.116.159, A 173.194.116.143, A 173.194.116.151 (91)
```
As you can see, dig sent the packet to 172.16.0.3:53, udpxd took it,
opened a new outgoing socket, bound to 78.47.130.33 and sent the
packet with that source ip address to the hetzner nameserver. It also
remembered which socket the particular client (source ip and source
port) has used. Then the response came back from hetzner, udpxd took
it, looked up its internal cache for the matching internal client and
sent it back to it with the source ip on the loopback interface.
As I already said, udpxd is a general purpose udp port forwarder, so
you can use it with almost any udp protocol. Here's another example
using ntp: now I set up a tunnel between two systems, because udpxd
cannot bind to any interface with port 123 while ntpd is running (ntpd
binds to *.123). So on system A I have 3 udpxd's running:
```default
udpxd -l 172.17.0.1:123 -b 78.47.130.33 -t 178.23.124.2:123
udpxd -l 172.17.0.2:123 -b 78.47.130.33 -t 5.9.29.107:123
udpxd -l 172.17.0.3:123 -b 78.47.130.33 -t 217.79.179.106:123
```
Here I forward ntp queries on 172.16.0.1-3:123 to the real ntp pool
servers and I'm using the .33 source ip to bind for outgoing packets
again. On the other system B, which is able to reach 172.17.0.0/24 via
the tunnel, I reconfigured the ntpd like so:
```default
server 172.17.0.1 iburst dynamic
server 172.17.0.2 iburst dynamic
server 172.17.0.3 iburst dynamic
```
After restarting, let's look how it works:
```default
ntpq> peers
remote refid st t when poll reach delay offset jitter
==============================================================================
*172.17.0.1 131.188.3.221 2 u 12 64 1 18.999 2.296 1.395
172.17.0.2 192.53.103.104 2 u 11 64 1 0.710 1.979 0.136
172.17.0.3 130.149.17.8 2 u 10 64 1 12.073 5.836 0.089
```
Seems to work :), here's what we see in the tunnel interface:
```default
14:13:32.534832 IP 10.10.10.1.123 > 172.17.0.1.123: NTPv4, Client, length 48
14:13:32.556627 IP 172.17.0.1.123 > 10.10.10.1.123: NTPv4, Server, length 48
14:13:33.535081 IP 10.10.10.1.123 > 172.17.0.2.123: NTPv4, Client, length 48
14:13:33.535530 IP 172.17.0.2.123 > 10.10.10.1.123: NTPv4, Server, length 48
14:13:34.535166 IP 10.10.10.1.123 > 172.17.0.1.123: NTPv4, Client, length 48
14:13:34.535278 IP 10.10.10.1.123 > 172.17.0.3.123: NTPv4, Client, length 48
14:13:34.544585 IP 172.17.0.3.123 > 10.10.10.1.123: NTPv4, Server, length 48
14:13:34.556956 IP 172.17.0.1.123 > 10.10.10.1.123: NTPv4, Server, length 48
14:13:35.535308 IP 10.10.10.1.123 > 172.17.0.2.123: NTPv4, Client, length 48
14:13:35.535742 IP 172.17.0.2.123 > 10.10.10.1.123: NTPv4, Server, length 48
14:13:36.535363 IP 10.10.10.1.123 > 172.17.0.1.123: NTPv4, Client, length 48
```
And the forwarded traffic on the public interface:
```default
14:13:32.534944 IP 78.47.130.33.63956 > 178.23.124.2.123: NTPv4, Client, length 48
14:13:32.556586 IP 178.23.124.2.123 > 78.47.130.33.63956: NTPv4, Server, length 48
14:13:33.535188 IP 78.47.130.33.48131 > 5.9.29.107.123: NTPv4, Client, length 48
14:13:33.535500 IP 5.9.29.107.123 > 78.47.130.33.48131: NTPv4, Server, length 48
14:13:34.535255 IP 78.47.130.33.56807 > 178.23.124.2.123: NTPv4, Client, length 48
14:13:34.535337 IP 78.47.130.33.56554 > 217.79.179.106.123: NTPv4, Client, length 48
14:13:34.544543 IP 217.79.179.106.123 > 78.47.130.33.56554: NTPv4, Server, length 48
14:13:34.556932 IP 178.23.124.2.123 > 78.47.130.33.56807: NTPv4, Server, length 48
14:13:35.535379 IP 78.47.130.33.22968 > 5.9.29.107.123: NTPv4, Client, length 48
14:13:35.535717 IP 5.9.29.107.123 > 78.47.130.33.22968: NTPv4, Server, length 48
14:13:36.535442 IP 78.47.130.33.24583 > 178.23.124.2.123: NTPv4, Client, length 48
```
You see, the ntp server gets the time via the tunnel via udpxd which
in turn forwards it to real ntp servers.
Note, if you left the parameter -b out, then the first ip address of
the outgoing interface will be used.
Udpxd can also proxy ipv6 udp traffic and forward between protocols
(v4 to v6 and vice versa). For instance listen on ipv6 loopback and
forward to ipv6 google:
```default
udpxd -l [::1]:53 -t [2001:4860:4860::8888]:53
```
Listen on ipv6 loopback and forward to ipv4 google:
```default
udpxd -l [::1]:53 -t 8.8.8.8:53
```
Or listen on ipv4 loopback and forward to ipv6 google:
```default
udpxd -l 127.0.0.1:53 -t [2001:4860:4860::8888]:53
```
Of course it is possble to set the bind ip address (-b) using ipv6 as
well.
## Documentation
You can read the documentation without installing the
@@ -21,40 +203,38 @@ If it is already installed, you can read the manual page:
man udpxd
## Installation
## Installation using pre-built binaries
You can download a statically pre-compiled binary for linux from the [release page](releases).
## Installation from source
This software doesn't have any external dependencies, but
you need either BSD make or GNU make installed to build it.
you will need `meson` and `ninja`: https://mesonbuild.com/.
First you need to check out the source code. Skip this, if
you have already done so:
Check out the source and execute:
git clone git@github.com:TLINDEN/udpxd.git
```default
meson setup build
ninja -C build
```
Next, change into the newly created directory 'udpxd' and
compile the source code:
You can supply some additional parameters to meson,
type `meson configuration` for details.
cd udpxd
make
To install, execute:
To install, type this command:
```default
sudo ninja -C install
```
sudo make install
This will install the binary to `$PREFIX/sbin/udpxd` and
the manual page to `$PREFIX/man/man1/udpxd.1`. You can
modify `$PREFIX` during installation time like this:
make install PREFIX=/opt
## Getting help
## Getting help
Although I'm happy to hear from udpxd users in private email,
that's the best way for me to forget to do something.
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
https://github.com/TLINDEN/udpxd/issues.
or to submit a patch, [please open an issue](https://codeberg.org/scip/udpxd/issues).
## Copyright and license
@@ -66,4 +246,4 @@ T.v.Dein <tom AT vondein DOT org>
## Project homepage
https://github.com/TLINDEN/udpxd
https://codeberg.org/scip/udpxd

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,34 +0,0 @@
environment:
global:
CYG_ROOT: C:\cygwin
CYG_BASH: C:\cygwin\bin\bash
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin\var\cache\setup
os: unstable
cache:
- '%CYG_CACHE%'
platform:
- Win32
test: off
init:
- git config --global core.autocrlf input
install:
- ps: 'Start-FileDownload "http://cygwin.com/setup-x86.exe" -FileName "setup-x86.exe"'
- cmd: 'setup-x86.exe --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages bind,netcat > NUL 2>&1'
- cmd: '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script:
- cmd: '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make"'
- cmd: '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; cygstart --hide ./udpxd -l 127.0.0.1:53 -t 8.8.8.8:53"'
- cmd: '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; cygstart --hide ./udpxd -l [::1]:53 -t 8.8.8.8:53"'
- cmd: '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; dig www.google.de soa @127.0.0.1"'
- cmd: '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; dig www.google.de soa @::1"'

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

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");
}
}
}

72
net.h
View File

@@ -1,72 +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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <setjmp.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <arpa/inet.h>
#include <pwd.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

289
udpxd.1
View File

@@ -1,289 +0,0 @@
.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
.\"
.\" 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" ''
'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 turned on, 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.
.ie \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. nr % 0
. rr F
.\}
.el \{\
. de IX
..
.\}
.\"
.\" 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 "2015-2016-04-27" "perl v5.14.2" "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> bind ip used for outgoing requests
\& \-\-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.
.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://github.com/TLINDEN/udpxd/issues>.
.SH "LICENSE"
.IX Header "LICENSE"
This software is licensed under the \s-1GNU\s0 \s-1GENERAL\s0 \s-1PUBLIC\s0 \s-1LICENSE\s0 version 3.
.PP
Copyright (c) 2015-2016 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;
}

45
udpxd.h
View File

@@ -1,45 +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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <arpa/inet.h>
#define UDPXD_VERSION "0.0.4"
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://github.com/TLINDEN/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