chiark / gitweb /
c846b39a08a7e44f319d7bf6edc9ee0d2b047496
[secnet.git] / slip.c
1 /* When dealing with SLIP (to a pty, or ipif) we have separate rx, tx
2    and client buffers.  When receiving we may read() any amount, not
3    just whole packets.  When transmitting we need to bytestuff anyway,
4    and may be part-way through receiving. */
5
6 #include "secnet.h"
7 #include "util.h"
8 #include "netlink.h"
9 #include "process.h"
10 #include "unaligned.h"
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <errno.h>
15 #include <fcntl.h>
16
17 #define SLIP_END    192
18 #define SLIP_ESC    219
19 #define SLIP_ESCEND 220
20 #define SLIP_ESCESC 221
21
22 struct slip {
23     struct netlink nl;
24     struct buffer_if *buff; /* We unstuff received packets into here
25                                and send them to the netlink code. */
26     bool_t pending_esc;
27     bool_t ignoring_packet; /* If this packet was corrupt or overlong,
28                                we ignore everything up to the next END */
29     netlink_deliver_fn *netlink_to_tunnel;
30 };
31
32 /* Generic SLIP mangling code */
33
34 static void slip_write(int fd, const uint8_t *p, size_t l)
35 {
36     while (l) {
37         ssize_t written=write(fd,p,l);
38         if (written<0) {
39             if (errno==EINTR) {
40                 continue;
41             } else if (iswouldblock(errno)) {
42                 lg_perror(0,"slip",0,M_ERR,errno,"write() (packet(s) lost)");
43                 return;
44             } else {
45                 fatal_perror("slip_stuff: write()");
46             }
47         }
48         assert(written>0);
49         assert((size_t)written<=l);
50         p+=written;
51         l-=written;
52     }
53 }
54
55 static void slip_stuff(struct slip *st, struct buffer_if *buf, int fd)
56 {
57     uint8_t txbuf[DEFAULT_BUFSIZE];
58     uint8_t *i;
59     int32_t j=0;
60
61     BUF_ASSERT_USED(buf);
62
63     /* There's probably a much more efficient way of implementing this */
64     txbuf[j++]=SLIP_END;
65     for (i=buf->start; i<(buf->start+buf->size); i++) {
66         switch (*i) {
67         case SLIP_END:
68             txbuf[j++]=SLIP_ESC;
69             txbuf[j++]=SLIP_ESCEND;
70             break;
71         case SLIP_ESC:
72             txbuf[j++]=SLIP_ESC;
73             txbuf[j++]=SLIP_ESCESC;
74             break;
75         default:
76             txbuf[j++]=*i;
77             break;
78         }
79         if ((j+2)>DEFAULT_BUFSIZE) {
80             slip_write(fd,txbuf,j);
81             j=0;
82         }
83     }
84     txbuf[j++]=SLIP_END;
85     slip_write(fd,txbuf,j);
86     BUF_FREE(buf);
87 }
88
89 static void slip_unstuff(struct slip *st, uint8_t *buf, uint32_t l)
90 {
91     uint32_t i;
92
93     BUF_ASSERT_USED(st->buff);
94     for (i=0; i<l; i++) {
95         int outputchr;
96         enum { OUTPUT_END = 256, OUTPUT_NOTHING = 257 };
97
98         if (!st->buff->size)
99             buffer_init(st->buff,calculate_max_start_pad());
100
101         if (st->pending_esc) {
102             st->pending_esc=False;
103             switch(buf[i]) {
104             case SLIP_ESCEND:
105                 outputchr=SLIP_END;
106                 break;
107             case SLIP_ESCESC:
108                 outputchr=SLIP_ESC;
109                 break;
110             default:
111                 if (!st->ignoring_packet) {
112                     Message(M_WARNING, "userv_afterpoll: bad SLIP escape"
113                             " character, dropping packet\n");
114                 }
115                 st->ignoring_packet=True;
116                 outputchr=OUTPUT_NOTHING;
117                 break;
118             }
119         } else {
120             switch (buf[i]) {
121             case SLIP_END:
122                 outputchr=OUTPUT_END;
123                 break;
124             case SLIP_ESC:
125                 st->pending_esc=True;
126                 outputchr=OUTPUT_NOTHING;
127                 break;
128             default:
129                 outputchr=buf[i];
130                 break;
131             }
132         }
133
134         if (st->ignoring_packet) {
135             if (outputchr == OUTPUT_END) {
136                 st->ignoring_packet=False;
137                 st->buff->size=0;
138             }
139         } else {
140             if (outputchr == OUTPUT_END) {
141                 if (st->buff->size>0) {
142                     st->netlink_to_tunnel(&st->nl,st->buff);
143                     BUF_ALLOC(st->buff,"userv_afterpoll");
144                 }
145                 st->buff->size=0;
146             } else if (outputchr != OUTPUT_NOTHING) {
147                 if (buf_remaining_space(st->buff)) {
148                     buf_append_uint8(st->buff,outputchr);
149                 } else {
150                     Message(M_WARNING, "userv_afterpoll: dropping overlong"
151                             " SLIP packet\n");
152                     st->ignoring_packet=True;
153                 }
154             }
155         }
156     }
157 }
158
159 static void slip_init(struct slip *st, struct cloc loc, dict_t *dict,
160                       cstring_t name, netlink_deliver_fn *to_host)
161 {
162     st->netlink_to_tunnel=
163         netlink_init(&st->nl,st,loc,dict,
164                      "netlink-userv-ipif",NULL,to_host);
165     st->buff=find_cl_if(dict,"buffer",CL_BUFFER,True,"name",loc);
166     BUF_ALLOC(st->buff,"slip_init");
167     st->pending_esc=False;
168     st->ignoring_packet=False;
169 }
170
171 /* Connection to the kernel through userv-ipif */
172
173 struct userv {
174     struct slip slip;
175     int txfd; /* We transmit to userv */
176     int rxfd; /* We receive from userv */
177     cstring_t userv_path;
178     cstring_t service_user;
179     cstring_t service_name;
180     pid_t pid;
181     bool_t expecting_userv_exit;
182 };
183
184 static int userv_beforepoll(void *sst, struct pollfd *fds, int *nfds_io,
185                             int *timeout_io)
186 {
187     struct userv *st=sst;
188
189     if (st->rxfd!=-1) {
190         BEFOREPOLL_WANT_FDS(2);
191         fds[0].fd=st->txfd;
192         fds[0].events=0; /* Might want to pick up POLLOUT sometime */
193         fds[1].fd=st->rxfd;
194         fds[1].events=POLLIN;
195     } else {
196         BEFOREPOLL_WANT_FDS(0);
197     }
198     return 0;
199 }
200
201 static void userv_afterpoll(void *sst, struct pollfd *fds, int nfds)
202 {
203     struct userv *st=sst;
204     uint8_t rxbuf[DEFAULT_BUFSIZE];
205     int l;
206
207     if (nfds==0) return;
208
209     if (fds[1].revents&POLLERR) {
210         Message(M_ERR,"%s: userv_afterpoll: POLLERR!\n",st->slip.nl.name);
211     }
212     if (fds[1].revents&POLLIN) {
213         l=read(st->rxfd,rxbuf,DEFAULT_BUFSIZE);
214         if (l<0) {
215             if (errno!=EINTR && !iswouldblock(errno))
216                 fatal_perror("%s: userv_afterpoll: read(rxfd)",
217                              st->slip.nl.name);
218         } else if (l==0) {
219             fatal("%s: userv_afterpoll: read(rxfd)=0; userv gone away?",
220                   st->slip.nl.name);
221         } else slip_unstuff(&st->slip,rxbuf,l);
222     }
223 }
224
225 /* Send buf to the kernel. Free buf before returning. */
226 static void userv_deliver_to_kernel(void *sst, struct buffer_if *buf)
227 {
228     struct userv *st=sst;
229
230     if (buf->size > st->slip.nl.mtu) {
231         Message(M_ERR,"%s: packet of size %"PRIu32" exceeds mtu %"PRIu32":"
232                 " cannot be injected into kernel, dropped\n",
233                 st->slip.nl.name, buf->size, st->slip.nl.mtu);
234         BUF_FREE(buf);
235         return;
236     }
237
238     slip_stuff(&st->slip,buf,st->txfd);
239 }
240
241 static void userv_userv_callback(void *sst, pid_t pid, int status)
242 {
243     struct userv *st=sst;
244
245     if (pid!=st->pid) {
246         Message(M_WARNING,"userv_callback called unexpectedly with pid %d "
247                 "(expected %d)\n",pid,st->pid);
248         return;
249     }
250     if (!(st->expecting_userv_exit &&
251           (!status ||
252            (WIFSIGNALED(status) && WTERMSIG(status)==SIGTERM)))) {
253         lg_exitstatus(0,st->slip.nl.name,0,
254                       st->expecting_userv_exit ? M_WARNING : M_FATAL,
255                       status,"userv");
256     }
257     st->pid=0;
258 }
259
260 struct userv_entry_rec {
261     cstring_t path;
262     const char **argv;
263     int in;
264     int out;
265     /* XXX perhaps we should collect and log stderr? */
266 };
267
268 static void userv_entry(void *sst)
269 {
270     struct userv_entry_rec *st=sst;
271
272     dup2(st->in,0);
273     dup2(st->out,1);
274
275     setsid();
276     /* XXX We really should strdup() all of argv[] but because we'll just
277        exit anyway if execvp() fails it doesn't seem worth bothering. */
278     execvp(st->path,(char *const*)st->argv);
279     perror("userv-entry: execvp()");
280     exit(1);
281 }
282
283 static void userv_invoke_userv(struct userv *st)
284 {
285     struct userv_entry_rec er[1];
286     int c_stdin[2];
287     int c_stdout[2];
288     string_t nets;
289     string_t s;
290     struct netlink_client *r;
291     struct ipset *allnets;
292     struct subnet_list *snets;
293     int i, nread;
294     uint8_t confirm;
295
296     if (st->pid) {
297         fatal("userv_invoke_userv: already running");
298     }
299
300     /* This is where we actually invoke userv - all the networks we'll
301        be using should already have been registered. */
302
303     char addrs[512];
304     snprintf(addrs,sizeof(addrs),"%s,%s,%d,slip",
305              ipaddr_to_string(st->slip.nl.local_address),
306              ipaddr_to_string(st->slip.nl.secnet_address),st->slip.nl.mtu);
307
308     allnets=ipset_new();
309     for (r=st->slip.nl.clients; r; r=r->next) {
310         if (r->link_quality > LINK_QUALITY_UNUSED) {
311             struct ipset *nan;
312             r->kup=True;
313             nan=ipset_union(allnets,r->networks);
314             ipset_free(allnets);
315             allnets=nan;
316         }
317     }
318     snets=ipset_to_subnet_list(allnets);
319     ipset_free(allnets);
320     nets=safe_malloc(20*snets->entries,"userv_invoke_userv:nets");
321     *nets=0;
322     for (i=0; i<snets->entries; i++) {
323         s=subnet_to_string(snets->list[i]);
324         strcat(nets,s);
325         strcat(nets,",");
326     }
327     nets[strlen(nets)-1]=0;
328     subnet_list_free(snets);
329
330     Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->slip.nl.name,
331             st->userv_path,st->service_user,st->service_name,addrs,nets);
332
333     st->slip.pending_esc=False;
334
335     /* Invoke userv */
336     pipe_cloexec(c_stdin);
337     pipe_cloexec(c_stdout);
338     st->txfd=c_stdin[1];
339     st->rxfd=c_stdout[0];
340     setnonblock(st->rxfd);
341     setnonblock(st->txfd);
342
343     er->in=c_stdin[0];
344     er->out=c_stdout[1];
345     /* The arguments are:
346        userv
347        service-user
348        service-name
349        local-addr,secnet-addr,mtu,protocol
350        route1,route2,... */
351     const char *er_argv[6];
352     er->argv=er_argv;
353     er->argv[0]=st->userv_path;
354     er->argv[1]=st->service_user;
355     er->argv[2]=st->service_name;
356     er->argv[3]=addrs;
357     er->argv[4]=nets;
358     er->argv[5]=NULL;
359     er->path=st->userv_path;
360
361     st->pid=makesubproc(userv_entry, userv_userv_callback,
362                         er, st, st->slip.nl.name);
363     close(er->in);
364     close(er->out);
365     free(nets);
366     Message(M_INFO,"%s: userv-ipif pid is %d\n",st->slip.nl.name,st->pid);
367     /* Read a single character from the pipe to confirm userv-ipif is
368        running. If we get a SIGCHLD at this point then we'll get EINTR. */
369     if ((nread=read(st->rxfd,&confirm,1))!=1) {
370         if (errno==EINTR) {
371             Message(M_WARNING,"%s: read of confirmation byte was "
372                     "interrupted\n",st->slip.nl.name);
373         } else {
374             if (nread<0) {
375                 fatal_perror("%s: error reading confirmation byte",
376                              st->slip.nl.name);
377             } else {
378                 fatal("%s: unexpected EOF instead of confirmation byte"
379                       " - userv ipif failed?", st->slip.nl.name);
380             }
381         }
382     } else {
383         if (confirm!=SLIP_END) {
384             fatal("%s: bad confirmation byte %d from userv-ipif",
385                   st->slip.nl.name,confirm);
386         }
387     }
388 }
389
390 static void userv_kill_userv(struct userv *st)
391 {
392     if (st->pid) {
393         kill(-st->pid,SIGTERM);
394         st->expecting_userv_exit=True;
395     }
396 }
397
398 static void userv_phase_hook(void *sst, uint32_t newphase)
399 {
400     struct userv *st=sst;
401     /* We must wait until signal processing has started before forking
402        userv */
403     if (newphase==PHASE_RUN) {
404         userv_invoke_userv(st);
405         /* Register for poll() */
406         register_for_poll(st, userv_beforepoll, userv_afterpoll,
407                           st->slip.nl.name);
408     }
409     if (newphase==PHASE_SHUTDOWN) {
410         userv_kill_userv(st);
411     }
412 }
413
414 static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
415                            list_t *args)
416 {
417     struct userv *st;
418     item_t *item;
419     dict_t *dict;
420
421     st=safe_malloc(sizeof(*st),"userv_apply");
422
423     /* First parameter must be a dict */
424     item=list_elem(args,0);
425     if (!item || item->type!=t_dict)
426         cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
427     
428     dict=item->data.dict;
429
430     slip_init(&st->slip,loc,dict,"netlink-userv-ipif",
431               userv_deliver_to_kernel);
432
433     st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
434                                     loc);
435     st->service_user=dict_read_string(dict,"service-user",False,
436                                       "userv-netlink",loc);
437     st->service_name=dict_read_string(dict,"service-name",False,
438                                       "userv-netlink",loc);
439     if (!st->userv_path) st->userv_path="userv";
440     if (!st->service_user) st->service_user="root";
441     if (!st->service_name) st->service_name="ipif";
442     st->rxfd=-1; st->txfd=-1;
443     st->pid=0;
444     st->expecting_userv_exit=False;
445     add_hook(PHASE_RUN,userv_phase_hook,st);
446     add_hook(PHASE_SHUTDOWN,userv_phase_hook,st);
447
448     return new_closure(&st->slip.nl.cl);
449 }
450
451 void slip_module(dict_t *dict)
452 {
453     add_closure(dict,"userv-ipif",userv_apply);
454 }