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