/* pppoebr.c * * A program that will accept PPPoE packets on one network interface * and forward them to another interface/address. Allows for both * bridging and [limited] routing of PPPoE packets. * * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: pppoed.c,v 1.5 2004/08/17 18:01:53 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. * */ #include #include #include #include #include #include #include #include #include #include #include #include /* from the packet man page so that we compile under libc5 or glibc */ #include #include /* for the glibc version number */ #if __GLIBC__ >= 2 && ((__GLIBC_MINOR >= 1) || (__GLIBC_MINOR__ >= 1)) #include #include /* the L2 protocols */ #include /* for ifreq */ #else #include #include #include #include /* The L2 protocols */ #endif #include #include "../../include/pretty_dump.h" #define ETH_P_PPPoE_DISC 0x8863 #define ETH_P_PPPoE_SESN 0x8864 enum { stPPPOE_DISC_PADI, stPPPOE_DISC_PADR, stPPPOE_SESN, } state = stPPPOE_DISC_PADI; #define PPPoE_CODE_PADI 0x09 #define PPPoE_CODE_PADO 0x07 #define PPPoE_CODE_PADR 0x19 #define PPPoE_CODE_PADS 0x65 #define PPPoE_CODE_PADT 0xa7 unsigned char *relay_tag; unsigned relay_tag_len; char *site_name = "hse"; unsigned char our_addr[6]; unsigned char dst_addr[6] = "\xff\xff\xff\xff\xff\xff"; unsigned char service_name[1500]; int service_name_len; unsigned char ac_cookie[1500]; int ac_cookie_len = -1; char ac_name[1500]; int ac_name_len; const char *wanted_ac_name; unsigned char host_uniq[1500]; int host_uniq_len; int wait_for_hangup = 0; int verbose = 0; char *lcp_interval = NULL; char *lcp_timeout = NULL; #define PACK __attribute__((packed)) struct pppoehdr { unsigned short ether_type; unsigned char vertype; unsigned char code; unsigned short sesn_id; unsigned short length; } PACK; #define PPPoE_TAG_END_OF_LIST 0x0000 #define PPPoE_TAG_SERVICE_NAME 0x0101 #define PPPoE_TAG_AC_NAME 0x0102 #define PPPoE_TAG_HOST_UNIQ 0x0103 #define PPPoE_TAG_AC_COOKIE 0x0104 #define PPPoE_TAG_VENDOR_SPECIFIC 0x0105 #define PPPoE_TAG_RELAY_SESSION_ID 0x0110 #define PPPoE_TAG_SERVICE_NAME_ERROR 0x0201 #define PPPoE_TAG_AC_SYSTEM_ERROR 0x0202 #define PPPoE_TAG_GENERIC_ERROR 0x0203 struct pppoetaghdr { unsigned short tag_type; unsigned short tag_length; } PACK; char *devname = "eth0"; struct sockaddr sa; void send_disc_pkt(int sk, unsigned char code, unsigned short sesn, int length, unsigned char *data) { unsigned char buf[1500]; struct pppoehdr *h = (struct pppoehdr *)(buf+12); memcpy(buf, dst_addr, 6); memcpy(buf+6, our_addr, 6); h->ether_type = htons(ETH_P_PPPoE_DISC); h->vertype = 0x11; h->code = code; h->sesn_id = htons(sesn); h->length = htons(length); if (length > 0) memcpy(h+1, data, length); length += sizeof(*h) + 12; if (verbose) { fprintf(stderr, "sending:\n"); pretty_dump(length, buf); } if (0 >= sendto(sk, buf, length, 0, &sa, sizeof(sa))) perror("write"); } #define mktag(type, tagdata, taglen) \ do { \ if (taglen < 0) \ break; \ if (buf > data+sizeof(data)) { \ fprintf(stderr, "send_disc_padr: overflow\n"); \ return; \ } \ ((struct pppoetaghdr *)buf)->tag_type = htons(type); \ ((struct pppoetaghdr *)buf)->tag_length = htons(taglen);\ buf += sizeof (struct pppoetaghdr); \ memcpy(buf, tagdata, taglen); \ buf += taglen; \ } while (0) void send_disc_padi(int sk) { unsigned char data[1500], *buf = data; mktag(PPPoE_TAG_HOST_UNIQ, host_uniq, host_uniq_len); mktag(PPPoE_TAG_SERVICE_NAME, service_name, service_name_len); send_disc_pkt(sk, PPPoE_CODE_PADI, 0x0000, buf - data, data); } void send_disc_padr(int sk) { unsigned char data[1500], *buf = data; mktag(PPPoE_TAG_HOST_UNIQ, host_uniq, host_uniq_len); mktag(PPPoE_TAG_AC_NAME, ac_name, ac_name_len); mktag(PPPoE_TAG_AC_COOKIE, ac_cookie, ac_cookie_len); mktag(PPPoE_TAG_RELAY_SESSION_ID, relay_tag, relay_tag_len); /* MUST be exactly one Service-Name tag */ mktag(PPPoE_TAG_SERVICE_NAME, service_name, service_name_len); send_disc_pkt(sk, PPPoE_CODE_PADR, 0x0000, buf-data, data); } int parse_pado(const struct pppoehdr *h, int len, const unsigned char *addr) { const unsigned char *buf; int optlen; if (verbose) fprintf(stderr, "got pado len %d/%d/%d!\n", len, len - (int)sizeof(struct pppoehdr), ntohs(h->length)); len -= sizeof(struct pppoehdr); if (ntohs(h->length) > len) { fprintf(stderr, "parse_pado: pppoe hdr length indicates short packet\n"); return 0; } len = ntohs(h->length); buf = (void *)(h + 1); while (len > 0) { const struct pppoetaghdr *t = (void *)buf; if (len < sizeof(*t)) { fprintf(stderr, "parse_pado: short packet in option\n"); return 0; } len -= sizeof(*t); buf += sizeof(*t); optlen = ntohs(t->tag_length); if (optlen < 0 || optlen > len) { fprintf(stderr, "parse_pado: bad option length(%d)\n", optlen); return 0; } switch (ntohs(t->tag_type)) { case PPPoE_TAG_AC_NAME: if (optlen > 0 && optlen < sizeof(ac_name)) { memcpy(ac_name, buf, optlen); ac_name_len = optlen; ac_name[ac_name_len] = 0; fprintf(stderr, "ac_name_len %d\n", optlen); fprintf(stderr, "ac_name[%s]\n", ac_name); if (wanted_ac_name && strcmp(wanted_ac_name, ac_name)) { fprintf(stderr, "ac name mismatch\n"); return 0; } } else { fprintf(stderr, "bad AC-Name?\n"); return 0; } break; case PPPoE_TAG_SERVICE_NAME: if (!optlen) break; if (optlen < sizeof(service_name)) { memcpy(service_name, buf, optlen); service_name_len = optlen; fprintf(stderr, "service_name_len %d\n", optlen); } else { fprintf(stderr, "bad servicename (%d)?\n", optlen); return 0; } break; case PPPoE_TAG_AC_COOKIE: if (optlen > 0 && optlen < sizeof(ac_cookie)) { memcpy(ac_cookie, buf, optlen); ac_cookie_len = optlen; fprintf(stderr, "ac_cookie_len %d\n", optlen); } else { fprintf(stderr, "bad AC-Cookie?\n"); return 0; } break; case PPPoE_TAG_HOST_UNIQ: if (optlen <= 0 || optlen > host_uniq_len) { fprintf(stderr, "bad Host-Uniq?\n"); return 0; } if (memcmp(host_uniq, buf, host_uniq_len)) { fprintf(stderr, "Host-Uniq mismatch!\n"); return 0; } break; case PPPoE_TAG_RELAY_SESSION_ID: relay_tag = malloc(optlen); relay_tag_len = optlen; memcpy(relay_tag, buf, optlen); default: fprintf(stderr, "parse_pado: unknown pppoe option (%04x,%d)\n", ntohs(t->tag_type), optlen); break; } len -= optlen; buf += optlen; } /* packet turned out okay, use it */ memcpy(dst_addr, addr, 6); state = stPPPOE_DISC_PADR; fprintf(stderr, "pado was good.\n"); return 1; } /* pppoe_recv * 0 = packet parsing failed. Don't transmit anything. * true = packet was good. State changed, transmit as appropriate. */ int pppoe_recv(const unsigned char *buf, int len) { struct pppoehdr *h = (struct pppoehdr *)(buf + 12); int may_transmit = 0; if (memcmp(buf, our_addr, 6)) { fprintf(stderr, "not our address.\n"); return 0; } if (h->vertype != 0x11) { fprintf(stderr, "wrong version.\n"); return 0; } switch (state) { case stPPPOE_DISC_PADI: if (h->code == PPPoE_CODE_PADO) may_transmit = parse_pado(h, len, buf+6); break; case stPPPOE_DISC_PADR: if (h->code == PPPoE_CODE_PADS) { char *argvs[16]; char **argvp; state = stPPPOE_SESN; fprintf(stderr, "got pads!\n"); sprintf((char *)buf, "%02x%02x%02x%02x%02x%02x%04x%s", dst_addr[0], dst_addr[1], dst_addr[2], dst_addr[3], dst_addr[4], dst_addr[5], ntohs(h->sesn_id), devname ); argvp = argvs; *argvp++ = "bdial"; *argvp++ = "--class=pppoe"; if (wait_for_hangup) *argvp++ = "--wait-for-hangup"; if (lcp_timeout) *argvp++ = lcp_timeout; if (lcp_interval) *argvp++ = lcp_interval; *argvp++ = site_name; *argvp++ = (char *)buf; *argvp++ = NULL; execvp("bdial", argvs); fprintf(stderr, "execvp: %s\n", strerror(errno)); exit(1); } break; case stPPPOE_SESN: break; } return may_transmit; } void parse_args(int argc, char *argv[]) { int i; for (i=1; i < argc; i++) { if (!strcmp(argv[i], "-v")) { verbose = 1; continue; } if (!strcmp(argv[i], "--wait-for-hangup")) { wait_for_hangup = 1; continue; } if (!strncmp(argv[i], "--lcp-timeout=", 14)) { lcp_timeout = argv[i]; continue; } if (!strncmp(argv[i], "--lcp-interval=", 15)) { lcp_interval = argv[i]; continue; } if (!strncmp(argv[i], "--site=", 7)) { site_name = argv[i] + 7; continue; } if (!strncmp(argv[i], "--ac-name=", 10)) { wanted_ac_name = argv[i] + 10; continue; } if (!strcmp(argv[i], "-d")) { if ((argc - i) < 2) goto usage; devname = argv[++i]; continue; } goto usage; } return; usage: fprintf(stderr, "usage: pppoed [options]\n" "where options are:\n" " -d \n" " --ac-name=\n" " --site=\n" " --wait-for-hangup\n" " --lcp-timeout=