While trying to test my RFC 4638 changes for pppoe(4) against a PPPoE server that won't agree to the increased MTU, I found that after setting up a pppoe(8)-based server I wasn't getting the expected behaviour.
It turns out pppoe(8) returns all of the clients tags in addition to any tags specific to the PADO or PADS packets so if a client requests an increased MTU, pppoe(8) blindly agrees to it by echoing the max payload tag back. Patch below adds a new function to scrub out any unknown tags that are not defined in pppoe.h. With this applied I get the correct negotiation. Matt --- src/usr.sbin/pppoe/server.c.orig Wed Jan 4 21:34:58 2012 +++ src/usr.sbin/pppoe/server.c Thu Jan 5 06:49:47 2012 @@ -72,6 +72,8 @@ static u_int8_t *key_make(u_int8_t *, int, u_int8_t *, int); static int key_cmp(u_int8_t *, int, u_int8_t *, int, u_int8_t *, int); +static u_long scrub_pkt(u_long, u_int8_t *); + void server_mode(int bpffd, u_int8_t *sysname, u_int8_t *srvname, struct ether_addr *ea) @@ -302,6 +304,7 @@ struct pppoe_header *ph, u_long pktlen, u_int8_t *pktbuf) { struct pppoe_tag ktag, htag; + u_long newlen; u_int8_t hn[MAXHOSTNAMELEN]; u_int8_t *k = NULL; struct iovec v[7]; @@ -315,8 +318,14 @@ ph->code = PPPOE_CODE_PADO; v[idx].iov_base = ph; v[idx].iov_len = sizeof(*ph); idx++; - v[idx].iov_base = pktbuf; v[idx].iov_len = pktlen; idx++; + /* Scrub unknown tags from the packet before returning them */ + if ((newlen = scrub_pkt(pktlen, pktbuf)) == 0) + return; + if (newlen != pktlen) + ph->len -= pktlen - newlen; + v[idx].iov_base = pktbuf; v[idx].iov_len = newlen; idx++; + if (gethostname((char *)hn, sizeof(hn)) < 0) return; htag.len = strlen((char *)hn); @@ -383,6 +392,7 @@ struct ether_header *eh, struct pppoe_header *ph, u_long pktlen, u_int8_t *pktbuf) { + u_long newlen; u_int8_t hn[MAXHOSTNAMELEN]; struct iovec v[16]; struct pppoe_session *s; @@ -404,8 +414,14 @@ return; v[idx].iov_base = ph; v[idx].iov_len = sizeof(*ph); idx++; - v[idx].iov_base = pktbuf; v[idx].iov_len = pktlen; idx++; + /* Scrub unknown tags from the packet before returning them */ + if ((newlen = scrub_pkt(pktlen, pktbuf)) == 0) + return; + if (newlen != pktlen) + ph->len -= pktlen - newlen; + v[idx].iov_base = pktbuf; v[idx].iov_len = newlen; idx++; + htag.len = strlen((char *)hn); htag.type = htons(PPPOE_TAG_AC_NAME); htag.val = hn; @@ -446,4 +462,57 @@ out: tag_destroy(&tl); +} + +static u_long +scrub_pkt(u_long pktlen, u_int8_t *pkt) +{ + u_long newlen = 0; + u_int16_t ttype, tlen; + u_int8_t *p = pkt; + + while (pktlen != 0) { + if (pktlen < sizeof(u_int16_t)) + break; + ttype = pkt[1] | (pkt[0] << 8); + pkt += sizeof(u_int16_t); + pktlen -= sizeof(u_int16_t); + + if (pktlen < sizeof(u_int16_t)) + break; + tlen = pkt[1] | (pkt[0] << 8); + pkt += sizeof(u_int16_t); + pktlen -= sizeof(u_int16_t); + + if (pktlen < tlen) + break; + + pkt += tlen; + pktlen -= tlen; + + switch (ttype) { + case PPPOE_TAG_END_OF_LIST: + case PPPOE_TAG_SERVICE_NAME: + case PPPOE_TAG_AC_NAME: + case PPPOE_TAG_HOST_UNIQ: + case PPPOE_TAG_AC_COOKIE: + case PPPOE_TAG_VENDOR_SPEC: + case PPPOE_TAG_RELAY_SESSION: + case PPPOE_TAG_SERVICE_NAME_ERROR: + case PPPOE_TAG_AC_SYSTEM_ERROR: + case PPPOE_TAG_GENERIC_ERROR: + p = pkt; + newlen += sizeof(u_int16_t) + sizeof(u_int16_t) + tlen; + break; + default: + if (pktlen != 0) + bcopy(pkt, p, pktlen); + pkt = p; + break; + } + } + + if (pktlen != 0) + return (0); + return (newlen); }