diff --git a/Tools/UDP_Proxy/Makefile b/Tools/UDP_Proxy/Makefile new file mode 100644 index 0000000000..352d066528 --- /dev/null +++ b/Tools/UDP_Proxy/Makefile @@ -0,0 +1,8 @@ +CC=gcc +CFLAGS=-Wall + +udpproxy: udpproxy.c + $(CC) $(CFLAGS) -o udpproxy udpproxy.c + +clean: + rm -f udpproxy diff --git a/Tools/UDP_Proxy/README.md b/Tools/UDP_Proxy/README.md new file mode 100644 index 0000000000..43ccd20b37 --- /dev/null +++ b/Tools/UDP_Proxy/README.md @@ -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. diff --git a/Tools/UDP_Proxy/start_proxies.sh b/Tools/UDP_Proxy/start_proxies.sh new file mode 100755 index 0000000000..4763d0cd92 --- /dev/null +++ b/Tools/UDP_Proxy/start_proxies.sh @@ -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 diff --git a/Tools/UDP_Proxy/udpproxy.c b/Tools/UDP_Proxy/udpproxy.c new file mode 100644 index 0000000000..9f2bbc5ba0 --- /dev/null +++ b/Tools/UDP_Proxy/udpproxy.c @@ -0,0 +1,195 @@ +/* + UDP proxy code for connecting two UDP endpoints + Released under GNU GPLv3 + Author: Andrew Tridgell + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 \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; +}