chiark / gitweb /
comm: Break out common code in comm
[secnet.git] / udp.c
1 /* UDP send/receive module for secnet */
2
3 /* This module enables sites to communicate by sending UDP
4  * packets. When an instance of the module is created we can
5  * optionally bind to a particular local IP address (not implemented
6  * yet).
7  *
8  * Packets are offered to registered receivers in turn. Once one
9  * accepts it, it isn't offered to any more. */
10
11 #include "secnet.h"
12 #include <stdio.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <sys/socket.h>
18 #include <sys/wait.h>
19 #include <netinet/in.h>
20 #include <arpa/inet.h>
21 #include "util.h"
22 #include "magic.h"
23 #include "unaligned.h"
24 #include "ipaddr.h"
25 #include "magic.h"
26 #include "comm-common.h"
27
28 static beforepoll_fn udp_beforepoll;
29 static afterpoll_fn udp_afterpoll;
30 static comm_sendmsg_fn udp_sendmsg;
31
32 #define UDP_MAX_SOCKETS 3 /* 2 ought to do really */
33
34 struct udpsock {
35     union iaddr addr;
36     int fd;
37 };
38
39 struct udp {
40     struct commcommon cc;
41     int n_socks;
42     struct udpsock socks[UDP_MAX_SOCKETS];
43     string_t authbind;
44     bool_t use_proxy;
45     union iaddr proxy;
46 };
47
48 static const char *udp_addr_to_string(void *commst, const struct comm_addr *ca)
49 {
50     struct udp *st=commst;
51     struct udp *socks=st; /* for now */
52     static char sbuf[100];
53     int ix=ca->ix>=0 ? ca->ix : 0;
54
55     assert(ix>=0 && ix<socks->n_socks);
56     snprintf(sbuf, sizeof(sbuf), "udp:%d:%s-%s",
57              ca->ix,
58              iaddr_to_string(&socks->socks[ix].addr),
59              iaddr_to_string(&ca->ia));
60     return sbuf;
61 }
62
63 static int udp_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
64                           int *timeout_io)
65 {
66     int i;
67     struct udp *st=state;
68     struct udp *socks=st; /* for now */
69     BEFOREPOLL_WANT_FDS(socks->n_socks);
70     for (i=0; i<socks->n_socks; i++) {
71         fds[i].fd=socks->socks[i].fd;
72         fds[i].events=POLLIN;
73     }
74     return 0;
75 }
76
77 static void udp_afterpoll(void *state, struct pollfd *fds, int nfds)
78 {
79     struct udp *st=state;
80     struct commcommon *cc=&st->cc;
81     struct udp *socks=st; /* for now */
82     struct udp *uc=st; /* for now */
83     union iaddr from;
84     socklen_t fromlen;
85     bool_t done;
86     int rv;
87     int i;
88
89     for (i=0; i<st->n_socks; i++) {
90         if (i>=nfds) continue;
91         if (!(fds[i].revents & POLLIN)) continue;
92         assert(fds[i].fd == socks->socks[i].fd);
93         int fd=socks->socks[i].fd;
94         do {
95             FILLZERO(from);
96             fromlen=sizeof(from);
97             BUF_ASSERT_FREE(cc->rbuf);
98             BUF_ALLOC(cc->rbuf,"udp_afterpoll");
99             buffer_init(cc->rbuf,calculate_max_start_pad());
100             rv=recvfrom(fd, cc->rbuf->start,
101                         buf_remaining_space(cc->rbuf),
102                         0, (struct sockaddr *)&from, &fromlen);
103             if (rv>0) {
104                 cc->rbuf->size=rv;
105                 if (uc->use_proxy) {
106                     /* Check that the packet came from our poxy server;
107                        we shouldn't be contacted directly by anybody else
108                        (since they can trivially forge source addresses) */
109                     if (!iaddr_equal(&from,&uc->proxy)) {
110                         Message(M_INFO,"udp: received packet that's not "
111                                 "from the proxy\n");
112                         BUF_FREE(cc->rbuf);
113                         continue;
114                     }
115                     /* proxy protocol supports ipv4 transport only */
116                     FILLZERO(from);
117                     from.sa.sa_family=AF_INET;
118                     memcpy(&from.sin.sin_addr,buf_unprepend(cc->rbuf,4),4);
119                     buf_unprepend(cc->rbuf,2);
120                     memcpy(&from.sin.sin_port,buf_unprepend(cc->rbuf,2),2);
121                 }
122                 struct comm_addr ca;
123                 FILLZERO(ca);
124                 ca.comm=&cc->ops;
125                 ca.ia=from;
126                 ca.ix=i;
127                 done=comm_notify(&cc->notify, cc->rbuf, &ca);
128                 if (!done) {
129                     uint32_t msgtype;
130                     if (cc->rbuf->size>12 /* prevents traffic amplification */
131                         && ((msgtype=get_uint32(cc->rbuf->start+8))
132                             != LABEL_NAK)) {
133                         uint32_t source,dest;
134                         /* Manufacture and send NAK packet */
135                         source=get_uint32(cc->rbuf->start); /* Us */
136                         dest=get_uint32(cc->rbuf->start+4); /* Them */
137                         send_nak(&ca,source,dest,msgtype,cc->rbuf,"unwanted");
138                     }
139                     BUF_FREE(cc->rbuf);
140                 }
141                 BUF_ASSERT_FREE(cc->rbuf);
142             } else {
143                 BUF_FREE(cc->rbuf);
144             }
145         } while (rv>=0);
146     }
147 }
148
149 static bool_t udp_sendmsg(void *commst, struct buffer_if *buf,
150                           const struct comm_addr *dest)
151 {
152     struct udp *st=commst;
153     struct udp *uc=st; /* for now */
154     struct udp *socks=st; /* for now */
155     uint8_t *sa;
156
157     if (uc->use_proxy) {
158         sa=buf_prepend(buf,8);
159         if (dest->ia.sa.sa_family != AF_INET) {
160             Message(M_INFO,
161                "udp: proxy means dropping outgoing non-IPv4 packet to %s\n",
162                     iaddr_to_string(&dest->ia));
163             return False;
164         }
165         memcpy(sa,&dest->ia.sin.sin_addr,4);
166         memset(sa+4,0,4);
167         memcpy(sa+6,&dest->ia.sin.sin_port,2);
168         sendto(socks->socks[0].fd,sa,buf->size+8,0,(struct sockaddr *)&uc->proxy,
169                sizeof(uc->proxy));
170         buf_unprepend(buf,8);
171     } else {
172         int i,r;
173         bool_t allunsupported=True;
174         for (i=0; i<socks->n_socks; i++) {
175             if (dest->ia.sa.sa_family != socks->socks[i].addr.sa.sa_family)
176                 /* no point even trying */
177                 continue;
178             r=sendto(socks->socks[i].fd, buf->start, buf->size, 0,
179                      &dest->ia.sa, iaddr_socklen(&dest->ia));
180             if (!r) return True;
181             if (!(errno==EAFNOSUPPORT || errno==ENETUNREACH))
182                 /* who knows what that error means? */
183                 allunsupported=False;
184         }
185         return !allunsupported; /* see doc for comm_sendmsg_fn in secnet.h */
186     }
187
188     return True;
189 }
190
191 static void udp_make_socket(struct udp *st, struct udpsock *us)
192 {
193     const union iaddr *addr=&us->addr;
194     struct commcommon *cc=&st->cc; /* for now */
195     struct udp *uc=st; /* for now */
196     us->fd=socket(addr->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
197     if (us->fd<0) {
198         fatal_perror("udp (%s:%d): socket",cc->loc.file,cc->loc.line);
199     }
200     if (fcntl(us->fd, F_SETFL, fcntl(us->fd, F_GETFL)|O_NONBLOCK)==-1) {
201         fatal_perror("udp (%s:%d): fcntl(set O_NONBLOCK)",
202                      cc->loc.file,cc->loc.line);
203     }
204     setcloexec(us->fd);
205 #ifdef CONFIG_IPV6
206     if (addr->sa.sa_family==AF_INET6) {
207         int r;
208         int optval=1;
209         socklen_t optlen=sizeof(optval);
210         r=setsockopt(us->fd,IPPROTO_IPV6,IPV6_V6ONLY,&optval,optlen);
211         if (r) fatal_perror("udp (%s:%d): setsockopt(,IPV6_V6ONLY,&1,)",
212                             cc->loc.file,cc->loc.line);
213     }
214 #endif
215
216     if (uc->authbind) {
217         pid_t c;
218         int status;
219
220         /* XXX this fork() and waitpid() business needs to be hidden
221            in some system-specific library functions. */
222         c=fork();
223         if (c==-1) {
224             fatal_perror("udp_phase_hook: fork() for authbind");
225         }
226         if (c==0) {
227             char *argv[5], addrstr[33], portstr[5];
228             const char *addrfam;
229             int port;
230             switch (addr->sa.sa_family) {
231             case AF_INET:
232                 sprintf(addrstr,"%08lX",(long)addr->sin.sin_addr.s_addr);
233                 port=addr->sin.sin_port;
234                 addrfam=NULL;
235                 break;
236 #ifdef CONFIG_IPV6
237             case AF_INET6: {
238                 int i;
239                 for (i=0; i<16; i++)
240                     sprintf(addrstr+i*2,"%02X",addr->sin6.sin6_addr.s6_addr[i]);
241                 port=addr->sin6.sin6_port;
242                 addrfam="6";
243                 break;
244             }
245 #endif /*CONFIG_IPV6*/
246             default:
247                 fatal("udp (%s:%d): unsupported address family for authbind",
248                       cc->loc.file,cc->loc.line);
249             }
250             sprintf(portstr,"%04X",port);
251             argv[0]=uc->authbind;
252             argv[1]=addrstr;
253             argv[2]=portstr;
254             argv[3]=(char*)addrfam;
255             argv[4]=NULL;
256             dup2(us->fd,0);
257             execvp(uc->authbind,argv);
258             _exit(255);
259         }
260         while (waitpid(c,&status,0)==-1) {
261             if (errno==EINTR) continue;
262             fatal_perror("udp (%s:%d): authbind",cc->loc.file,cc->loc.line);
263         }
264         if (WIFSIGNALED(status)) {
265             fatal("udp (%s:%d): authbind died on signal %d",cc->loc.file,
266                   cc->loc.line, WTERMSIG(status));
267         }
268         if (WIFEXITED(status) && WEXITSTATUS(status)!=0) {
269             fatal("udp (%s:%d): authbind died with status %d",cc->loc.file,
270                   cc->loc.line, WEXITSTATUS(status));
271         }
272     } else {
273         if (bind(us->fd, &addr->sa, iaddr_socklen(addr))!=0) {
274             fatal_perror("udp (%s:%d): bind",cc->loc.file,cc->loc.line);
275         }
276     }
277 }
278
279 static void udp_phase_hook(void *sst, uint32_t new_phase)
280 {
281     struct udp *st=sst;
282     struct udp *socks=st; /* for now */
283     int i;
284     for (i=0; i<socks->n_socks; i++)
285         udp_make_socket(st,&socks->socks[i]);
286
287     register_for_poll(st,udp_beforepoll,udp_afterpoll,"udp");
288 }
289
290 static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
291                          list_t *args)
292 {
293     struct udp *st;
294     item_t *item;
295     list_t *caddrl;
296     dict_t *d;
297     list_t *l;
298     uint32_t a;
299     int i;
300
301     COMM_APPLY(st,&st->cc,udp_,"udp",loc);
302     struct commcommon *cc=&st->cc; /* for now */
303     struct udp *uc=st; /* for now */
304     struct udp *socks=st; /* for now */
305     uc->use_proxy=False;
306
307     item=list_elem(args,0);
308     if (!item || item->type!=t_dict) {
309         cfgfatal(cc->loc,"udp","first argument must be a dictionary\n");
310     }
311     d=item->data.dict;
312
313     int port=dict_read_number(d,"port",True,"udp",cc->loc,0);
314
315     union iaddr defaultaddrs[] = {
316 #ifdef CONFIG_IPV6
317         { .sin6 = { .sin6_family=AF_INET6,
318                     .sin6_port=htons(port),
319                     .sin6_addr=IN6ADDR_ANY_INIT } },
320 #endif
321         { .sin = { .sin_family=AF_INET,
322                    .sin_port=htons(port),
323                    .sin_addr= { .s_addr=INADDR_ANY } } }
324     };
325
326     caddrl=dict_lookup(d,"address");
327     socks->n_socks=caddrl ? list_length(caddrl) : (int)ARRAY_SIZE(defaultaddrs);
328     if (socks->n_socks<=0 || socks->n_socks>UDP_MAX_SOCKETS)
329         cfgfatal(cc->loc,"udp","`address' must be 1..%d addresses",
330                  UDP_MAX_SOCKETS);
331
332     for (i=0; i<socks->n_socks; i++) {
333         struct udpsock *us=&socks->socks[i];
334         FILLZERO(us->addr);
335         if (!list_length(caddrl)) {
336             us->addr=defaultaddrs[i];
337         } else {
338             text2iaddr(list_elem(caddrl,i),port,
339                        &us->addr,"udp");
340         }
341         us->fd=-1;
342     }
343
344     cc->rbuf=find_cl_if(d,"buffer",CL_BUFFER,True,"udp",cc->loc);
345     uc->authbind=dict_read_string(d,"authbind",False,"udp",cc->loc);
346     l=dict_lookup(d,"proxy");
347     if (l) {
348         uc->use_proxy=True;
349         FILLZERO(uc->proxy);
350         uc->proxy.sa.sa_family=AF_INET;
351         item=list_elem(l,0);
352         if (!item || item->type!=t_string) {
353             cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
354         }
355         a=string_item_to_ipaddr(item,"proxy");
356         uc->proxy.sin.sin_addr.s_addr=htonl(a);
357         item=list_elem(l,1);
358         if (!item || item->type!=t_number) {
359             cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
360         }
361         uc->proxy.sin.sin_port=htons(item->data.number);
362     }
363
364     update_max_start_pad(&comm_max_start_pad, uc->use_proxy ? 8 : 0);
365
366     add_hook(PHASE_GETRESOURCES,udp_phase_hook,st);
367
368     return new_closure(&cc->cl);
369 }
370
371 void udp_module(dict_t *dict)
372 {
373     add_closure(dict,"udp",udp_apply);
374 }