1
0
mirror of https://github.com/troglobit/uredir.git synced 2025-09-20 05:22:03 +03:00
Files
uredir/youdp.c
Joachim Nilsson 73964e33dd Fix compiler warning introduced in last pull request
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
2018-12-05 07:42:46 +01:00

435 lines
8.6 KiB
C

/* UDP service redirector
*
* Copyright (c) 2017 Tobias Waldekranz <tobias@waldekranz.com>
* Copyright (C) 2017-2018 Joachim Nilsson <troglobit@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/udp.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <uev/uev.h>
#include "uredir.h"
#define _d(fmt, args...) \
syslog(LOG_DEBUG, "DBG %-15s " fmt, __func__, ##args)
#define _e(fmt, args...) \
syslog(LOG_ERR, "ERR %-15s " fmt, __func__, ##args)
#define conn_dump(c) \
_d("remote:%s:%u", inet_ntoa(c->remote->sin_addr), \
ntohs(c->remote->sin_port)); \
_d("local:%s sd:%d", inet_ntoa(c->local), c->sd);
#define CONTROL_BUFSIZE 512
#define DATA_BUFSIZE BUFSIZ
static uev_t outer_watcher;
static struct sockaddr_in outer;
static struct sockaddr_in inner;
struct conn {
LIST_ENTRY(conn) list;
int sd;
uev_t watcher;
uev_t timer;
struct msghdr *hdr;
struct in_addr local;
struct sockaddr_in *remote;
};
LIST_HEAD(connhead, conn) conns;
#define conn_foreach(_c) LIST_FOREACH(_c, &conns, list)
static void conn_del(struct conn *c);
static void conn_end(struct conn *c);
void hdr_reset_buffer_sizes(struct msghdr* hdr)
{
hdr->msg_namelen = sizeof(struct sockaddr_in);
hdr->msg_controllen = CONTROL_BUFSIZE;
hdr->msg_iovlen = 1;
hdr->msg_iov->iov_len = DATA_BUFSIZE;
}
struct msghdr *hdr_new(void)
{
struct msghdr *hdr;
hdr = malloc(sizeof(*hdr));
assert(hdr);
hdr->msg_name = malloc(sizeof(struct sockaddr_in));
assert(hdr->msg_name);
hdr->msg_control = malloc(CONTROL_BUFSIZE);
assert(hdr->msg_control);
hdr->msg_iov = malloc(sizeof(struct iovec));
assert(hdr->msg_iov);
hdr->msg_iov->iov_base = malloc(DATA_BUFSIZE);
assert(hdr->msg_iov->iov_base);
hdr_reset_buffer_sizes(hdr);
return hdr;
}
void hdr_free(struct msghdr *hdr)
{
free(hdr->msg_iov->iov_base);
free(hdr->msg_iov);
free(hdr->msg_control);
free(hdr->msg_name);
free(hdr);
}
static void timer_cb(uev_t *w, void *arg, int events)
{
if (events & UEV_ERROR) {
uev_exit(w->ctx);
return;
}
if (!inetd) {
_d("Connection timeout, cleaning up.");
conn_del((struct conn *)arg);
return;
}
_d("Timeout, exiting.");
uev_exit(w->ctx);
}
void timer_reset(struct conn *c)
{
_d("");
uev_timer_set(&c->timer, timeout * 1000, 0);
}
/* Peek into socket to figure out where an inbound packet comes from */
static struct in_addr *peek(int sd, void *name, socklen_t len)
{
static char cmbuf[0x100];
struct msghdr msgh;
struct cmsghdr *cmsg;
memset(&msgh, 0, sizeof(msgh));
msgh.msg_name = name;
msgh.msg_namelen = len;
msgh.msg_control = cmbuf;
msgh.msg_controllen = sizeof(cmbuf);
if (recvmsg(sd, &msgh, MSG_PEEK) < 0)
return NULL;
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
struct in_pktinfo *ipi = (struct in_pktinfo *)CMSG_DATA(cmsg);
if (cmsg->cmsg_level != SOL_IP || cmsg->cmsg_type != IP_PKTINFO)
continue;
return &ipi->ipi_spec_dst;
}
return NULL;
}
int sock_new(int *sock)
{
int sd = *sock;
int opt = 0, on = 1;
if (sd < 0) {
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
_e("Failed opening UDP socket: %m");
return -1;
}
}
/* Socket must be non-blocking for libev/libuEv */
fcntl(sd, F_SETFL, fcntl(sd, F_GETFL) | O_NONBLOCK);
/* At least on Linux the obnoxious IP_MULTICAST_ALL flag is set by default */
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_ALL, &opt, sizeof(opt)))
goto error;
if (setsockopt(sd, SOL_IP, IP_PKTINFO, &on, sizeof(on)))
goto error;
*sock = sd;
return 0;
error:
close(sd);
return -1;
}
static void conn_to_outer(uev_t *w, void *arg, int events)
{
struct conn *c = (struct conn *)arg;
ssize_t n;
_d("");
if (events & UEV_ERROR) {
uev_exit(w->ctx);
return;
}
n = recv(c->sd, c->hdr->msg_iov->iov_base, DATA_BUFSIZE, MSG_TRUNC);
if (n <= 0) {
_e("recv: %m");
conn_end(c);
return;
}
if (n > DATA_BUFSIZE)
_e("Received truncated data %d < %zd", DATA_BUFSIZE, n);
if (sendto(outer_watcher.fd, c->hdr->msg_iov->iov_base, n, 0,
c->hdr->msg_name, c->hdr->msg_namelen) == -1) {
_e("sendto: %m");
conn_end(c);
return;
}
conn_dump(c);
timer_reset(c);
}
static struct conn *conn_find(struct in_addr *local, struct sockaddr_in *remote)
{
struct conn *c;
conn_foreach(c) {
if (c->remote->sin_addr.s_addr == remote->sin_addr.s_addr &&
c->remote->sin_port == remote->sin_port &&
c->local.s_addr == local->s_addr) {
_d("found\n");
return c;
}
}
_d("missing\n");
return NULL;
}
static struct conn *conn_new(uev_ctx_t *ctx, struct in_addr *local, struct sockaddr_in *remote)
{
struct msghdr *hdr;
struct conn *c;
hdr = hdr_new();
if (!hdr)
return NULL;
if (hdr->msg_namelen > sizeof(*remote))
hdr->msg_namelen = sizeof(*remote);
memcpy(hdr->msg_name, remote, hdr->msg_namelen);
c = malloc(sizeof(*c));
if (!c) {
hdr_free(hdr);
return NULL;
}
c->sd = -1;
c->hdr = hdr;
c->remote = hdr->msg_name;
c->local = *local;
if (sock_new(&c->sd)) {
free(c);
hdr_free(hdr);
return NULL;
}
if (connect(c->sd, (struct sockaddr *)&inner, sizeof(inner))) {
close(c->sd);
free(c);
hdr_free(hdr);
return NULL;
}
LIST_INSERT_HEAD(&conns, c, list);
uev_timer_init(ctx, &c->timer, timer_cb, c, timeout * 1000, 0);
uev_io_init(ctx, &c->watcher, conn_to_outer, c, c->sd, UEV_READ);
_d("");
conn_dump(c);
return c;
}
static void conn_renew(struct conn *c)
{
hdr_reset_buffer_sizes(c->hdr);
}
static void conn_del(struct conn *c)
{
uev_io_stop(&c->watcher);
close(c->watcher.fd);
uev_timer_stop(&c->timer);
hdr_free(c->hdr);
LIST_REMOVE(c, list);
free(c);
}
static void conn_end(struct conn *c)
{
conn_del(c);
if (!inetd)
return;
if (LIST_EMPTY(&conns))
uev_exit(c->watcher.ctx);
}
static void outer_to_inner(uev_t *w, void *arg, int events)
{
struct sockaddr_in sin;
struct in_addr *local;
struct conn *c;
ssize_t len;
_d("\n");
if (events & UEV_ERROR) {
uev_exit(w->ctx);
return;
}
local = peek(w->fd, &sin, sizeof(sin));
if (!local) {
if (inetd)
uev_exit(w->ctx);
return;
}
c = conn_find(local, &sin);
if (!c) {
c = conn_new(w->ctx, local, &sin);
if (!c) {
_e("Failed allocating new connection: %m");
uev_exit(w->ctx);
return;
}
} else
conn_renew(c);
len = recvmsg(w->fd, c->hdr, 0);
if (len == -1) {
conn_end(c);
return;
} else if (c->hdr->msg_flags != 0) {
_e("Received message with flag = 0x%X."
"(MSG_EOR=0x%X, MSG_TRUNC=0x%X, MSG_CTRUNC=0x%X, MSG_OOB=0x%X)",
c->hdr->msg_flags,
MSG_EOR,
MSG_TRUNC,
MSG_CTRUNC,
MSG_OOB);
}
_d("");
conn_dump(c);
if (send(c->sd, c->hdr->msg_iov->iov_base, len, 0) == -1)
conn_end(c);
else
timer_reset(c);
}
static int outer_init(char *addr, short port)
{
int sd = -1;
if (sock_new(&sd))
return -1;
memset(&outer, 0, sizeof(outer));
outer.sin_family = AF_INET;
inet_aton(addr, &outer.sin_addr);
outer.sin_port = htons(port);
if (bind(sd, (struct sockaddr *)&outer, sizeof(outer))) {
_e("Failed binding to %s:%d: %m", addr, port);
return -1;
}
_d("ready\n");
return sd;
}
int redirect_init(uev_ctx_t *ctx, char *src, short src_port, char *dst, short dst_port)
{
int sd;
memset(&inner, 0, sizeof(inner));
inner.sin_family = AF_INET;
inet_aton(dst, &inner.sin_addr);
inner.sin_port = htons(dst_port);
if (!src) {
/* Running as an inetd service */
sd = STDIN_FILENO;
if (sock_new(&sd))
return 1;
} else {
sd = outer_init(src, src_port);
if (sd < 0)
return 1;
}
return uev_io_init(ctx, &outer_watcher, outer_to_inner, NULL, sd, UEV_READ);
}
int redirect_exit(void)
{
struct conn *c;
while (!LIST_EMPTY(&conns)) {
c = LIST_FIRST(&conns);
conn_del(c);
}
return uev_io_stop(&outer_watcher) || close(outer_watcher.fd);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/