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