chiark / gitweb /
polypath: Provide Linux interface monitor
[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     execvp(st->path,(char *const*)st->argv);
277     perror("userv-entry: execvp()");
278     exit(1);
279 }
280
281 static void userv_invoke_userv(struct userv *st)
282 {
283     struct userv_entry_rec er[1];
284     int c_stdin[2];
285     int c_stdout[2];
286     string_t nets;
287     string_t s;
288     struct netlink_client *r;
289     struct ipset *allnets;
290     struct subnet_list *snets;
291     int i, nread;
292     uint8_t confirm;
293
294     if (st->pid) {
295         fatal("userv_invoke_userv: already running");
296     }
297
298     /* This is where we actually invoke userv - all the networks we'll
299        be using should already have been registered. */
300
301     char addrs[512];
302     snprintf(addrs,sizeof(addrs),"%s,%s,%d,slip",
303              ipaddr_to_string(st->slip.nl.local_address),
304              ipaddr_to_string(st->slip.nl.secnet_address),st->slip.nl.mtu);
305
306     allnets=ipset_new();
307     for (r=st->slip.nl.clients; r; r=r->next) {
308         if (r->link_quality > LINK_QUALITY_UNUSED) {
309             struct ipset *nan;
310             r->kup=True;
311             nan=ipset_union(allnets,r->networks);
312             ipset_free(allnets);
313             allnets=nan;
314         }
315     }
316     snets=ipset_to_subnet_list(allnets);
317     ipset_free(allnets);
318     nets=safe_malloc(20*snets->entries,"userv_invoke_userv:nets");
319     *nets=0;
320     for (i=0; i<snets->entries; i++) {
321         s=subnet_to_string(snets->list[i]);
322         strcat(nets,s);
323         strcat(nets,",");
324     }
325     nets[strlen(nets)-1]=0;
326     subnet_list_free(snets);
327
328     Message(M_INFO,"%s: about to invoke: %s %s %s %s %s\n",st->slip.nl.name,
329             st->userv_path,st->service_user,st->service_name,addrs,nets);
330
331     st->slip.pending_esc=False;
332
333     /* Invoke userv */
334     pipe_cloexec(c_stdin);
335     pipe_cloexec(c_stdout);
336     st->txfd=c_stdin[1];
337     st->rxfd=c_stdout[0];
338
339     er->in=c_stdin[0];
340     er->out=c_stdout[1];
341     /* The arguments are:
342        userv
343        service-user
344        service-name
345        local-addr,secnet-addr,mtu,protocol
346        route1,route2,... */
347     const char *er_argv[6];
348     er->argv=er_argv;
349     er->argv[0]=st->userv_path;
350     er->argv[1]=st->service_user;
351     er->argv[2]=st->service_name;
352     er->argv[3]=addrs;
353     er->argv[4]=nets;
354     er->argv[5]=NULL;
355     er->path=st->userv_path;
356
357     st->pid=makesubproc(userv_entry, userv_userv_callback,
358                         er, st, st->slip.nl.name);
359     close(er->in);
360     close(er->out);
361     free(nets);
362     Message(M_INFO,"%s: userv-ipif pid is %d\n",st->slip.nl.name,st->pid);
363     /* Read a single character from the pipe to confirm userv-ipif is
364        running. If we get a SIGCHLD at this point then we'll get EINTR. */
365     if ((nread=read(st->rxfd,&confirm,1))!=1) {
366         if (errno==EINTR) {
367             Message(M_WARNING,"%s: read of confirmation byte was "
368                     "interrupted\n",st->slip.nl.name);
369         } else {
370             if (nread<0) {
371                 fatal_perror("%s: error reading confirmation byte",
372                              st->slip.nl.name);
373             } else {
374                 fatal("%s: unexpected EOF instead of confirmation byte"
375                       " - userv ipif failed?", st->slip.nl.name);
376             }
377         }
378     } else {
379         if (confirm!=SLIP_END) {
380             fatal("%s: bad confirmation byte %d from userv-ipif",
381                   st->slip.nl.name,confirm);
382         }
383     }
384     setnonblock(st->txfd);
385     setnonblock(st->rxfd);
386 }
387
388 static void userv_kill_userv(struct userv *st)
389 {
390     if (st->pid) {
391         kill(-st->pid,SIGTERM);
392         st->expecting_userv_exit=True;
393     }
394 }
395
396 static void userv_phase_hook(void *sst, uint32_t newphase)
397 {
398     struct userv *st=sst;
399     /* We must wait until signal processing has started before forking
400        userv */
401     if (newphase==PHASE_RUN) {
402         userv_invoke_userv(st);
403         /* Register for poll() */
404         register_for_poll(st, userv_beforepoll, userv_afterpoll,
405                           st->slip.nl.name);
406     }
407     if (newphase==PHASE_SHUTDOWN) {
408         userv_kill_userv(st);
409     }
410 }
411
412 static list_t *userv_apply(closure_t *self, struct cloc loc, dict_t *context,
413                            list_t *args)
414 {
415     struct userv *st;
416     item_t *item;
417     dict_t *dict;
418
419     st=safe_malloc(sizeof(*st),"userv_apply");
420
421     /* First parameter must be a dict */
422     item=list_elem(args,0);
423     if (!item || item->type!=t_dict)
424         cfgfatal(loc,"userv-ipif","parameter must be a dictionary\n");
425     
426     dict=item->data.dict;
427
428     slip_init(&st->slip,loc,dict,"netlink-userv-ipif",
429               userv_deliver_to_kernel);
430
431     st->userv_path=dict_read_string(dict,"userv-path",False,"userv-netlink",
432                                     loc);
433     st->service_user=dict_read_string(dict,"service-user",False,
434                                       "userv-netlink",loc);
435     st->service_name=dict_read_string(dict,"service-name",False,
436                                       "userv-netlink",loc);
437     if (!st->userv_path) st->userv_path="userv";
438     if (!st->service_user) st->service_user="root";
439     if (!st->service_name) st->service_name="ipif";
440     st->rxfd=-1; st->txfd=-1;
441     st->pid=0;
442     st->expecting_userv_exit=False;
443     add_hook(PHASE_RUN,userv_phase_hook,st);
444     add_hook(PHASE_SHUTDOWN,userv_phase_hook,st);
445
446     return new_closure(&st->slip.nl.cl);
447 }
448
449 void slip_module(dict_t *dict)
450 {
451     add_closure(dict,"userv-ipif",userv_apply);
452 }