Tools: added UDP proxy code

very useful for fwding mavlink
This commit is contained in:
Andrew Tridgell 2019-09-24 12:41:06 +10:00
parent 328fff8585
commit 5aaeaaea8c
4 changed files with 296 additions and 0 deletions

8
Tools/UDP_Proxy/Makefile Normal file
View File

@ -0,0 +1,8 @@
CC=gcc
CFLAGS=-Wall
udpproxy: udpproxy.c
$(CC) $(CFLAGS) -o udpproxy udpproxy.c
clean:
rm -f udpproxy

74
Tools/UDP_Proxy/README.md Normal file
View File

@ -0,0 +1,74 @@
# UDP Proxy
This is a tool to do UDP proxying, particularly for MAVLink
connections. It is useful when operating both a ground station and
aircraft on network links that don't have a public IP address.
# Functionality
udpproxy opens two listening UDP ports. When it has a connection on
both ports then it will forward packets between the ports. This allows
your GCS to connect to one of the ports and your aircraft to connect
to the other port. The GCS and aircraft will be able to communicate,
despite both not having public IP addresses.
# Why not a VPN?
udpproxy is an alternative to using a VPN for communication between
the aircraft and the GCS. The reason for not using a VPN in flight is
VPNs typically have a high reconnect time, and often add significant
latency. This poses an issue for aircraft control as you may lose the
ability to control the aircraft for minutes if there is a short
network outage. Using udpproxy minimises the time for the link to
re-establish after a network outage.
# Disadvantages
The main disadvantage of udpproxy is that it offers no security. If
someone knows that UDP ports and host you are using then they could
connect to your aircraft and control it. The risk can be reduced by
enabling MAVLink2 signing which allows you to ensure that nobody can
control the aircraft without knowing the signing key.
You can also reduce the risk by using firewall rules on the computer
to run the proxy on to only allow connections from the IP ranges you
known you will be using.
# Building
Just run 'make' command
# Usage
Basic usage is:
udpproxy PORT1 PORT2
this will listen on both PORT1 and PORT2. You should then make an
outgoing UDP connection from both GCS and aircraft to those ports, one
to each port.
Adding the -v option tells udpproxy to display information about new
connections and shows transfer rates which are useful for diagnostics.
You should run udpproxy on a computer with a public IP address.
# Keeping it running
You will typically want to keep udpproxy running for long periods
without having to keep a shell open on the computer running the
proxy. An example script which starts it under GNU screen and thus
allows you to monitor the connections and automatically restart them
is provided in this directory.
# Connecting
To connect from mavproxy to your proxy just add this to the
mavproxy.py command line:
--out AA.BB.CC.DD:PORT1
where AA.BB.CC.DD is the IPv4 network address of your proxy.
To connect from MissionPlanner use the "UDPCL" option, and enter the
IP address and port number of the proxy.

View File

@ -0,0 +1,19 @@
#!/bin/bash
# an example script that starts udpproxy for multiple ports under GNU
# screen, allowing for unattended operation of the proxy for long
# periods
killall -9 udpproxy
screen -AdmS proxy -t tab0 bash
BASE_PORT=10401
NUM_PORTS=10
port=$BASE_PORT
count=$NUM_PORTS
while [ $count -gt 0 ]; do
port2=$((port+1))
echo $port $port2
screen -S proxy -X screen -t $port ./udpproxy $port $port2 -v
port=$((port+2))
count=$((count-2))
done

195
Tools/UDP_Proxy/udpproxy.c Normal file
View File

@ -0,0 +1,195 @@
/*
UDP proxy code for connecting two UDP endpoints
Released under GNU GPLv3
Author: Andrew Tridgell
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
static bool verbose;
static int listen_port1, listen_port2;
static double timestamp()
{
struct timeval tval;
gettimeofday(&tval,NULL);
return tval.tv_sec + (tval.tv_usec*1.0e-6);
}
/*
open a socket of the specified type, port and address for incoming data
*/
int open_socket_in(int port)
{
struct sockaddr_in sock;
int res;
int one=1;
memset(&sock,0,sizeof(sock));
#ifdef HAVE_SOCK_SIN_LEN
sock.sin_len = sizeof(sock);
#endif
sock.sin_port = htons(port);
sock.sin_family = AF_INET;
res = socket(AF_INET, SOCK_DGRAM, 0);
if (res == -1) {
fprintf(stderr, "socket failed\n"); return -1;
return -1;
}
setsockopt(res,SOL_SOCKET,SO_REUSEADDR,(char *)&one,sizeof(one));
if (bind(res, (struct sockaddr *)&sock, sizeof(sock)) < 0) {
return(-1);
}
return res;
}
static void main_loop(int sock1, int sock2)
{
unsigned char buf[10240];
bool have_conn1=false;
bool have_conn2=false;
double last_pkt1=0;
double last_pkt2=0;
int fdmax = (sock1>sock2?sock1:sock2)+1;
double last_stats = timestamp();
uint32_t bytes_in1=0;
uint32_t bytes_in2=0;
while (1) {
fd_set fds;
int ret;
struct timeval tval;
double now = timestamp();
if (verbose && now - last_stats > 1) {
double dt = now - last_stats;
printf("%u: %u bytes/sec %u: %u bytes/sec\n",
(unsigned)listen_port1, (unsigned)(bytes_in1/dt),
(unsigned)listen_port2, (unsigned)(bytes_in2/dt));
bytes_in1 = bytes_in2 = 0;
last_stats = now;
}
if (have_conn1 && now - last_pkt1 > 10) {
break;
}
if (have_conn2 && now - last_pkt2 > 10) {
break;
}
FD_ZERO(&fds);
FD_SET(sock1, &fds);
FD_SET(sock2, &fds);
tval.tv_sec = 10;
tval.tv_usec = 0;
ret = select(fdmax, &fds, NULL, NULL, &tval);
if (ret == -1 && errno == EINTR) continue;
if (ret <= 0) break;
now = timestamp();
if (FD_ISSET(sock1, &fds)) {
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int n = recvfrom(sock1, buf, sizeof(buf), 0,
(struct sockaddr *)&from, &fromlen);
if (n <= 0) break;
bytes_in1 += n;
last_pkt1 = now;
if (!have_conn1) {
if (connect(sock1, (struct sockaddr *)&from, fromlen) != 0) {
break;
}
have_conn1 = true;
printf("have conn1\n");
}
if (have_conn2) {
if (send(sock2, buf, n, 0) != n) {
break;
}
}
}
if (FD_ISSET(sock2, &fds)) {
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int n = recvfrom(sock2, buf, sizeof(buf), 0,
(struct sockaddr *)&from, &fromlen);
if (n <= 0) break;
bytes_in2 += n;
last_pkt2 = now;
if (!have_conn2) {
if (connect(sock2, (struct sockaddr *)&from, fromlen) != 0) {
break;
}
have_conn2 = true;
printf("have conn2\n");
}
if (have_conn1) {
if (send(sock1, buf, n, 0) != n) {
break;
}
}
}
}
}
int main(int argc, char *argv[])
{
int sock_in1, sock_in2;
if (argc < 3) {
printf("Usage: udpproxy <port1> <port2>\n");
exit(1);
}
if (argc > 3) {
verbose = strcmp(argv[3],"-v") == 0;
printf("verbose=%u\n", (unsigned)verbose);
}
while (true) {
listen_port1 = atoi(argv[1]);
listen_port2 = atoi(argv[2]);
printf("Opening sockets %u %u\n", listen_port1, listen_port2);
sock_in1 = open_socket_in(listen_port1);
sock_in2 = open_socket_in(listen_port2);
if (sock_in1 == -1 || sock_in2 == -1) {
fprintf(stderr,"sock on ports %d or %d failed - %s\n",
listen_port1, listen_port2, strerror(errno));
exit(1);
}
main_loop(sock_in1, sock_in2);
close(sock_in1);
close(sock_in2);
}
return 0;
}