📄 hdlc_fr.c
字号:
/* * Generic HDLC support routines for Linux * Frame Relay support * * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl> * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License * as published by the Free Software Foundation. * Theory of PVC state in DCE mode: (exist,new) -> 0,0 when "PVC create" or if "link unreliable" 0,x -> 1,1 if "link reliable" when sending FULL STATUS 1,1 -> 1,0 if received FULL STATUS ACK (active) -> 0 when "ifconfig PVC down" or "link unreliable" or "PVC create" -> 1 when "PVC up" and (exist,new) = 1,0*/#include <linux/module.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/poll.h>#include <linux/errno.h>#include <linux/if_arp.h>#include <linux/init.h>#include <linux/skbuff.h>#include <linux/pkt_sched.h>#include <linux/random.h>#include <linux/inetdevice.h>#include <linux/lapb.h>#include <linux/rtnetlink.h>#include <linux/etherdevice.h>#include <linux/hdlc.h>#undef DEBUG_PKT#undef DEBUG_ECN#define MAXLEN_LMISTAT 20 /* max size of status enquiry frame */#define PVC_STATE_NEW 0x01#define PVC_STATE_ACTIVE 0x02#define PVC_STATE_FECN 0x08 /* FECN condition */#define PVC_STATE_BECN 0x10 /* BECN condition */#define FR_UI 0x03#define FR_PAD 0x00#define NLPID_IP 0xCC#define NLPID_IPV6 0x8E#define NLPID_SNAP 0x80#define NLPID_PAD 0x00#define NLPID_Q933 0x08#define LMI_DLCI 0 /* LMI DLCI */#define LMI_PROTO 0x08#define LMI_CALLREF 0x00 /* Call Reference */#define LMI_ANSI_LOCKSHIFT 0x95 /* ANSI lockshift */#define LMI_REPTYPE 1 /* report type */#define LMI_CCITT_REPTYPE 0x51#define LMI_ALIVE 3 /* keep alive */#define LMI_CCITT_ALIVE 0x53#define LMI_PVCSTAT 7 /* pvc status */#define LMI_CCITT_PVCSTAT 0x57#define LMI_FULLREP 0 /* full report */#define LMI_INTEGRITY 1 /* link integrity report */#define LMI_SINGLE 2 /* single pvc report */#define LMI_STATUS_ENQUIRY 0x75#define LMI_STATUS 0x7D /* reply */#define LMI_REPT_LEN 1 /* report type element length */#define LMI_INTEG_LEN 2 /* link integrity element length */#define LMI_LENGTH 13 /* standard LMI frame length */#define LMI_ANSI_LENGTH 14typedef struct {#if defined(__LITTLE_ENDIAN_BITFIELD) unsigned ea1: 1; unsigned cr: 1; unsigned dlcih: 6; unsigned ea2: 1; unsigned de: 1; unsigned becn: 1; unsigned fecn: 1; unsigned dlcil: 4;#else unsigned dlcih: 6; unsigned cr: 1; unsigned ea1: 1; unsigned dlcil: 4; unsigned fecn: 1; unsigned becn: 1; unsigned de: 1; unsigned ea2: 1;#endif}__attribute__ ((packed)) fr_hdr;static inline u16 q922_to_dlci(u8 *hdr){ return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);}static inline void dlci_to_q922(u8 *hdr, u16 dlci){ hdr[0] = (dlci >> 2) & 0xFC; hdr[1] = ((dlci << 4) & 0xF0) | 0x01;}static inline pvc_device* find_pvc(hdlc_device *hdlc, u16 dlci){ pvc_device *pvc = hdlc->state.fr.first_pvc; while (pvc) { if (pvc->dlci == dlci) return pvc; if (pvc->dlci > dlci) return NULL; /* the listed is sorted */ pvc = pvc->next; } return NULL;}static inline pvc_device* add_pvc(hdlc_device *hdlc, u16 dlci){ pvc_device *pvc, **pvc_p = &hdlc->state.fr.first_pvc; while (*pvc_p) { if ((*pvc_p)->dlci == dlci) return *pvc_p; if ((*pvc_p)->dlci > dlci) break; /* the list is sorted */ pvc_p = &(*pvc_p)->next; } pvc = kmalloc(sizeof(pvc_device), GFP_ATOMIC); if (!pvc) return NULL; memset(pvc, 0, sizeof(pvc_device)); pvc->dlci = dlci; pvc->master = hdlc; pvc->next = *pvc_p; /* Put it in the chain */ *pvc_p = pvc; return pvc;}static inline int pvc_is_used(pvc_device *pvc){ return pvc->main != NULL || pvc->ether != NULL;}static inline void delete_unused_pvcs(hdlc_device *hdlc){ pvc_device **pvc_p = &hdlc->state.fr.first_pvc; while (*pvc_p) { if (!pvc_is_used(*pvc_p)) { pvc_device *pvc = *pvc_p; *pvc_p = pvc->next; kfree(pvc); continue; } pvc_p = &(*pvc_p)->next; }}static inline struct net_device** get_dev_p(pvc_device *pvc, int type){ if (type == ARPHRD_ETHER) return &pvc->ether; else return &pvc->main;}static inline u16 status_to_dlci(u8 *status, int *active, int *new){ *new = (status[2] & 0x08) ? 1 : 0; *active = (status[2] & 0x02) ? 1 : 0; return ((status[0] & 0x3F) << 4) | ((status[1] & 0x78) >> 3);}static inline void dlci_to_status(u16 dlci, u8 *status, int active, int new){ status[0] = (dlci >> 4) & 0x3F; status[1] = ((dlci << 3) & 0x78) | 0x80; status[2] = 0x80; if (new) status[2] |= 0x08; else if (active) status[2] |= 0x02;}static int fr_hard_header(struct sk_buff **skb_p, u16 dlci){ u16 head_len; struct sk_buff *skb = *skb_p; switch (skb->protocol) { case __constant_ntohs(ETH_P_IP): head_len = 4; skb_push(skb, head_len); skb->data[3] = NLPID_IP; break; case __constant_ntohs(ETH_P_IPV6): head_len = 4; skb_push(skb, head_len); skb->data[3] = NLPID_IPV6; break; case __constant_ntohs(LMI_PROTO): head_len = 4; skb_push(skb, head_len); skb->data[3] = LMI_PROTO; break; case __constant_ntohs(ETH_P_802_3): head_len = 10; if (skb_headroom(skb) < head_len) { struct sk_buff *skb2 = skb_realloc_headroom(skb, head_len); if (!skb2) return -ENOBUFS; dev_kfree_skb(skb); skb = *skb_p = skb2; } skb_push(skb, head_len); skb->data[3] = FR_PAD; skb->data[4] = NLPID_SNAP; skb->data[5] = FR_PAD; skb->data[6] = 0x80; skb->data[7] = 0xC2; skb->data[8] = 0x00; skb->data[9] = 0x07; /* bridged Ethernet frame w/out FCS */ break; default: head_len = 10; skb_push(skb, head_len); skb->data[3] = FR_PAD; skb->data[4] = NLPID_SNAP; skb->data[5] = FR_PAD; skb->data[6] = FR_PAD; skb->data[7] = FR_PAD; *(u16*)(skb->data + 8) = skb->protocol; } dlci_to_q922(skb->data, dlci); skb->data[2] = FR_UI; return 0;}static int pvc_open(struct net_device *dev){ pvc_device *pvc = dev_to_pvc(dev); if ((hdlc_to_dev(pvc->master)->flags & IFF_UP) == 0) return -EIO; /* Master must be UP in order to activate PVC */ if (pvc->open_count++ == 0) { if (pvc->master->state.fr.settings.lmi == LMI_NONE) pvc->state.active = 1; pvc->master->state.fr.dce_changed = 1; } return 0;}static int pvc_close(struct net_device *dev){ pvc_device *pvc = dev_to_pvc(dev); if (--pvc->open_count == 0) { if (pvc->master->state.fr.settings.lmi == LMI_NONE) pvc->state.active = 0; if (pvc->master->state.fr.settings.dce) { pvc->master->state.fr.dce_changed = 1; pvc->state.active = 0; } } return 0;}int pvc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd){ pvc_device *pvc = dev_to_pvc(dev); fr_proto_pvc_info info; if (ifr->ifr_settings.type == IF_GET_PROTO) { if (dev->type == ARPHRD_ETHER) ifr->ifr_settings.type = IF_PROTO_FR_ETH_PVC; else ifr->ifr_settings.type = IF_PROTO_FR_PVC; if (ifr->ifr_settings.size < sizeof(info)) { /* data size wanted */ ifr->ifr_settings.size = sizeof(info); return -ENOBUFS; } info.dlci = pvc->dlci; memcpy(info.master, hdlc_to_name(pvc->master), IFNAMSIZ); if (copy_to_user(ifr->ifr_settings.ifs_ifsu.fr_pvc_info, &info, sizeof(info))) return -EFAULT; return 0; } return -EINVAL;}static inline struct net_device_stats *pvc_get_stats(struct net_device *dev){ return (struct net_device_stats *) ((char *)dev + sizeof(struct net_device));}static int pvc_xmit(struct sk_buff *skb, struct net_device *dev){ pvc_device *pvc = dev_to_pvc(dev); struct net_device_stats *stats = pvc_get_stats(dev); if (pvc->state.active) { if (dev->type == ARPHRD_ETHER) { int pad = ETH_ZLEN - skb->len; if (pad > 0) { /* Pad the frame with zeros */ int len = skb->len; if (skb_tailroom(skb) < pad) if (pskb_expand_head(skb, 0, pad, GFP_ATOMIC)) { stats->tx_dropped++; dev_kfree_skb(skb); return 0; } skb_put(skb, pad); memset(skb->data + len, 0, pad); } skb->protocol = __constant_htons(ETH_P_802_3); } if (!fr_hard_header(&skb, pvc->dlci)) { stats->tx_bytes += skb->len; stats->tx_packets++; if (pvc->state.fecn) /* TX Congestion counter */ stats->tx_compressed++; skb->dev = hdlc_to_dev(pvc->master); dev_queue_xmit(skb); return 0; } } stats->tx_dropped++; dev_kfree_skb(skb); return 0;}static int pvc_change_mtu(struct net_device *dev, int new_mtu){ if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU)) return -EINVAL; dev->mtu = new_mtu; return 0;}static inline void fr_log_dlci_active(pvc_device *pvc){ printk(KERN_INFO "%s: DLCI %d [%s%s%s]%s %s\n", hdlc_to_name(pvc->master), pvc->dlci, pvc->main ? pvc->main->name : "", pvc->main && pvc->ether ? " " : "", pvc->ether ? pvc->ether->name : "", pvc->state.new ? " new" : "", !pvc->state.exist ? "deleted" : pvc->state.active ? "active" : "inactive");}static inline u8 fr_lmi_nextseq(u8 x){ x++; return x ? x : 1;}static void fr_lmi_send(hdlc_device *hdlc, int fullrep){ struct sk_buff *skb; pvc_device *pvc = hdlc->state.fr.first_pvc; int len = (hdlc->state.fr.settings.lmi == LMI_ANSI) ? LMI_ANSI_LENGTH : LMI_LENGTH; int stat_len = 3; u8 *data; int i = 0; if (hdlc->state.fr.settings.dce && fullrep) { len += hdlc->state.fr.dce_pvc_count * (2 + stat_len); if (len > HDLC_MAX_MRU) { printk(KERN_WARNING "%s: Too many PVCs while sending " "LMI full report\n", hdlc_to_name(hdlc)); return; } } skb = dev_alloc_skb(len); if (!skb) { printk(KERN_WARNING "%s: Memory squeeze on fr_lmi_send()\n", hdlc_to_name(hdlc)); return; } memset(skb->data, 0, len); skb_reserve(skb, 4); skb->protocol = __constant_htons(LMI_PROTO); fr_hard_header(&skb, LMI_DLCI); data = skb->tail; data[i++] = LMI_CALLREF; data[i++] = hdlc->state.fr.settings.dce ? LMI_STATUS : LMI_STATUS_ENQUIRY; if (hdlc->state.fr.settings.lmi == LMI_ANSI) data[i++] = LMI_ANSI_LOCKSHIFT; data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT) ? LMI_CCITT_REPTYPE : LMI_REPTYPE; data[i++] = LMI_REPT_LEN; data[i++] = fullrep ? LMI_FULLREP : LMI_INTEGRITY; data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT) ? LMI_CCITT_ALIVE : LMI_ALIVE; data[i++] = LMI_INTEG_LEN; data[i++] = hdlc->state.fr.txseq =fr_lmi_nextseq(hdlc->state.fr.txseq); data[i++] = hdlc->state.fr.rxseq; if (hdlc->state.fr.settings.dce && fullrep) { while (pvc) { data[i++] = (hdlc->state.fr.settings.lmi == LMI_CCITT) ? LMI_CCITT_PVCSTAT : LMI_PVCSTAT; data[i++] = stat_len; /* LMI start/restart */ if (hdlc->state.fr.reliable && !pvc->state.exist) { pvc->state.exist = pvc->state.new = 1; fr_log_dlci_active(pvc); } /* ifconfig PVC up */ if (pvc->open_count && !pvc->state.active && pvc->state.exist && !pvc->state.new) { pvc->state.active = 1; fr_log_dlci_active(pvc); } dlci_to_status(pvc->dlci, data + i, pvc->state.active, pvc->state.new); i += stat_len; pvc = pvc->next; } } skb_put(skb, i); skb->priority = TC_PRIO_CONTROL; skb->dev = hdlc_to_dev(hdlc); skb->nh.raw = skb->data; dev_queue_xmit(skb);}static void fr_timer(unsigned long arg){ hdlc_device *hdlc = (hdlc_device*)arg; int i, cnt = 0, reliable; u32 list; if (hdlc->state.fr.settings.dce) reliable = (jiffies - hdlc->state.fr.last_poll < hdlc->state.fr.settings.t392 * HZ); else { hdlc->state.fr.last_errors <<= 1; /* Shift the list */ if (hdlc->state.fr.request) { if (hdlc->state.fr.reliable) printk(KERN_INFO "%s: No LMI status reply " "received\n", hdlc_to_name(hdlc)); hdlc->state.fr.last_errors |= 1; } list = hdlc->state.fr.last_errors; for (i = 0; i < hdlc->state.fr.settings.n393; i++, list >>= 1) cnt += (list & 1); /* errors count */ reliable = (cnt < hdlc->state.fr.settings.n392); } if (hdlc->state.fr.reliable != reliable) { pvc_device *pvc = hdlc->state.fr.first_pvc; hdlc->state.fr.reliable = reliable; printk(KERN_INFO "%s: Link %sreliable\n", hdlc_to_name(hdlc), reliable ? "" : "un"); if (reliable) { hdlc->state.fr.n391cnt = 0; /* Request full status */ hdlc->state.fr.dce_changed = 1; } else { while (pvc) { /* Deactivate all PVCs */ pvc->state.exist = 0; pvc->state.active = pvc->state.new = 0; pvc = pvc->next; } } } if (hdlc->state.fr.settings.dce) hdlc->state.fr.timer.expires = jiffies + hdlc->state.fr.settings.t392 * HZ; else { if (hdlc->state.fr.n391cnt) hdlc->state.fr.n391cnt--; fr_lmi_send(hdlc, hdlc->state.fr.n391cnt == 0); hdlc->state.fr.request = 1; hdlc->state.fr.timer.expires = jiffies + hdlc->state.fr.settings.t391 * HZ; } hdlc->state.fr.timer.function = fr_timer; hdlc->state.fr.timer.data = arg; add_timer(&hdlc->state.fr.timer);}static int fr_lmi_recv(hdlc_device *hdlc, struct sk_buff *skb)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -