/* pppoe.c * A PPP over Ethernet module for use with Linux 2.0/2.2/2.3 and Babylon 1.4. * * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: pppoe.c,v 1.3 2004/08/24 01:46:12 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. */ #include "bab_module.h" #include #include #include #include #include #include "vercomp.h" #include "aps_if.h" /* relevant settings for skb->pkt_type: PACKET_HOST and PACKET_BROADCAST */ #define ETH_P_PPPOE_DISCOVERY 0x8863 #define ETH_P_PPPOE_SESSION 0x8864 #define NR_PPPOE_SESSIONS 8 struct session { channel_t ch; struct net_device *ether_dev; u16 sesn_id; u8 rem_addr[6]; } sessions[NR_PPPOE_SESSIONS]; struct session *id_to_session[65536]; int pppoe_output(channel_t *ch, struct sk_buff *skb) { struct session *s = (void *)ch; u16 len; u8 *hdr; if (s->ch.state != CS_CONNECTED) { b_dev_kfree_skb(skb); return 0; } if (skb->data[0] == 0xff && skb->data[1] == 0x03) skb_pull(skb, 2); /*if (skb_headroom(skb) < 20)*/ { struct sk_buff *skb2 = skb; //printk("doh: skb_headroom(skb) = %d\n", skb_headroom(skb)); skb = dev_alloc_skb(skb->len+20); if (!skb) { b_dev_kfree_skb(skb2); return 0; } #if LINUX_VERSION_CODE < 0x20100 skb->free = FREE_READ; #endif skb_reserve(skb, 20); memcpy(skb_put(skb, skb2->len), skb2->data, skb2->len); b_dev_kfree_skb(skb2); } #if LINUX_VERSION_CODE < 0x20100 skb->arp = 1; #endif skb_reset_network_header(skb); len = skb->len; hdr = skb_push(skb, 20); memcpy(hdr, s->rem_addr, 6); memcpy(hdr+6, s->ether_dev->dev_addr, 6); hdr[12] = 0x88; hdr[13] = 0x64; hdr[14] = 0x11; hdr[15] = 0x00; hdr[16] = s->sesn_id >> 8; hdr[17] = s->sesn_id; hdr[18] = len >> 8; hdr[19] = len; #if 0 { int i; printk("tx packet length=%d\n ", skb->len); for (i=0; ilen; i++) printk(" %02x", skb->data[i]); printk("\n"); } #endif s->ch.stats.tx_packets ++; #if LINUX_VERSION_CODE >= 0x20100 s->ch.stats.tx_bytes += skb->len; #endif skb->dev = s->ether_dev; skb_reset_mac_header(skb); skb_set_transport_header(skb, 14); skb->protocol = __constant_htons(0x8864); b_dev_queue_xmit(skb); return 0; } int pppoe_sesn(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) , struct net_device *dev2 #endif ) { struct session *s; int len; #if LINUX_VERSION_CODE < 0x20100 skb->free = FREE_READ; #endif if (skb->len < 24) { printk("pppoe: dropping runt packet len=%d\n", skb->len); goto drop; } s = id_to_session[(skb->data[2] << 8U) | skb->data[3]]; if (!s) goto drop; if (s->ch.state != CS_CONNECTED) { //printk("pppoe: not connected\n"); goto drop; } if (memcmp(skb_mac_header(skb), s->ether_dev->dev_addr, 6) || memcmp(skb_mac_header(skb)+6, s->rem_addr, 6) || memcmp(skb->data, "\x11\x00", 2) || skb->data[2] != (s->sesn_id >> 8) || skb->data[3] != (s->sesn_id & 0xff) ) { #if 0 int i; printk("dropping unmatched pppoe packet len=%d, data:\n", skb->len); for (i=0; i<32; i++) printk(" %02x", skb->mac.raw[i]); printk("\n"); #endif goto drop; } /* This packet is actually for us, so pass it into the ppp stack */ len = skb->data[4] << 8 | skb->data[5]; skb_pull(skb, 6); if (len > skb->len) { printk("skb->len=%d len=%d!\n", skb->len, len); goto drop; } skb_trim(skb, len); { struct sk_buff *skb2 = dev_alloc_skb(len); if (!skb2) { printk("drop -- dev_alloc_skb(%d) failed.\n", len); goto drop; } memcpy(skb_put(skb2, len), skb->data, len); b_kfree_skb(skb); skb = skb2; } skb_reset_mac_header(skb); s->ch.stats.rx_packets ++; #if LINUX_VERSION_CODE >= 0x020100 s->ch.stats.rx_bytes += skb->len; #endif ch_Input(&s->ch, skb); return 0; drop: b_kfree_skb(skb); return 0; } static struct packet_type pt_pppoe_session = { 0, NULL, /* struct net_device *dev */ .func = pppoe_sesn, /* int (*func)(skb, dev, pt) */ NULL, /* void * data */ NULL /* next */ }; void pppoe_use(struct channel *ch) { MOD_INC_USE_COUNT; } void pppoe_unuse(struct channel *ch) { MOD_DEC_USE_COUNT; } static inline int hexbyte(const char *str) { int val = 0; #define DIG(d) if (d >= '0' && d <= '9') val |= d - '0'; \ else if (d >= 'a' && d <= 'f') val |= d -'a' + 10; \ else if (d >= 'A' && d <= 'F') val |= d -'A' + 10; \ else return -1; DIG(str[0]); val <<= 4; DIG(str[1]); return val; } int pppoe_connect(struct channel *ch, const char *number, u32 flags) { struct session *s = (struct session *)ch; int i; MOD_INC_USE_COUNT; /* we take a phone number format of * * which is 16 digits plus strlen(dev_name). */ if (strlen(number) < 17) goto out_invalid; s->ether_dev = dev_get_by_name(number + 16); if (!s->ether_dev) { printk("no ether device found\n"); goto out_invalid; } printk("%s addr_len = %d\n addr:", number+16, s->ether_dev->addr_len); { int i; for (i=0; i<6; i++) printk(" %02x", s->ether_dev->dev_addr[i]); } printk("\n"); printk("setting addr:"); for (i=0; i<6; i++) { int byte = hexbyte(number + i*2); if (byte < 0) goto out_invalid; s->rem_addr[i] = byte; printk(" %02x", byte); } i = hexbyte(number+12); if (i < 0) goto out_invalid; s->sesn_id = i << 8; i = hexbyte(number+14); if (i < 0) goto out_invalid; s->sesn_id |= i; id_to_session[s->sesn_id] = s; printk(" : %04x\n", s->sesn_id); s->ch.state = CS_CONNECTED; clear_busy(&s->ch); s->ch.ConnectComplete(&s->ch, 0); /* connect completed ok */ return 0; out_invalid: MOD_DEC_USE_COUNT; return -EIO; } int pppoe_hangup(struct channel *ch) { struct session *s = (struct session *)ch; if (ch->state != CS_IDLE) { set_busy(ch); ch->state = CS_IDLE; ch->Down(ch); dev_put(s->ether_dev); s->ether_dev = NULL; MOD_DEC_USE_COUNT; } return 0; } int pppoe_ch_ioctl(channel_t *ch, unsigned int cmd, unsigned long arg) { return -ENOTTY; } static int pppoe_init_session(struct session *s, unsigned idx) { sprintf(s->ch.device_name, "pppoe%u", idx); strcpy(s->ch.dev_class, "pppoe"); s->ch.mru = 1492; s->ch.use = pppoe_use; s->ch.unuse = pppoe_unuse; s->ch.Output = pppoe_output; s->ch.Connect = pppoe_connect; s->ch.Hangup = pppoe_hangup; s->ch.ioctl = pppoe_ch_ioctl; if (RegisterChannel(&s->ch)) { printk("unable to register channel\n"); return -ENODEV; } set_busy(&s->ch); return 0; } int init_module(void) { unsigned i; for (i=0; i