/* 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. * Copyright (C) 2004,2007 Benjamin LaHaise * $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 "../include/bab_module.h" #include #include #include #include #include #include #include "../include/vercomp.h" #include "../include/aps_if.h" #include "bab.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 PPPoE_CODE_DATA 0x00 #define PPPoE_CODE_PADT 0xa7 #define NR_PPPOE_SESSIONS 0 struct session { channel_t ch; struct net_device *ether_dev; u16 sesn_id; u8 rem_addr[6]; } sessions[NR_PPPOE_SESSIONS]; static struct kmem_cache *session_cachep; 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 = skb_share_check(skb, GFP_ATOMIC)) == NULL) { s->ch.stats.tx_dropped ++; return 0; } 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; *(u16 *)(hdr+16) = 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 += 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; } static inline int maccmp(unsigned char *left, unsigned char *right, int len) { if (len == 6) { u32 sum = *(u32 *)left ^ *(u32 *)right; sum |= *(u16 *)(left+4) ^ *(u16 *)(right + 4); return sum; } return memcmp(left, right, len); } 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; unsigned idx; u8 type; int len; #if LINUX_VERSION_CODE < 0x20100 skb->free = FREE_READ; #endif if (skb->len < 6) { printk("bab_pppoe: dropping runt packet len=%d\n", skb->len); goto drop_no_chan; } idx = *(u16 *)(skb->data + 2); s = id_to_session[idx]; if (!s) { pr_debug("pppoe: no session[%u]\n", idx); goto drop_no_chan; } if (s->ch.state != CS_CONNECTED) { pr_debug("pppoe: not connected\n"); goto drop_no_chan; } if (maccmp(skb_mac_header(skb), s->ether_dev->dev_addr, 6) || maccmp(skb_mac_header(skb)+6, s->rem_addr, 6) || skb->data[0] != 0x11 /* vertype */ || *(u16 *)(skb->data + 2) != s->sesn_id ) { #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_header(skb)[i]); printk("\n"); #endif goto drop_no_chan; } type = skb->data[1]; if (PPPoE_CODE_PADT == type) { /* The server has terminated this session. */ id_to_session[idx] = NULL; s->ch.state = CS_IDLE; s->ch.ConnectComplete(&s->ch, 0x10); goto drop; } if (PPPoE_CODE_DATA != type) goto drop; /* This packet is actually for us, so pass it into the ppp stack */ len = ntohs(*(u16 *)(skb->data + 4)); skb_pull(skb, 6); if (!len) goto drop; if (len > skb->len) { pr_debug("skb->len=%d len=%d!\n", skb->len, len); goto drop; } skb_trim(skb, len); if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) goto drop; skb_reset_mac_header(skb); ch_Input(&s->ch, skb); return 0; drop: s->ch.stats.rx_dropped ++; drop_no_chan: if (skb) 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; u16 sesn_id; int i; /* 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(&init_net, 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; sesn_id = i << 8; i = hexbyte(number+14); if (i < 0) goto out_invalid; sesn_id |= i; sesn_id = htons(sesn_id); if (id_to_session[sesn_id]) goto out_busy; id_to_session[sesn_id] = s; s->sesn_id = sesn_id; 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_busy: return -EBUSY; out_invalid: return -EIO; } void pppoe_send_padt(struct session *s) { struct sk_buff *skb = alloc_skb(64, GFP_ATOMIC); int len = 0; u8 *hdr; if (!skb) { printk("pppoe_send_padt: skb alloc failed\n"); return; } skb_reset_network_header(skb); hdr = skb_put(skb, 20 + 40); memcpy(hdr, s->rem_addr, 6); memcpy(hdr+6, s->ether_dev->dev_addr, 6); hdr[12] = 0x88; hdr[13] = 0x64; hdr[14] = 0x11; /* vertype */ hdr[15] = PPPoE_CODE_PADT; *(u16 *)(hdr+16) = s->sesn_id; hdr[18] = len >> 8; hdr[19] = len; memset(hdr + 20, 0, 40); /* pad skb to 60 bytes */ 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); } int pppoe_hangup(struct channel *ch) { struct session *s = (struct session *)ch; if (ch->state != CS_IDLE) { set_busy(ch); pppoe_send_padt(s); ch->state = CS_DISCONNECTING; id_to_session[s->sesn_id] = NULL; ch->Down(ch); dev_put(s->ether_dev); s->ether_dev = NULL; } 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; } static int bpppoe_session_idx = NR_PPPOE_SESSIONS; static DEFINE_SPINLOCK(bpppoe_session_lock); static void bpppoe_channel_release(channel_t *ch) { return; } static int bpppoe_open(struct inode *ino, struct file *filp) { struct session *session; struct chan *call; int idx, ret; if (!(filp->f_flags & O_NONBLOCK)) return -EINVAL; session = kmem_cache_alloc(session_cachep, GFP_KERNEL); if (!session) return -ENOMEM; memset(session, 0, sizeof(*session)); spin_lock(&bpppoe_session_lock); /* FIXME: handle session_idx wrap around */ idx = bpppoe_session_idx++; spin_unlock(&bpppoe_session_lock); ret = pppoe_init_session(session, idx); if (ret) { kmem_cache_free(session_cachep, session); return ret; } session->ch.Release = bpppoe_channel_release; call = babylon_alloc_call(&session->ch, &ret); if (!call) { UnregisterChannel(&session->ch); kmem_cache_free(session_cachep, session); return ret; } session->ch.use_existing_call = 1; filp->private_data = call; return 0; } static int bpppoe_release(struct inode *ino, struct file *filp) { struct chan *chan = filp->private_data; struct session *session; int ret; BUG_ON(!chan); BUG_ON(!chan->ch); session = container_of(chan->ch, struct session, ch); ret = babylon_release_call(chan, filp); UnregisterChannel(&session->ch); kmem_cache_free(session_cachep, session); return ret; } static struct file_operations bpppoe_fops = { .owner = THIS_MODULE, .read = babylon_read, .write = babylon_write, .poll = babylon_poll, .unlocked_ioctl = babylon_unlocked_ioctl, .open = bpppoe_open, .release = bpppoe_release, }; static struct miscdevice bpppoe_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "bpppoe", #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,31) .nodename = "bpppoe", #endif .fops = &bpppoe_fops, }; int init_module(void) { unsigned i; int err; session_cachep = kmem_cache_create("babylon_pppoe", sizeof(struct session), 0, 0, NULL); if (!session_cachep) return -ENOMEM; for (i=0; i