chiark / gitweb /
comm: Break out some common udp parts
[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 struct udp {
33     struct udpcommon uc;
34     struct udpsocks socks;
35 };
36
37 static const char *udp_addr_to_string(void *commst, const struct comm_addr *ca)
38 {
39     struct udp *st=commst;
40     struct udpsocks *socks=&st->socks;
41     static char sbuf[100];
42     int ix=ca->ix>=0 ? ca->ix : 0;
43
44     assert(ix>=0 && ix<socks->n_socks);
45     snprintf(sbuf, sizeof(sbuf), "udp:%d:%s-%s",
46              ca->ix,
47              iaddr_to_string(&socks->socks[ix].addr),
48              iaddr_to_string(&ca->ia));
49     return sbuf;
50 }
51
52 int udp_socks_beforepoll(struct udpsocks *socks,
53                          struct pollfd *fds, int *nfds_io,
54                          int *timeout_io)
55 {
56     int i;
57     BEFOREPOLL_WANT_FDS(socks->n_socks);
58     for (i=0; i<socks->n_socks; i++) {
59         fds[i].fd=socks->socks[i].fd;
60         fds[i].events=POLLIN;
61     }
62     return 0;
63 }
64
65 static int udp_beforepoll(void *state, struct pollfd *fds, int *nfds_io,
66                           int *timeout_io)
67 {
68     struct udp *st=state;
69     return udp_socks_beforepoll(&st->socks,fds,nfds_io,timeout_io);
70 }
71
72 void udp_socks_afterpoll(struct udpcommon *uc, struct udpsocks *socks,
73                          struct pollfd *fds, int nfds)
74 {
75     union iaddr from;
76     socklen_t fromlen;
77     bool_t done;
78     int rv;
79     int i;
80
81     struct commcommon *cc=&uc->cc;
82
83     for (i=0; i<socks->n_socks; i++) {
84         if (i>=nfds) continue;
85         if (!(fds[i].revents & POLLIN)) continue;
86         assert(fds[i].fd == socks->socks[i].fd);
87         int fd=socks->socks[i].fd;
88         do {
89             FILLZERO(from);
90             fromlen=sizeof(from);
91             BUF_ASSERT_FREE(cc->rbuf);
92             BUF_ALLOC(cc->rbuf,"udp_afterpoll");
93             buffer_init(cc->rbuf,calculate_max_start_pad());
94             rv=recvfrom(fd, cc->rbuf->start,
95                         buf_remaining_space(cc->rbuf),
96                         0, (struct sockaddr *)&from, &fromlen);
97             if (rv>0) {
98                 cc->rbuf->size=rv;
99                 if (uc->use_proxy) {
100                     /* Check that the packet came from our poxy server;
101                        we shouldn't be contacted directly by anybody else
102                        (since they can trivially forge source addresses) */
103                     if (!iaddr_equal(&from,&uc->proxy)) {
104                         Message(M_INFO,"udp: received packet that's not "
105                                 "from the proxy\n");
106                         BUF_FREE(cc->rbuf);
107                         continue;
108                     }
109                     /* proxy protocol supports ipv4 transport only */
110                     FILLZERO(from);
111                     from.sa.sa_family=AF_INET;
112                     memcpy(&from.sin.sin_addr,buf_unprepend(cc->rbuf,4),4);
113                     buf_unprepend(cc->rbuf,2);
114                     memcpy(&from.sin.sin_port,buf_unprepend(cc->rbuf,2),2);
115                 }
116                 struct comm_addr ca;
117                 FILLZERO(ca);
118                 ca.comm=&cc->ops;
119                 ca.ia=from;
120                 ca.ix=i;
121                 done=comm_notify(&cc->notify, cc->rbuf, &ca);
122                 if (!done) {
123                     uint32_t msgtype;
124                     if (cc->rbuf->size>12 /* prevents traffic amplification */
125                         && ((msgtype=get_uint32(cc->rbuf->start+8))
126                             != LABEL_NAK)) {
127                         uint32_t source,dest;
128                         /* Manufacture and send NAK packet */
129                         source=get_uint32(cc->rbuf->start); /* Us */
130                         dest=get_uint32(cc->rbuf->start+4); /* Them */
131                         send_nak(&ca,source,dest,msgtype,cc->rbuf,"unwanted");
132                     }
133                     BUF_FREE(cc->rbuf);
134                 }
135                 BUF_ASSERT_FREE(cc->rbuf);
136             } else {
137                 BUF_FREE(cc->rbuf);
138             }
139         } while (rv>=0);
140     }
141 }
142
143 static void udp_afterpoll(void *state, struct pollfd *fds, int nfds)
144 {
145     struct udp *st=state;
146     return udp_socks_afterpoll(&st->uc,&st->socks,fds,nfds);
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 udpcommon *uc=&st->uc;
154     struct udpsocks *socks=&st->socks;
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 udpcommon *uc=&st->uc;
195     struct commcommon *cc=&uc->cc;
196
197     us->fd=socket(addr->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
198     if (us->fd<0) {
199         fatal_perror("udp (%s:%d): socket",cc->loc.file,cc->loc.line);
200     }
201     if (fcntl(us->fd, F_SETFL, fcntl(us->fd, F_GETFL)|O_NONBLOCK)==-1) {
202         fatal_perror("udp (%s:%d): fcntl(set O_NONBLOCK)",
203                      cc->loc.file,cc->loc.line);
204     }
205     setcloexec(us->fd);
206 #ifdef CONFIG_IPV6
207     if (addr->sa.sa_family==AF_INET6) {
208         int r;
209         int optval=1;
210         socklen_t optlen=sizeof(optval);
211         r=setsockopt(us->fd,IPPROTO_IPV6,IPV6_V6ONLY,&optval,optlen);
212         if (r) fatal_perror("udp (%s:%d): setsockopt(,IPV6_V6ONLY,&1,)",
213                             cc->loc.file,cc->loc.line);
214     }
215 #endif
216
217     if (uc->authbind) {
218         pid_t c;
219         int status;
220
221         /* XXX this fork() and waitpid() business needs to be hidden
222            in some system-specific library functions. */
223         c=fork();
224         if (c==-1) {
225             fatal_perror("udp_phase_hook: fork() for authbind");
226         }
227         if (c==0) {
228             char *argv[5], addrstr[33], portstr[5];
229             const char *addrfam;
230             int port;
231             switch (addr->sa.sa_family) {
232             case AF_INET:
233                 sprintf(addrstr,"%08lX",(long)addr->sin.sin_addr.s_addr);
234                 port=addr->sin.sin_port;
235                 addrfam=NULL;
236                 break;
237 #ifdef CONFIG_IPV6
238             case AF_INET6: {
239                 int i;
240                 for (i=0; i<16; i++)
241                     sprintf(addrstr+i*2,"%02X",addr->sin6.sin6_addr.s6_addr[i]);
242                 port=addr->sin6.sin6_port;
243                 addrfam="6";
244                 break;
245             }
246 #endif /*CONFIG_IPV6*/
247             default:
248                 fatal("udp (%s:%d): unsupported address family for authbind",
249                       cc->loc.file,cc->loc.line);
250             }
251             sprintf(portstr,"%04X",port);
252             argv[0]=uc->authbind;
253             argv[1]=addrstr;
254             argv[2]=portstr;
255             argv[3]=(char*)addrfam;
256             argv[4]=NULL;
257             dup2(us->fd,0);
258             execvp(uc->authbind,argv);
259             _exit(255);
260         }
261         while (waitpid(c,&status,0)==-1) {
262             if (errno==EINTR) continue;
263             fatal_perror("udp (%s:%d): authbind",cc->loc.file,cc->loc.line);
264         }
265         if (WIFSIGNALED(status)) {
266             fatal("udp (%s:%d): authbind died on signal %d",cc->loc.file,
267                   cc->loc.line, WTERMSIG(status));
268         }
269         if (WIFEXITED(status) && WEXITSTATUS(status)!=0) {
270             fatal("udp (%s:%d): authbind died with status %d",cc->loc.file,
271                   cc->loc.line, WEXITSTATUS(status));
272         }
273     } else {
274         if (bind(us->fd, &addr->sa, iaddr_socklen(addr))!=0) {
275             fatal_perror("udp (%s:%d): bind",cc->loc.file,cc->loc.line);
276         }
277     }
278 }
279
280 static void udp_phase_hook(void *sst, uint32_t new_phase)
281 {
282     struct udp *st=sst;
283     struct udpsocks *socks=&st->socks;
284     int i;
285     for (i=0; i<socks->n_socks; i++)
286         udp_make_socket(st,&socks->socks[i]);
287
288     register_for_poll(st,udp_beforepoll,udp_afterpoll,"udp");
289 }
290
291 static list_t *udp_apply(closure_t *self, struct cloc loc, dict_t *context,
292                          list_t *args)
293 {
294     struct udp *st;
295     list_t *caddrl;
296     list_t *l;
297     uint32_t a;
298     int i;
299
300     COMM_APPLY(st,&st->uc.cc,udp_,"udp",loc);
301     COMM_APPLY_STANDARD(st,&st->uc.cc,"udp",args);
302     UDP_APPLY_STANDARD(st,&st->uc,"udp");
303
304     struct udpcommon *uc=&st->uc;
305     struct udpsocks *socks=&st->socks;
306     struct commcommon *cc=&uc->cc;
307
308     union iaddr defaultaddrs[] = {
309 #ifdef CONFIG_IPV6
310         { .sin6 = { .sin6_family=AF_INET6,
311                     .sin6_port=htons(uc->port),
312                     .sin6_addr=IN6ADDR_ANY_INIT } },
313 #endif
314         { .sin = { .sin_family=AF_INET,
315                    .sin_port=htons(uc->port),
316                    .sin_addr= { .s_addr=INADDR_ANY } } }
317     };
318
319     caddrl=dict_lookup(d,"address");
320     socks->n_socks=caddrl ? list_length(caddrl) : (int)ARRAY_SIZE(defaultaddrs);
321     if (socks->n_socks<=0 || socks->n_socks>UDP_MAX_SOCKETS)
322         cfgfatal(cc->loc,"udp","`address' must be 1..%d addresses",
323                  UDP_MAX_SOCKETS);
324
325     for (i=0; i<socks->n_socks; i++) {
326         struct udpsock *us=&socks->socks[i];
327         FILLZERO(us->addr);
328         if (!list_length(caddrl)) {
329             us->addr=defaultaddrs[i];
330         } else {
331             text2iaddr(list_elem(caddrl,i),uc->port,
332                        &us->addr,"udp");
333         }
334         us->fd=-1;
335     }
336
337     uc->authbind=dict_read_string(d,"authbind",False,"udp",cc->loc);
338     l=dict_lookup(d,"proxy");
339     if (l) {
340         uc->use_proxy=True;
341         FILLZERO(uc->proxy);
342         uc->proxy.sa.sa_family=AF_INET;
343         item=list_elem(l,0);
344         if (!item || item->type!=t_string) {
345             cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
346         }
347         a=string_item_to_ipaddr(item,"proxy");
348         uc->proxy.sin.sin_addr.s_addr=htonl(a);
349         item=list_elem(l,1);
350         if (!item || item->type!=t_number) {
351             cfgfatal(cc->loc,"udp","proxy must supply ""addr"",port\n");
352         }
353         uc->proxy.sin.sin_port=htons(item->data.number);
354     }
355
356     update_max_start_pad(&comm_max_start_pad, uc->use_proxy ? 8 : 0);
357
358     add_hook(PHASE_GETRESOURCES,udp_phase_hook,st);
359
360     return new_closure(&cc->cl);
361 }
362
363 void udp_module(dict_t *dict)
364 {
365     add_closure(dict,"udp",udp_apply);
366 }