mirror of
https://codeberg.org/scip/udpxd.git
synced 2025-12-18 12:30:58 +01:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 320a9eedb7 | |||
| 35390269ff | |||
|
|
5cf6e6b19d |
24
.travis.yml
24
.travis.yml
@@ -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 &
|
||||
49
Makefile
49
Makefile
@@ -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
228
README.md
@@ -1,15 +1,197 @@
|
||||
[](https://travis-ci.org/TLINDEN/udpxd)
|
||||
[](https://ci.appveyor.com/project/TLINDEN/udpxd)
|
||||
[](https://ci.codeberg.org/repos/15646)
|
||||
[](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
7
TODO
@@ -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
|
||||
34
appveyor.yml
34
appveyor.yml
@@ -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"'
|
||||
|
||||
|
||||
|
||||
82
client.c
82
client.c
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
87
client.h
87
client.h
@@ -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
173
host.c
@@ -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
56
host.h
@@ -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
48
log.c
@@ -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
40
log.h
@@ -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
447
net.c
@@ -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
72
net.h
@@ -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
289
udpxd.1
@@ -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
250
udpxd.c
@@ -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
45
udpxd.h
@@ -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
153
udpxd.pod
@@ -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
|
||||
20
vg.sh
20
vg.sh
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user