#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "pppoe.h" #define UNIQLEN 8 #define MAXPKTLEN 1514 /* forced by Ethernet */ #define PPP_FLAG 0x7e #define PPP_ESC 0x7d #define PPP_MOD 0x20 #define PPP_INITFCS 0xffff #define PPP_GOODFCS 0xf0b8 #define PPP_FCSCHAR(fcs,c) (((fcs)>>8)^ppp_fcstab[((fcs)^(c))&0xff]) typedef struct tag TAG; struct tag { unsigned short int type; unsigned short int length; const unsigned char *value; } ; static const char *ifname; static const char *pppname; static int iis; static int bpffd; static int randfd; static const char *bpfname; static int bpfblen; static char *bpfbuf; static unsigned char mymac[6]; static int state; #define S_INITIATE 1 /* sending PADI packets, looking for PADO */ #define S_REQUEST 2 /* sent PADR packet, looking for PADS */ #define S_SESSION 3 /* exchanging data packets */ static unsigned short int session_id; static struct timeval timeout; static char uniq[UNIQLEN]; static unsigned char peermac[6]; static int ntags; static TAG *tags; static int tagsalloc; static unsigned char opkt[MAXPKTLEN]; static int pktoff; static char ptymname[11]; static int ptymfd; static char ptysname[11]; static int ptysfd; static int devnull; static sigset_t caught_sigs; static int pppd_pid; static int pppdeathpipe; static unsigned char ppp_pty_frame[MAXPKTLEN+2]; static int ppp_pty_flen; static int ppp_pty_fcs; static int ppp_pty_esc; static const unsigned short int ppp_fcstab[256] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; static int plevel; #define P_NONE 0 #define P_WARN 1 #define P_NOTE 2 #define P_INFO 3 #define P_DEBUG 4 static int pppdebug = 0; static void p(int, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void p(int level, const char *fmt, ...) { va_list ap; if (plevel >= level) { va_start(ap,fmt); vprintf(fmt,ap); va_end(ap); } } static void catch_signal(int sig, void (*handler)(int)) { struct sigaction sa; sigaddset(&caught_sigs,sig); sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(sig,&sa,0); } static void sig_to_pppd(int sig) { if (pppd_pid > 1) kill(pppd_pid,sig); } static void sig_to_self(int sig, void (*handler)(int)) { struct sigaction sa; sigset_t m; sa.sa_handler = SIG_DFL; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(sig,&sa,0); sigemptyset(&m); sigaddset(&m,sig); sigprocmask(SIG_UNBLOCK,&m,0); kill(getpid(),sig); /* if we survived... */ catch_signal(sig,handler); } static void sig_to_pppd_or_self(int sig) { if (pppd_pid > 1) sig_to_pppd(sig); else sig_to_self(sig,sig_to_pppd_or_self); } static void sig_to_pppd_and_self(int sig) { sig_to_pppd(sig); sig_to_self(sig,sig_to_pppd_and_self); } static void setup(void) { int i; char *n; char *d; struct ifreq ifr; struct bpf_version bpf_vers; static struct bpf_insn bpf_code[] = { BPF_STMT(BPF_LD+BPF_H+BPF_ABS,12), BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K,ETHERTYPE_PPPOE,1,0), BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K,ETHERTYPE_PPPOEDISC,0,5), BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0), BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K,0/*runtime*/,0,3), #define CODE_OFFSET_ETH_4_AT_0 4 BPF_STMT(BPF_LD+BPF_H+BPF_ABS,4), BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K,0/*runtime*/,0,1), #define CODE_OFFSET_ETH_2_AT_4 6 BPF_STMT(BPF_RET+BPF_K,-1), BPF_STMT(BPF_RET+BPF_K,0) }; struct bpf_program bpf_prog; struct termios tio; struct ifconf ifc; struct ifreq *ifcr; int l; struct sockaddr_dl *sdl; int havemac; char ifcbuf[8192] __attribute__((__aligned__)); devnull = open("/dev/null",O_RDWR,0); if (devnull < 0) { fprintf(stderr,"%s: /dev/null: %s\n",__progname,strerror(errno)); exit(1); } while ((devnull < 3) && (devnull >= 0)) devnull = dup(devnull); iis = socket(AF_LOCAL,SOCK_DGRAM,0); if (iis < 0) { fprintf(stderr,"%s: interface ioctl socket: %s\n",__progname,strerror(errno)); exit(1); } randfd = open("/dev/urandom",O_RDONLY,0); if (randfd < 0) { fprintf(stderr,"%s: /dev/urandom: %s\n",__progname,strerror(errno)); exit(1); } n = 0; d = 0; for (i=0;;i++) { free(n); asprintf(&n,"bpf%d",i); free(d); asprintf(&d,"/dev/%s",n); bpffd = open(d,O_RDWR,0); if (bpffd >= 0) break; if (errno == EBUSY) continue; fprintf(stderr,"%s: %s: %s\n",__progname,d,strerror(errno)); exit(1); } bpfname = n; free(d); bpfblen = 65536; if (ioctl(bpffd,BIOCSBLEN,&bpfblen) < 0) { fprintf(stderr,"%s: BIOCSBLEN on %s: %s\n",__progname,bpfname,strerror(errno)); exit(1); } strncpy(&ifr.ifr_name[0],ifname,sizeof(ifr.ifr_name)); if (ioctl(bpffd,BIOCSETIF,&ifr) < 0) { fprintf(stderr,"%s: BIOCSETIF %s on %s: %s\n",__progname,ifname,bpfname,strerror(errno)); exit(1); } i = 1; if (ioctl(bpffd,BIOCIMMEDIATE,&i) < 0) { fprintf(stderr,"%s: enable BIOCIMMEDIATE on %s: %s\n",__progname,bpfname,strerror(errno)); exit(1); } if (ioctl(bpffd,BIOCVERSION,&bpf_vers) < 0) { fprintf(stderr,"%s: BIOCVERSION on %s: %s\n",__progname,bpfname,strerror(errno)); exit(1); } if ( (bpf_vers.bv_major != BPF_MAJOR_VERSION) || (bpf_vers.bv_minor < BPF_MINOR_VERSION) ) { fprintf(stderr,"%s: incompatible BPF filter version: program %d.%d, kernel %d.%d\n",__progname,BPF_MAJOR_VERSION,BPF_MINOR_VERSION,bpf_vers.bv_major,bpf_vers.bv_minor); exit(1); } havemac = 0; ifc.ifc_buf = &ifcbuf[0]; ifc.ifc_len = sizeof(ifcbuf); if (ioctl(iis,SIOCGIFCONF,&ifc) < 0) { fprintf(stderr,"%s: SIOCGIFCONF: %s\n",__progname,strerror(errno)); exit(1); } i = 0; while (i < ifc.ifc_len) { ifcr = (void *) (i+(char *)ifc.ifc_req); l = ifcr->ifr_addr.sa_len; if (l < sizeof(struct sockaddr)) l = sizeof(struct sockaddr); l += sizeof(ifcr->ifr_name); if (i+l > ifc.ifc_len) { fprintf(stderr,"%s: SIOCGIFCONF partial ifreq (want %d, %d left)\n",__progname,l,ifc.ifc_len-i); break; } if (ifcr->ifr_addr.sa_family == AF_LINK) { if (i > 0) bcopy(ifcr,&ifcbuf[0],l); ifcr = (void *) &ifcbuf[0]; sdl = (struct sockaddr_dl *) &ifcr->ifr_addr; if ( !bcmp(&sdl->sdl_data[0],ifname,sdl->sdl_nlen) && !ifname[sdl->sdl_nlen] ) { if (sdl->sdl_alen == 6) { if (havemac) { fprintf(stderr,"%s: warning: multiple MAC addresses found for %s, using last\n",__progname,ifname); if (havemac == 1) fprintf(stderr,"\t%02x:%02x:%02x:%02x:%02x:%02x\n",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]); } bcopy(LLADDR(sdl),&mymac[0],6); if (havemac) fprintf(stderr,"\t%02x:%02x:%02x:%02x:%02x:%02x\n",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]); havemac ++; } } } i += l; } if (! havemac) { fprintf(stderr,"%s: no MAC address found for %s\n",__progname,ifname); exit(1); } p(P_INFO,"%s -> %02x:%02x:%02x:%02x:%02x:%02x\n",ifname,mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]); /* Should these two be in network byte order? */ bpf_code[CODE_OFFSET_ETH_4_AT_0].k = (mymac[0] << 24) | (mymac[1] << 16) | (mymac[2] << 8) | mymac[3]; bpf_code[CODE_OFFSET_ETH_2_AT_4].k = (mymac[4] << 8) | mymac[5]; bpf_prog.bf_len = sizeof(bpf_code) / sizeof(bpf_code[0]); bpf_prog.bf_insns = &bpf_code[0]; if (ioctl(bpffd,BIOCSETF,&bpf_prog) < 0) { fprintf(stderr,"%s: BIOCSETF on %s: %s\n",__progname,bpfname,strerror(errno)); exit(1); } bpfbuf = malloc(bpfblen); for (i=0;i<256;i++) { static const char ptychr[16] = "pqrstuvwxyzPQRST"; sprintf(&ptymname[0],"/dev/pty%c%x",ptychr[i>>4],i&0xf); ptymfd = open(&ptymname[0],O_RDWR,0); if (ptymfd >= 0) { strcpy(&ptysname[0],&ptymname[0]); ptysname[5] = 't'; ptysfd = open(&ptysname[0],O_RDWR,0); if (ptysfd >= 0) break; close(ptymfd); ptymfd = -1; } else { if (errno == ENOENT) break; } } if (ptymfd < 0) { fprintf(stderr,"%s: all ptys in use\n",__progname); exit(1); } tio.c_iflag = 0; tio.c_oflag = 0; tio.c_cflag = CS8 | CREAD | CLOCAL; tio.c_lflag = 0; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; tcsetattr(ptysfd,TCSANOW,&tio); tags = 0; ntags = 0; tagsalloc = 0; pppdeathpipe = -1; ppp_pty_flen = 0; ppp_pty_esc = 0; pppd_pid = -1; sigemptyset(&caught_sigs); catch_signal(SIGHUP,sig_to_pppd_or_self); catch_signal(SIGINT,sig_to_pppd_or_self); catch_signal(SIGTERM,sig_to_pppd_or_self); catch_signal(SIGTSTP,sig_to_pppd_and_self); catch_signal(SIGCONT,sig_to_pppd); } static void init_packet(void) { pktoff = 0; } static int pktlenchk(int len) { if (pktoff+len > MAXPKTLEN) { if (pktoff <= MAXPKTLEN) p(P_WARN,"output packet overflow\n"); pktoff = MAXPKTLEN + 1; return(1); } return(0); } static void append_packet(const void *data, int len) { if (pktlenchk(len)) return; bcopy(data,&opkt[pktoff],len); pktoff += len; } static void append_packet_8(unsigned char v) { if (pktlenchk(1)) return; opkt[pktoff++] = v; } static void append_packet_16(unsigned short int v) { if (pktlenchk(2)) return; opkt[pktoff++] = v >> 8; opkt[pktoff++] = v & 0xff; } static int skip_packet(int n) { if (pktlenchk(n)) return(-1); pktoff += n; return(pktoff-n); } static void write_packet_16(int at, unsigned short int v) { if ((at < 0) || (at+2 > pktoff)) { fprintf(stderr,"write_packet_16 outside packet\n"); abort(); } opkt[at] = v >> 8; opkt[at+1] = v & 0xff; } static int packet_length(void) { return(pktoff); } static void pad_packet(void) { if (pktoff < ETHER_MIN_LEN-ETHER_CRC_LEN) { bzero(&opkt[pktoff],ETHER_MIN_LEN-ETHER_CRC_LEN-pktoff); pktoff = ETHER_MIN_LEN - ETHER_CRC_LEN; } } static void send_padi(void) { int len; int lenat; int paybase; init_packet(); /* Ethernet header */ append_packet("\xff\xff\xff\xff\xff\xff",6); append_packet(&mymac[0],6); append_packet_16(ETHERTYPE_PPPOEDISC); /* PPPoE header */ append_packet_8((PPPOE_VER<<4)|PPPOE_TYPE); append_packet_8(PPPOE_CODE_PADI); append_packet_16(0); /* SESSION_ID */ lenat = skip_packet(2); /* PPPoE payload */ paybase = skip_packet(0); /* Zero-length Service-Name */ append_packet_16(PPPOE_TAG_SERV); append_packet_16(0); /* UNIQLEN-byte random Host-Uniq */ append_packet_16(PPPOE_TAG_HUNIQ); append_packet_16(UNIQLEN); read(randfd,&uniq[0],UNIQLEN); append_packet(&uniq[0],UNIQLEN); /* Set length */ write_packet_16(lenat,packet_length()-paybase); pad_packet(); len = packet_length(); write(bpffd,&opkt[0],len); { const unsigned char *pp; int i; p(P_DEBUG,"wrote PADI packet: [%d]",len); for (pp=&opkt[0],i=len;i>0;pp++,i--) p(P_DEBUG," %02x",*pp); p(P_DEBUG,"\n"); } } static void process_timeout(void) { switch (state) { case S_REQUEST: state = S_INITIATE; gettimeofday(&timeout,0); /* fall through */ case S_INITIATE: send_padi(); timeout.tv_sec += 5; break; } } static void scan_tags(const unsigned char *dp, int len) { unsigned short int type; unsigned short int length; ntags = 0; while (len > 0) { if (len < 4) { p(P_WARN,"note: too-short tag header (length %d) ignored\n",len); break; } type = (dp[0] << 8) | dp[1]; length = (dp[2] << 8) | dp[3]; if (length > len-4) { p(P_WARN,"note: tag overruns packet (length %d, %d left) - ignored\n",length,len-4); break; } if (ntags >= tagsalloc) { tags = realloc(tags,sizeof(*tags)*++tagsalloc); } tags[ntags].type = type; tags[ntags].length = length; tags[ntags].value = dp + 4; ntags ++; dp += 4 + length; len -= 4 + length; } } static int find_next_tag(unsigned short int type, int i) { for (i++;i= 0) { append_packet_16(PPPOE_TAG_ACCOOK); append_packet_16(tags[i].length); append_packet(tags[i].value,tags[i].length); i = find_next_tag(PPPOE_TAG_ACCOOK,i); } /* Copy of any Relay-Session-Ids */ i = find_first_tag(PPPOE_TAG_RELAYID); while (i >= 0) { append_packet_16(PPPOE_TAG_RELAYID); append_packet_16(tags[i].length); append_packet(tags[i].value,tags[i].length); i = find_next_tag(PPPOE_TAG_RELAYID,i); } /* Set length */ write_packet_16(lenat,packet_length()-paybase); pad_packet(); len = packet_length(); write(bpffd,&opkt[0],len); { const unsigned char *pp; int i; p(P_DEBUG,"wrote PADR packet: [%d]",len); for (pp=&opkt[0],i=len;i>0;pp++,i--) p(P_DEBUG," %02x",*pp); p(P_DEBUG,"\n"); } } static void send_padt(void) { int len; int lenat; int paybase; init_packet(); /* Ethernet header */ append_packet(&peermac[0],6); append_packet(&mymac[0],6); append_packet_16(ETHERTYPE_PPPOEDISC); /* PPPoE header */ append_packet_8((PPPOE_VER<<4)|PPPOE_TYPE); append_packet_8(PPPOE_CODE_PADT); append_packet_16(session_id); lenat = skip_packet(2); /* PPPoE payload */ paybase = skip_packet(0); /* Set length */ write_packet_16(lenat,packet_length()-paybase); pad_packet(); len = packet_length(); write(bpffd,&opkt[0],len); { const unsigned char *pp; int i; p(P_DEBUG,"wrote PADT packet: [%d]",len); for (pp=&opkt[0],i=len;i>0;pp++,i--) p(P_DEBUG," %02x",*pp); p(P_DEBUG,"\n"); } } static void fork_pppd(void) { int kid; int i; int e; int deathpipe[2]; int forkpipe[2]; int pidpipe[2]; sigset_t prevmask; sigprocmask(SIG_BLOCK,&caught_sigs,&prevmask); if ( (pipe(pidpipe) < 0) || (pipe(forkpipe) < 0) || (pipe(deathpipe) < 0) ) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid != 0) { char c; close(forkpipe[1]); if (read(forkpipe[0],&c,1) == 1) { for (i=0;i2;i--) if (i != deathpipe[1]) close(i); wait(&i); _exit(0); } p(P_INFO,"forked pppd, pid %d\n",getpid()); fflush(stdout); sigprocmask(SIG_SETMASK,&prevmask,0); fcntl(forkpipe[1],F_SETFD,1); dup2(ptysfd,0); dup2(0,1); for (i=getdtablesize()-1;i>2;i--) if (i != forkpipe[1]) close(i); { const char *args[1000]; /* hope 1000 is enough! */ const char **ap; ap = &args[0]; /* XXX there should be a way to configure things a user might reasonably want to tweak, like IPX support, at run time.... */ *ap++ = "pppd"; *ap++ = "115200"; /* XXX - pppd demands(!) a speed in some versions */ *ap++ = "connect"; *ap++ = "/usr/bin/true"; *ap++ = "ipcp-accept-local"; *ap++ = "ipcp-accept-remote"; *ap++ = "local"; *ap++ = "mru"; *ap++ = "1400"; *ap++ = "mtu"; *ap++ = "1400"; *ap++ = "name"; *ap++ = pppname; *ap++ = "noaccomp"; *ap++ = "noauth"; *ap++ = "nobsdcomp"; *ap++ = "noccp"; *ap++ = "nodefaultroute"; *ap++ = "nodeflate"; *ap++ = "nodetach"; *ap++ = "noipdefault"; *ap++ = "noipx"; *ap++ = "nopcomp"; *ap++ = "nopersist"; *ap++ = "nopredictor1"; *ap++ = "noproxyarp"; *ap++ = "novj"; *ap++ = "novjccomp"; if (pppdebug) *ap++ = "debug"; *ap = 0; execv("/usr/sbin/pppd",(const void *)&args[0]); } e = errno; write(forkpipe[1],"e",1); write(forkpipe[1],&e,sizeof(e)); _exit(0); } static void send_frame_to_pppd(const void *data, int len) { unsigned char stuffup[3+(len*2)+4+1]; unsigned char *sp; const unsigned char *dp; unsigned short int fcs; static void addchar(unsigned char ch) { switch (ch) { case PPP_FLAG: case PPP_ESC: case 0x00 ... 0x1f: case 0x7f ... 0x9f: *sp++ = PPP_ESC; *sp++ = ch ^ PPP_MOD; break; default: *sp++ = ch; break; } fcs = PPP_FCSCHAR(fcs,ch); } sp = &stuffup[0]; fcs = PPP_INITFCS; *sp++ = PPP_FLAG; addchar(0xff); addchar(0x03); for (dp=data;len>0;dp++,len--) addchar(*dp); fcs = 0xffff & ~fcs; { unsigned short int fcssave; fcssave = fcs; addchar(fcssave&0xff); addchar(fcssave>>8); } *sp++ = PPP_FLAG; write(ptymfd,&stuffup[0],sp-&stuffup[0]); { int i; int l; l = sp - &stuffup[0]; p(P_DEBUG,"wrote to tty %d:",l); for (i=0;i0;dp++,i--) p(P_DEBUG," %02x",*dp); p(P_DEBUG,"\n"); } dp = data; if (len < 14) { p(P_WARN,"received runt packet (len %d)\n",len); return; } if (bcmp(dp,&mymac[0],6)) { p(P_WARN,"received packet not for me(?) [for %02x:%02x:%02x:%02x:%02x:%02x]\n",dp[0],dp[1],dp[2],dp[3],dp[4],dp[5]); return; } etype = (dp[12] << 8) | dp[13]; if ((etype != ETHERTYPE_PPPOEDISC) && (etype != ETHERTYPE_PPPOE)) { p(P_WARN,"received packet of wrong ethertype (%04x)\n",etype); return; } bcopy(dp+6,&srcmac[0],6); dp += 14; len -= 14; if (len < 6) { p(P_WARN,"received packet too short to hold PPPoE header (%d)\n",len); return; } if ((dp[0] & 0xf0) != 0x10) { p(P_WARN,"received packet with wrong ver (%d)\n",dp[0]>>4); return; } if ((dp[0] & 0x0f) != 0x01) { p(P_WARN,"received packet with wrong ver (%d)\n",dp[0]&0xf); return; } if ((dp[4]<<8)+dp[5] > len-6) { p(P_WARN,"length PPPoE header too large (%d, only %d available)\n",(dp[4]<<8)+dp[5],len-6); return; } if ((dp[4]<<8)+dp[5] != len-6) { p(P_DEBUG,"[shrinking length from %d to %d]\n",len,(dp[4]<<8)+dp[5]+6); len = (dp[4]<<8) + dp[5] + 6; } switch (state) { case S_INITIATE: { int acnx; int i; /* expecting PADO */ if (etype != ETHERTYPE_PPPOEDISC) { p(P_WARN,"received Session-phase packet in Initate state\n"); return; } if (dp[1] != PPPOE_CODE_PADO) { p(P_WARN,"received non-PADO (code %d) packet in Initate state\n",dp[1]); return; } if (dp[2] || dp[3]) { p(P_WARN,"received PADO packet with session ID %02x%02x\n",dp[2],dp[3]); return; } scan_tags(dp+6,len-6); acnx = find_first_tag(PPPOE_TAG_ACNAME); if (acnx < 0) { p(P_WARN,"received PADO without an AC-Name tag\n"); return; } i = find_first_tag(PPPOE_TAG_SERV); while (i >= 0) { if (tags[i].length == 0) break; i = find_next_tag(PPPOE_TAG_SERV,i); } if (i < 0) { p(P_WARN,"received PADO without our Service-Name tag\n"); return; } i = find_first_tag(PPPOE_TAG_HUNIQ); while (i >= 0) { if ( (tags[i].length == UNIQLEN) && !bcmp(tags[i].value,&uniq[0],UNIQLEN) ) break; i = find_next_tag(PPPOE_TAG_HUNIQ,i); } if (i < 0) { p(P_WARN,"received PADO without our Host-Uniq tag\n"); return; } bcopy(&srcmac[0],&peermac[0],6); p(P_INFO,"PADO from AC \"%.*s\" at %02x:%02x:%02x:%02x:%02x:%02x\n",tags[acnx].length,tags[acnx].value,peermac[0],peermac[1],peermac[2],peermac[3],peermac[4],peermac[5]); send_padr(); state = S_REQUEST; } break; case S_REQUEST: { int sid; int i; /* expecting PADS */ if (etype != ETHERTYPE_PPPOEDISC) { p(P_WARN,"received Session-phase packet in Initate state\n"); return; } if (dp[1] != PPPOE_CODE_PADS) { p(P_WARN,"received non-PADS (code %d) packet in Request state\n",dp[1]); return; } if (bcmp(&srcmac[0],&peermac[0],6)) { p(P_WARN,"received PADS packet from someone (%02x:%02x:%02x:%02x:%02x:%02x) other than our peer (%02x:%02x:%02x:%02x:%02x:%02x)\n",srcmac[0],srcmac[1],srcmac[2],srcmac[3],srcmac[4],srcmac[5],peermac[0],peermac[1],peermac[2],peermac[3],peermac[4],peermac[5]); return; } sid = (dp[2] << 8) | dp[3]; scan_tags(dp+6,len-6); i = find_first_tag(PPPOE_TAG_HUNIQ); while (i >= 0) { if ( (tags[i].length == UNIQLEN) && !bcmp(tags[i].value,&uniq[0],UNIQLEN) ) break; i = find_next_tag(PPPOE_TAG_HUNIQ,i); } if (i < 0) { p(P_WARN,"received PADS without our Host-Uniq tag\n"); return; } i = find_first_tag(PPPOE_TAG_SNAMEERR); if (i >= 0) { p(P_WARN,"received PADS with Service-Name-Error tag\n"); return; } session_id = sid; p(P_INFO,"PADS with session ID %04x\n",sid); state = S_SESSION; fork_pppd(); } break; case S_SESSION: { int sid; /* expecting data or maybe a PADT */ switch (etype) { case ETHERTYPE_PPPOEDISC: if (dp[1] != PPPOE_CODE_PADT) { p(P_WARN,"received non-PADT (code %d) discover packet in Session state\n",dp[1]); return; } if (bcmp(&srcmac[0],&peermac[0],6)) { p(P_WARN,"received PADT packet from someone (%02x:%02x:%02x:%02x:%02x:%02x) other than our peer (%02x:%02x:%02x:%02x:%02x:%02x)\n",srcmac[0],srcmac[1],srcmac[2],srcmac[3],srcmac[4],srcmac[5],peermac[0],peermac[1],peermac[2],peermac[3],peermac[4],peermac[5]); return; } sid = (dp[2] << 8) | dp[3]; if (sid != session_id) { p(P_WARN,"received PADT packet for wrong session (packet %04x, current %04x)\n",sid,session_id); return; } p(P_INFO,"Exiting on PADT\n"); exit(0); break; case ETHERTYPE_PPPOE: if (dp[1] != PPPOE_CODE_DATA) { p(P_WARN,"received non-DATA (code %d) session packet\n",dp[1]); return; } if (bcmp(&srcmac[0],&peermac[0],6)) { p(P_WARN,"received DATA packet from someone (%02x:%02x:%02x:%02x:%02x:%02x) other than our peer (%02x:%02x:%02x:%02x:%02x:%02x)\n",srcmac[0],srcmac[1],srcmac[2],srcmac[3],srcmac[4],srcmac[5],peermac[0],peermac[1],peermac[2],peermac[3],peermac[4],peermac[5]); return; } sid = (dp[2] << 8) | dp[3]; if (sid != session_id) { p(P_WARN,"received DATA packet for wrong session (packet %04x, current %04x)\n",sid,session_id); return; } send_frame_to_pppd(dp+6,len-6); break; } } break; } } static void process_bpf_input(void) { int n; struct bpf_hdr h; int l; char *bp; n = read(bpffd,bpfbuf,bpfblen); if (n < 0) { fprintf(stderr,"%s: bpf read: %s\n",__progname,strerror(errno)); exit(1); } bp = bpfbuf; while (n > 0) { if (n < sizeof(h)) { fprintf(stderr,"%s: partial header!\n",__progname); break; } bcopy(bp,&h,sizeof(h)); if (h.bh_caplen != h.bh_datalen) { fprintf(stderr,"%s: truncated packet(?), ignored\n",__progname); } else { l = h.bh_hdrlen + h.bh_caplen; if (l > n) { fprintf(stderr,"%s: partial packet(?) (l=%d n=%d)\n",__progname,l,n); break; } process_packet(bp+h.bh_hdrlen,h.bh_caplen); l = BPF_WORDALIGN(l); if (l > n) l = n; bp += l; n -= l; } } } static void send_data(const void *pppframe, int ppplen) { int len; int lenat; int paybase; if (state != S_SESSION) return; init_packet(); /* Ethernet header */ append_packet(&peermac[0],6); append_packet(&mymac[0],6); append_packet_16(ETHERTYPE_PPPOE); /* PPPoE header */ append_packet_8((PPPOE_VER<<4)|PPPOE_TYPE); append_packet_8(PPPOE_CODE_DATA); append_packet_16(session_id); lenat = skip_packet(2); /* PPPoE payload */ paybase = skip_packet(0); append_packet(pppframe,ppplen); /* Set length */ write_packet_16(lenat,packet_length()-paybase); pad_packet(); len = packet_length(); write(bpffd,&opkt[0],len); { const unsigned char *pp; int i; p(P_DEBUG,"wrote DATA packet: [%d]",len); for (pp=&opkt[0],i=len;i>0;pp++,i--) p(P_DEBUG," %02x",*pp); p(P_DEBUG,"\n"); } } static void process_pty_input(void) { unsigned char ibuf[8192]; int n; int i; int c; n = read(ptymfd,&ibuf[0],sizeof(ibuf)); if (n < 0) { fprintf(stderr,"%s: pty read: %s\n",__progname,strerror(errno)); exit(1); } p(P_DEBUG,"pty read: got %d:",n); for (i=0;i 0) && !ppp_pty_esc && (ppp_pty_fcs == PPP_GOODFCS) ) { if ( (ppp_pty_flen > 4) && (ppp_pty_frame[0] == 0xff) && (ppp_pty_frame[1] == 0x03) ) { send_data(&ppp_pty_frame[2],ppp_pty_flen-4); } else { send_data(&ppp_pty_frame[0],ppp_pty_flen-2); } } ppp_pty_flen = 0; ppp_pty_fcs = 0xffff; ppp_pty_esc = 0; break; case PPP_ESC: ppp_pty_esc = 1; break; default: if (ppp_pty_esc) { c = ibuf[i] ^ PPP_MOD; ppp_pty_esc = 0; } else { c = ibuf[i]; } if ((ppp_pty_flen >= 0) && (ppp_pty_flen < MAXPKTLEN+2)) { ppp_pty_frame[ppp_pty_flen++] = c; ppp_pty_fcs = PPP_FCSCHAR(ppp_pty_fcs,c); } break; } } } static void process_ppp_death(void) { while (wait3(0,WNOHANG,0) > 0) ; p(P_INFO,"exiting on ppp death\n"); close(pppdeathpipe); send_padt(); exit(0); /* state = S_INITIATE; gettimeofday(&timeout,0); timeout.tv_sec = 1; */ } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { if (! ifname) { ifname = *av; } else if (! pppname) { pppname = *av; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-if")) { WANTARG(); ifname = av[skip]; continue; } else if (!strcmp(*av,"-name")) { WANTARG(); pppname = av[skip]; continue; } else if (!strcmp(*av,"-debug")) { WANTARG(); if (!strcasecmp(av[skip],"none")) plevel = P_NONE; else if (!strcasecmp(av[skip],"warn")) plevel = P_WARN; else if (!strcasecmp(av[skip],"note")) plevel = P_NOTE; else if (!strcasecmp(av[skip],"info")) plevel = P_INFO; else if (!strcasecmp(av[skip],"debug")) plevel = P_DEBUG; else { fprintf(stderr,"%s: bad -debug arg `%s'\n",__progname,av[skip]); fprintf(stderr,"%s: valid args are: none warn note info debug\n",__progname); errs ++; } continue; } else if (!strcmp(*av,"-pppdebug")) { pppdebug = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (! ifname) { fprintf(stderr,"%s: must specify an interface name\n",__progname); errs ++; } if (! pppname) { fprintf(stderr,"%s: must specify PPP name\n",__progname); errs ++; } if (errs) exit(1); } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); setup(); state = S_INITIATE; gettimeofday(&timeout,0); while (1) { struct timeval *seltimeout; struct timeval delta; fd_set rfds; int srv; seltimeout = 0; FD_ZERO(&rfds); FD_SET(bpffd,&rfds); switch (state) { case S_INITIATE: case S_REQUEST: { struct timeval now; gettimeofday(&now,0); if ( (now.tv_sec > timeout.tv_sec) || ( (now.tv_sec == timeout.tv_sec) && (now.tv_usec >= timeout.tv_usec) ) ) { delta.tv_sec = 0; delta.tv_usec = 0; } else if (timeout.tv_usec >= now.tv_usec) { delta.tv_sec = timeout.tv_sec - now.tv_sec; delta.tv_usec = timeout.tv_usec - now.tv_usec; } else { delta.tv_sec = timeout.tv_sec - 1 - now.tv_sec; delta.tv_usec = timeout.tv_usec + 1000000 - now.tv_usec; } seltimeout = δ } break; case S_SESSION: FD_SET(ptymfd,&rfds); FD_SET(pppdeathpipe,&rfds); break; } srv = select(FD_SETSIZE,&rfds,0,0,seltimeout); if (srv < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: select: %s\n",__progname,strerror(errno)); exit(1); } if (srv == 0) { process_timeout(); } else { if (FD_ISSET(bpffd,&rfds)) process_bpf_input(); if (state == S_SESSION) { if (FD_ISSET(ptymfd,&rfds)) process_pty_input(); if (FD_ISSET(pppdeathpipe,&rfds)) process_ppp_death(); } } } }