]> git.sesse.net Git - ultimatescore/blob - micropubsub/proxy.cpp
Add a little UDP proxy.
[ultimatescore] / micropubsub / proxy.cpp
1 // An UDP proxy to get around NAT issues. Use as
2 //
3 //   g++ -Wall -O2 -o proxy proxy.cpp -pthread
4 //   ./proxy 6001 7001
5 //
6 // Send packets from the bodet client to port 6001. Anyone who contacts
7 // port 7001 often enough will get those packets sent back to them.
8
9 #include <stdio.h>
10 #include <string.h>
11 #include <arpa/inet.h>
12 #include <netinet/in.h>
13 #include <netinet/tcp.h>
14 #include <stdlib.h>
15 #include <sys/types.h>
16 #include <sys/socket.h>
17 #include <map>
18 #include <chrono>
19 #include <vector>
20 #include <mutex>
21 #include <thread>
22 #include <algorithm>
23
24 using namespace std;
25 using namespace std::chrono;
26
27 struct Client {
28         sockaddr_in6 addr;
29         steady_clock::time_point last_seen;
30 };
31 vector<Client> clients;
32 mutex clients_mu;
33
34 bool sockaddr6_eq(const sockaddr_in6 &addr1, const sockaddr_in6 &addr2)
35 {
36         return memcmp(addr1.sin6_addr.s6_addr, addr2.sin6_addr.s6_addr, 16) == 0 &&
37                 addr1.sin6_port == addr2.sin6_port;
38 }
39
40 string sockaddr_to_string(const sockaddr_in6 &addr)
41 {
42         char buf[256], buf2[256];
43         snprintf(buf2, sizeof(buf2), "%s:%d", 
44                 inet_ntop(AF_INET6, &addr.sin6_addr, buf, sizeof(buf)),
45                 ntohs(addr.sin6_port));
46         return buf2;
47 }
48
49 int create_sock(int port)
50 {
51         int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
52         if (sock == -1) {
53                 perror("socket");
54                 exit(1);
55         }
56
57         int one = 1;
58         if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
59                 perror("setsockopt");
60                 exit(1);
61         }
62
63         sockaddr_in6 saddr6;
64         memset(&saddr6, 0, sizeof(saddr6));
65         saddr6.sin6_family = AF_INET6;
66         inet_pton(AF_INET6, "::", &saddr6.sin6_addr);
67         saddr6.sin6_port = htons(port);
68         if (bind(sock, (sockaddr *)&saddr6, sizeof(saddr6)) == -1) {
69                 perror("bind");
70                 exit(1);
71         }
72
73         return sock;
74 }
75
76 void recv_thread_func(int bodet_sock, int client_sock)
77 {
78         for ( ;; ) {
79                 char buf[4096];
80                 int err = recv(bodet_sock, buf, sizeof(buf), 0);
81                 if (err == -1) {
82                         perror("recv");
83                         exit(1);
84                 }
85
86                 printf("received packet '%.*s' from bodet\n", err, buf);
87
88                 steady_clock::time_point now = steady_clock::now();
89
90                 lock_guard<mutex> lock(clients_mu);
91
92                 // time out old clients
93                 auto rm_it = remove_if(clients.begin(), clients.end(), [now](const Client &c) {
94                         return now - c.last_seen > seconds(60);
95                 });
96                 for (auto it = rm_it; it != clients.end(); ++it) {
97                         printf(" - timing out %s\n", sockaddr_to_string(it->addr).c_str());
98                 }
99                 clients.erase(rm_it, clients.end());
100
101                 for (const Client &client : clients) {
102                         printf(" - sending it to %s\n", sockaddr_to_string(client.addr).c_str());
103                         sendto(client_sock, buf, err, 0, (sockaddr *)&client.addr, sizeof(client.addr));
104                 }
105         }       
106 }
107
108 void attach_thread_func(int sock)
109 {
110         for ( ;; ) {
111                 sockaddr_in6 addr;
112                 socklen_t addrlen = sizeof(addr);
113
114                 char buf[4096];
115                 int err = recvfrom(sock, buf, sizeof(buf), 0, (sockaddr *)&addr, &addrlen);
116                 if (err == -1) {
117                         perror("recv");
118                         exit(1);
119                 }
120
121                 printf("received hello packet from %s\n", sockaddr_to_string(addr).c_str());
122
123                 lock_guard<mutex> lock(clients_mu);
124                 bool found = false;
125                 for (Client &client : clients) {
126                         if (sockaddr6_eq(client.addr, addr)) {
127                                 found = true;
128                                 client.last_seen = steady_clock::now();
129                                 break;
130                         }
131                 }
132
133                 if (!found) {
134                         printf(" - new client added!\n");
135                         clients.push_back(Client{ addr, steady_clock::now() });
136                 }
137         }       
138 }
139
140 int main(int argc, char **argv)
141 {
142         int bodet_port = atoi(argv[1]);
143         int client_port = atoi(argv[2]);
144
145         int bodet_sock = create_sock(bodet_port);
146         int client_sock = create_sock(client_port);
147
148         thread t1(recv_thread_func, bodet_sock, client_sock);
149         thread t2(attach_thread_func, client_sock);
150         t1.join();
151         t2.join();
152 }