]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/pppoe.c
6in4: add -m mtu option to specify outer MTU
[plan9front.git] / sys / src / cmd / ip / pppoe.c
1 /*
2  * User-level PPP over Ethernet (PPPoE) client.
3  * See RFC 2516
4  */
5
6 #include <u.h>
7 #include <libc.h>
8 #include <ip.h>
9
10 void dumppkt(uchar*);
11 uchar *findtag(uchar*, int, int*, int);
12 void hexdump(uchar*, int);
13 int malformed(uchar*, int, int);
14 int pppoe(char*);
15 void execppp(int);
16
17 int alarmed;
18 int debug;
19 int sessid;
20 char *keyspec;
21 int primary;
22 char *pppnetmtpt;
23 char *acname;
24 char *pppname = "/bin/ip/ppp";
25 char *srvname = "";
26 char *wantac;
27 uchar *cookie;
28 int cookielen;
29 uchar etherdst[6];
30 int mtu = 1492;
31 int pktcompress, hdrcompress;
32
33 void
34 usage(void)
35 {
36         fprint(2, "usage: pppoe [-PdcC] [-A acname] [-S srvname] [-k keyspec] [-m mtu] [-x pppnet] [ether0]\n");
37         exits("usage");
38 }
39
40 int
41 catchalarm(void *a, char *msg)
42 {
43         USED(a);
44
45         if(strstr(msg, "alarm")){
46                 alarmed = 1;
47                 return 1;
48         }
49         if(debug)
50                 fprint(2, "note rcved: %s\n", msg);
51         return 0;
52 }
53
54 void
55 main(int argc, char **argv)
56 {
57         int fd;
58         char *dev;
59
60         ARGBEGIN{
61         case 'A':
62                 wantac = EARGF(usage());
63                 break;
64         case 'P':
65                 primary = 1;
66                 break;
67         case 'S':
68                 srvname = EARGF(usage());
69                 break;
70         case 'd':
71                 debug++;
72                 break;
73         case 'm':
74                 mtu = atoi(EARGF(usage()));
75                 break;
76         case 'k':
77                 keyspec = EARGF(usage());
78                 break;
79         case 'c':
80                 pktcompress = 1;
81                 break;
82         case 'C':
83                 hdrcompress = 1;
84                 break;
85         case 'x':
86                 pppnetmtpt = EARGF(usage());
87                 break;
88         default:
89                 usage();
90         }ARGEND
91
92         switch(argc){
93         default:
94                 usage();
95         case 0:
96                 dev = "ether0";
97                 break;
98         case 1:
99                 dev = argv[0];
100                 break;
101         }
102
103         fmtinstall('E', eipfmt);
104
105         atnotify(catchalarm, 1);
106         fd = pppoe(dev);
107         execppp(fd);
108 }
109
110 typedef struct Etherhdr Etherhdr;
111 struct Etherhdr {
112         uchar dst[6];
113         uchar src[6];
114         uchar type[2];
115 };
116
117 enum {
118         EtherHdrSz = 6+6+2,
119         EtherMintu = 60,
120
121         EtherPppoeDiscovery = 0x8863,
122         EtherPppoeSession = 0x8864,
123 };
124
125 uchar etherbcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
126
127 int
128 etherhdr(uchar *pkt, uchar *dst, int type)
129 {
130         Etherhdr *eh;
131
132         eh = (Etherhdr*)pkt;
133         memmove(eh->dst, dst, sizeof(eh->dst));
134         hnputs(eh->type, type);
135         return EtherHdrSz;
136 }
137
138 typedef struct Pppoehdr Pppoehdr;
139 struct Pppoehdr {
140         uchar verstype;
141         uchar code;
142         uchar sessid[2];
143         uchar length[2];        /* of payload */
144 };
145
146 enum {
147         PppoeHdrSz = 1+1+2+2,
148         Hdr = EtherHdrSz+PppoeHdrSz,
149 };
150
151 enum {
152         VersType = 0x11,
153
154         /* Discovery codes */
155         CodeDiscInit = 0x09,    /* discovery init */
156         CodeDiscOffer = 0x07,   /* discovery offer */
157         CodeDiscReq = 0x19,     /* discovery request */
158         CodeDiscSess = 0x65,    /* session confirmation */
159
160         /* Session codes */
161         CodeSession = 0x00,
162 };
163
164 int
165 pppoehdr(uchar *pkt, int code, int sessid)
166 {
167         Pppoehdr *ph;
168
169         ph = (Pppoehdr*)pkt;
170         ph->verstype = VersType;
171         ph->code = code;
172         hnputs(ph->sessid, sessid);
173         return PppoeHdrSz;
174 }
175
176 typedef struct Taghdr Taghdr;
177 struct Taghdr {
178         uchar type[2];
179         uchar length[2];        /* of value */
180 };
181
182 enum {
183         TagEnd = 0x0000,                /* end of tag list */
184         TagSrvName = 0x0101,    /* service name */
185         TagAcName = 0x0102,     /* access concentrator name */
186         TagHostUniq = 0x0103,   /* nonce */
187         TagAcCookie = 0x0104,   /* a.c. cookie */
188         TagVendSpec = 0x0105,   /* vendor specific */
189         TagRelaySessId = 0x0110,        /* relay session id */
190         TagSrvNameErr = 0x0201, /* service name error (ascii) */
191         TagAcSysErr = 0x0202,   /* a.c. system error */
192 };
193
194 int
195 tag(uchar *pkt, int type, void *value, int nvalue)
196 {
197         Taghdr *h;
198
199         h = (Taghdr*)pkt;
200         hnputs(h->type, type);
201         hnputs(h->length, nvalue);
202         memmove(pkt+4, value, nvalue);
203         return 4+nvalue;
204 }
205
206 /* PPPoE Active Discovery Initiation */
207 int
208 padi(uchar *pkt)
209 {
210         int sz, tagoff;
211         uchar *length;
212
213         sz = 0;
214         sz += etherhdr(pkt+sz, etherbcast, EtherPppoeDiscovery);
215         sz += pppoehdr(pkt+sz, CodeDiscInit, 0x0000);
216         length = pkt+sz-2;
217         tagoff = sz;
218         sz += tag(pkt+sz, TagSrvName, srvname, strlen(srvname));
219         hnputs(length, sz-tagoff);
220         return sz;
221 }
222
223 /* PPPoE Active Discovery Request */
224 int
225 padr(uchar *pkt)
226 {
227         int sz, tagoff;
228         uchar *length;
229
230         sz = 0;
231         sz += etherhdr(pkt+sz, etherdst, EtherPppoeDiscovery);
232         sz += pppoehdr(pkt+sz, CodeDiscReq, 0x0000);
233         length = pkt+sz-2;
234         tagoff = sz;
235         sz += tag(pkt+sz, TagSrvName, srvname, strlen(srvname));
236         sz += tag(pkt+sz, TagAcName, acname, strlen(acname));
237         if(cookie)
238                 sz += tag(pkt+sz, TagAcCookie, cookie, cookielen);
239         hnputs(length, sz-tagoff);
240         return sz;
241 }
242
243 void
244 ewrite(int fd, void *buf, int nbuf)
245 {
246         char e[ERRMAX], path[64];
247
248         if(write(fd, buf, nbuf) != nbuf){
249                 rerrstr(e, sizeof e);
250                 strcpy(path, "unknown");
251                 fd2path(fd, path, sizeof path);
252                 sysfatal("write %d to %s: %s", nbuf, path, e);
253         }
254 }
255
256 void*
257 emalloc(long n)
258 {
259         void *v;
260
261         v = malloc(n);
262         if(v == nil)
263                 sysfatal("out of memory");
264         return v;
265 }
266
267 int
268 aread(int timeout, int fd, void *buf, int nbuf)
269 {
270         int n;
271
272         alarmed = 0;
273         alarm(timeout);
274         n = read(fd, buf, nbuf);
275         alarm(0);
276         if(alarmed)
277                 return -1;
278         if(n < 0)
279                 sysfatal("read: %r");
280         if(n == 0)
281                 sysfatal("short read");
282         return n;
283 }
284
285 int
286 pktread(int timeout, int fd, void *buf, int nbuf, int (*want)(uchar*))
287 {
288         int n, t2;
289         n = -1;
290         for(t2=timeout; t2<16000; t2*=2){
291                 while((n = aread(t2, fd, buf, nbuf)) > 0){
292                         if(malformed(buf, n, EtherPppoeDiscovery)){
293                                 if(debug)
294                                         fprint(2, "dropping pkt: %r\n");
295                                 continue;
296                         }
297                         if(debug)
298                                 dumppkt(buf);
299                         if(!want(buf)){
300                                 if(debug)
301                                         fprint(2, "dropping unwanted pkt: %r\n");
302                                 continue;
303                         }
304                         break;
305                 }
306                 if(n > 0)
307                         break;
308         }
309         return n;
310 }
311
312 int
313 bad(char *reason)
314 {
315         werrstr(reason);
316         return 0;
317 }
318
319 void*
320 copy(uchar *s, int len)
321 {
322         uchar *v;
323
324         v = emalloc(len+1);
325         memmove(v, s, len);
326         v[len] = '\0';
327         return v;
328 }
329
330 void
331 clearstate(void)
332 {
333         sessid = -1;
334         free(acname);
335         acname = nil;
336         free(cookie);
337         cookie = nil;
338 }
339
340 int
341 wantoffer(uchar *pkt)
342 {
343         int i, len;
344         uchar *s;
345         Etherhdr *eh;
346         Pppoehdr *ph;
347
348         eh = (Etherhdr*)pkt;
349         ph = (Pppoehdr*)(pkt+EtherHdrSz);
350
351         if(ph->code != CodeDiscOffer)
352                 return bad("not an offer");
353         if(nhgets(ph->sessid) != 0x0000)
354                 return bad("bad session id");
355
356         for(i=0;; i++){
357                 if((s = findtag(pkt, TagSrvName, &len, i)) == nil)
358                         return bad("no matching service name");
359                 if(len == strlen(srvname) && memcmp(s, srvname, len) == 0)
360                         break;
361         }
362
363         if((s = findtag(pkt, TagAcName, &len, 0)) == nil)
364                 return bad("no ac name");
365         acname = copy(s, len);
366         if(wantac && strcmp(acname, wantac) != 0){
367                 free(acname);
368                 acname = nil;
369                 return bad("wrong ac name");
370         }
371
372         if(s = findtag(pkt, TagAcCookie, &len, 0)){
373                 cookie = copy(s, len);
374                 cookielen = len;
375         }
376         memmove(etherdst, eh->src, sizeof etherdst);
377         return 1;
378 }
379
380 int
381 wantsession(uchar *pkt)
382 {
383         int len;
384         uchar *s;
385         Pppoehdr *ph;
386
387         ph = (Pppoehdr*)(pkt+EtherHdrSz);
388
389         if(ph->code != CodeDiscSess)
390                 return bad("not a session confirmation");
391         if(nhgets(ph->sessid) == 0x0000)
392                 return bad("bad session id");
393         if(findtag(pkt, TagSrvName, &len, 0) == nil)
394                 return bad("no service name");
395         if(findtag(pkt, TagSrvNameErr, &len, 0))
396                 return bad("service name error");
397         if(findtag(pkt, TagAcSysErr, &len, 0))
398                 return bad("ac system error");
399
400         /*
401          * rsc said: ``if there is no -S option given, the current code
402          * waits for an offer with service name == "".
403          * that's silly.  it should take the first one it gets.''
404          */
405         if(srvname[0] != '\0') {
406                 if((s = findtag(pkt, TagSrvName, &len, 0)) == nil)
407                         return bad("no matching service name");
408                 if(len != strlen(srvname) || memcmp(s, srvname, len) != 0)
409                         return bad("no matching service name");
410         }
411         sessid = nhgets(ph->sessid);
412         return 1;
413 }
414
415 int
416 pppoe(char *ether)
417 {
418         char buf[64];
419         uchar pkt[1520];
420         int dfd, p[2], n, sfd, sz, timeout;
421         Pppoehdr *ph;
422
423         ph = (Pppoehdr*)(pkt+EtherHdrSz);
424         snprint(buf, sizeof buf, "%s!%d", ether, EtherPppoeDiscovery);
425         if((dfd = dial(buf, nil, nil, nil)) < 0)
426                 sysfatal("dial %s: %r", buf);
427
428         snprint(buf, sizeof buf, "%s!%d", ether, EtherPppoeSession);
429         if((sfd = dial(buf, nil, nil, nil)) < 0)
430                 sysfatal("dial %s: %r", buf);
431
432         for(timeout=250; timeout<16000; timeout*=2){
433                 clearstate();
434                 memset(pkt, 0, sizeof pkt);
435                 sz = padi(pkt);
436                 if(debug)
437                         dumppkt(pkt);
438                 if(sz < EtherMintu)
439                         sz = EtherMintu;
440                 ewrite(dfd, pkt, sz);
441
442                 if(pktread(timeout, dfd, pkt, sizeof pkt, wantoffer) < 0)
443                         continue;
444
445                 memset(pkt, 0, sizeof pkt);
446                 sz = padr(pkt);
447                 if(debug)
448                         dumppkt(pkt);
449                 if(sz < EtherMintu)
450                         sz = EtherMintu;
451                 ewrite(dfd, pkt, sz);
452
453                 if(pktread(timeout, dfd, pkt, sizeof pkt, wantsession) < 0)
454                         continue;
455
456                 break;
457         }
458         if(sessid < 0)
459                 sysfatal("could not establish session");
460
461         rfork(RFNOTEG);
462         if(pipe(p) < 0)
463                 sysfatal("pipe: %r");
464
465         switch(fork()){
466         case -1:
467                 sysfatal("fork: %r");
468         default:
469                 break;
470         case 0:
471                 close(p[1]);
472                 while((n = read(p[0], pkt+Hdr, sizeof pkt-Hdr)) > 0){
473                         etherhdr(pkt, etherdst, EtherPppoeSession);
474                         pppoehdr(pkt+EtherHdrSz, 0x00, sessid);
475                         hnputs(pkt+Hdr-2, n);
476                         sz = Hdr+n;
477                         if(debug > 1){
478                                 dumppkt(pkt);
479                                 hexdump(pkt, sz);
480                         }
481                         if(sz < EtherMintu)
482                                 sz = EtherMintu;
483                         if(write(sfd, pkt, sz) < 0){
484                                 if(debug)
485                                         fprint(2, "write to ether failed: %r");
486                                 _exits(nil);
487                         }
488                 }
489                 _exits(nil);
490         }
491
492         switch(fork()){
493         case -1:
494                 sysfatal("fork: %r");
495         default:
496                 break;
497         case 0:
498                 close(p[1]);
499                 while((n = read(sfd, pkt, sizeof pkt)) > 0){
500                         if(malformed(pkt, n, EtherPppoeSession)
501                         || ph->code != 0x00 || nhgets(ph->sessid) != sessid){
502                                 if(debug)
503                                         fprint(2, "malformed session pkt: %r\n");
504                                 if(debug)
505                                         dumppkt(pkt);
506                                 continue;
507                         }
508                         if(write(p[0], pkt+Hdr, nhgets(ph->length)) < 0){
509                                 if(debug)
510                                         fprint(2, "write to ppp failed: %r\n");
511                                 _exits(nil);
512                         }
513                 }
514                 _exits(nil);
515         }
516         close(p[0]);
517         return p[1];
518 }
519
520 void
521 execppp(int fd)
522 {
523         char *argv[16];
524         int argc;
525         char smtu[10];
526
527         argc = 0;
528         argv[argc++] = pppname;
529         snprint(smtu, sizeof(smtu), "-m%d", mtu);
530         argv[argc++] = smtu;
531         argv[argc++] = "-F";
532         if(debug)
533                 argv[argc++] = "-d";
534         if(primary)
535                 argv[argc++] = "-P";
536         if(hdrcompress)
537                 argv[argc++] = "-C";
538         if(pktcompress)
539                 argv[argc++] = "-c";
540         if(pppnetmtpt){
541                 argv[argc++] = "-x";
542                 argv[argc++] = pppnetmtpt;
543         }
544         if(keyspec){
545                 argv[argc++] = "-k";
546                 argv[argc++] = keyspec;
547         }
548         argv[argc] = nil;
549
550         dup(fd, 0);
551         dup(fd, 1);
552         exec(pppname, argv);
553         sysfatal("exec: %r");
554 }
555
556 uchar*
557 findtag(uchar *pkt, int tagtype, int *plen, int skip)
558 {
559         int len, sz, totlen;
560         uchar *tagdat, *v;
561         Etherhdr *eh;
562         Pppoehdr *ph;
563         Taghdr *t;
564
565         eh = (Etherhdr*)pkt;
566         ph = (Pppoehdr*)(pkt+EtherHdrSz);
567         tagdat = pkt+Hdr;
568
569         if(nhgets(eh->type) != EtherPppoeDiscovery)
570                 return nil;
571         totlen = nhgets(ph->length);
572
573         sz = 0;
574         while(sz+4 <= totlen){
575                 t = (Taghdr*)(tagdat+sz);
576                 v = tagdat+sz+4;
577                 len = nhgets(t->length);
578                 if(sz+4+len > totlen)
579                         break;
580                 if(nhgets(t->type) == tagtype && skip-- == 0){
581                         *plen = len;
582                         return v;
583                 }
584                 sz += 2+2+len;
585         }
586         return nil;     
587 }
588
589 void
590 dumptags(uchar *tagdat, int ntagdat)
591 {
592         int i,len, sz;
593         uchar *v;
594         Taghdr *t;
595
596         sz = 0;
597         while(sz+4 <= ntagdat){
598                 t = (Taghdr*)(tagdat+sz);
599                 v = tagdat+sz+2+2;
600                 len = nhgets(t->length);
601                 if(sz+4+len > ntagdat)
602                         break;
603                 fprint(2, "\t0x%x %d: ", nhgets(t->type), len);
604                 switch(nhgets(t->type)){
605                 case TagEnd:
606                         fprint(2, "end of tag list\n");
607                         break;
608                 case TagSrvName:
609                         fprint(2, "service '%.*s'\n", len, (char*)v);
610                         break;
611                 case TagAcName:
612                         fprint(2, "ac '%.*s'\n", len, (char*)v);
613                         break;
614                 case TagHostUniq:
615                         fprint(2, "nonce ");
616                 Hex:
617                         for(i=0; i<len; i++)
618                                 fprint(2, "%.2ux", v[i]);
619                         fprint(2, "\n");
620                         break;
621                 case TagAcCookie:
622                         fprint(2, "ac cookie ");
623                         goto Hex;
624                 case TagVendSpec:
625                         fprint(2, "vend spec ");
626                         goto Hex;
627                 case TagRelaySessId:
628                         fprint(2, "relay ");
629                         goto Hex;
630                 case TagSrvNameErr:
631                         fprint(2, "srverr '%.*s'\n", len, (char*)v);
632                         break;
633                 case TagAcSysErr:
634                         fprint(2, "syserr '%.*s'\n", len, (char*)v);
635                         break;
636                 }
637                 sz += 2+2+len;
638         }
639         if(sz != ntagdat)
640                 fprint(2, "warning: only dumped %d of %d bytes\n", sz, ntagdat);
641 }
642
643 void
644 dumppkt(uchar *pkt)
645 {
646         int et;
647         Etherhdr *eh;
648         Pppoehdr *ph;
649
650         eh = (Etherhdr*)pkt;
651         ph = (Pppoehdr*)(pkt+EtherHdrSz);
652         et = nhgets(eh->type);
653
654         fprint(2, "%E -> %E type 0x%x\n", 
655                 eh->src, eh->dst, et);
656         switch(et){
657         case EtherPppoeDiscovery:
658         case EtherPppoeSession:
659                 fprint(2, "\tvers %d type %d code 0x%x sessid 0x%x length %d\n",
660                         ph->verstype>>4, ph->verstype&15,
661                         ph->code, nhgets(ph->sessid), nhgets(ph->length));
662                 if(et == EtherPppoeDiscovery)
663                         dumptags(pkt+Hdr, nhgets(ph->length));
664         }
665 }
666
667 int
668 malformed(uchar *pkt, int n, int wantet)
669 {
670         int et;
671         Etherhdr *eh;
672         Pppoehdr *ph;
673
674         eh = (Etherhdr*)pkt;
675         ph = (Pppoehdr*)(pkt+EtherHdrSz);
676
677         if(n < Hdr || n < Hdr+nhgets(ph->length)){
678                 werrstr("packet too short %d != %d", n, Hdr+nhgets(ph->length));
679                 return 1;
680         }
681
682         et = nhgets(eh->type);
683         if(et != wantet){
684                 werrstr("wrong ethernet packet type 0x%x != 0x%x", et, wantet);
685                 return 1;
686         }
687
688         return 0;
689 }
690
691 void
692 hexdump(uchar *a, int na)
693 {
694         int i;
695         char buf[80];
696
697         buf[0] = '\0';
698         for(i=0; i<na; i++){
699                 sprint(buf+strlen(buf), " %.2ux", a[i]);
700                 if(i%16 == 7)
701                         sprint(buf+strlen(buf), " --");
702                 if(i%16==15){
703                         sprint(buf+strlen(buf), "\n");
704                         write(2, buf, strlen(buf));
705                         buf[0] = 0;
706                 }
707         }
708         if(i%16){
709                 sprint(buf+strlen(buf), "\n");
710                 write(2, buf, strlen(buf));
711         }
712 }