forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
11 KiB
541 lines
11 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* DLCI Implementation of Frame Relay protocol for Linux, according to |
|
* RFC 1490. This generic device provides en/decapsulation for an |
|
* underlying hardware driver. Routes & IPs are assigned to these |
|
* interfaces. Requires 'dlcicfg' program to create usable |
|
* interfaces, the initial one, 'dlci' is for IOCTL use only. |
|
* |
|
* Version: @(#)dlci.c 0.35 4 Jan 1997 |
|
* |
|
* Author: Mike McLagan <[email protected]> |
|
* |
|
* Changes: |
|
* |
|
* 0.15 Mike Mclagan Packet freeing, bug in kmalloc call |
|
* DLCI_RET handling |
|
* 0.20 Mike McLagan More conservative on which packets |
|
* are returned for retry and which are |
|
* are dropped. If DLCI_RET_DROP is |
|
* returned from the FRAD, the packet is |
|
* sent back to Linux for re-transmission |
|
* 0.25 Mike McLagan Converted to use SIOC IOCTL calls |
|
* 0.30 Jim Freeman Fixed to allow IPX traffic |
|
* 0.35 Michael Elizabeth Fixed incorrect memcpy_fromfs |
|
*/ |
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/module.h> |
|
#include <linux/kernel.h> |
|
#include <linux/types.h> |
|
#include <linux/fcntl.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/ptrace.h> |
|
#include <linux/ioport.h> |
|
#include <linux/in.h> |
|
#include <linux/init.h> |
|
#include <linux/slab.h> |
|
#include <linux/string.h> |
|
#include <linux/errno.h> |
|
#include <linux/netdevice.h> |
|
#include <linux/skbuff.h> |
|
#include <linux/if_arp.h> |
|
#include <linux/if_frad.h> |
|
#include <linux/bitops.h> |
|
|
|
#include <net/sock.h> |
|
|
|
#include <asm/io.h> |
|
#include <asm/dma.h> |
|
#include <linux/uaccess.h> |
|
|
|
static const char version[] = "DLCI driver v0.35, 4 Jan 1997, [email protected]"; |
|
|
|
static LIST_HEAD(dlci_devs); |
|
|
|
static void dlci_setup(struct net_device *); |
|
|
|
/* |
|
* these encapsulate the RFC 1490 requirements as well as |
|
* deal with packet transmission and reception, working with |
|
* the upper network layers |
|
*/ |
|
|
|
static int dlci_header(struct sk_buff *skb, struct net_device *dev, |
|
unsigned short type, const void *daddr, |
|
const void *saddr, unsigned len) |
|
{ |
|
struct frhdr hdr; |
|
unsigned int hlen; |
|
char *dest; |
|
|
|
hdr.control = FRAD_I_UI; |
|
switch (type) |
|
{ |
|
case ETH_P_IP: |
|
hdr.IP_NLPID = FRAD_P_IP; |
|
hlen = sizeof(hdr.control) + sizeof(hdr.IP_NLPID); |
|
break; |
|
|
|
/* feel free to add other types, if necessary */ |
|
|
|
default: |
|
hdr.pad = FRAD_P_PADDING; |
|
hdr.NLPID = FRAD_P_SNAP; |
|
memset(hdr.OUI, 0, sizeof(hdr.OUI)); |
|
hdr.PID = htons(type); |
|
hlen = sizeof(hdr); |
|
break; |
|
} |
|
|
|
dest = skb_push(skb, hlen); |
|
if (!dest) |
|
return 0; |
|
|
|
memcpy(dest, &hdr, hlen); |
|
|
|
return hlen; |
|
} |
|
|
|
static void dlci_receive(struct sk_buff *skb, struct net_device *dev) |
|
{ |
|
struct frhdr *hdr; |
|
int process, header; |
|
|
|
if (!pskb_may_pull(skb, sizeof(*hdr))) { |
|
netdev_notice(dev, "invalid data no header\n"); |
|
dev->stats.rx_errors++; |
|
kfree_skb(skb); |
|
return; |
|
} |
|
|
|
hdr = (struct frhdr *) skb->data; |
|
process = 0; |
|
header = 0; |
|
skb->dev = dev; |
|
|
|
if (hdr->control != FRAD_I_UI) |
|
{ |
|
netdev_notice(dev, "Invalid header flag 0x%02X\n", |
|
hdr->control); |
|
dev->stats.rx_errors++; |
|
} |
|
else |
|
switch (hdr->IP_NLPID) |
|
{ |
|
case FRAD_P_PADDING: |
|
if (hdr->NLPID != FRAD_P_SNAP) |
|
{ |
|
netdev_notice(dev, "Unsupported NLPID 0x%02X\n", |
|
hdr->NLPID); |
|
dev->stats.rx_errors++; |
|
break; |
|
} |
|
|
|
if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0) |
|
{ |
|
netdev_notice(dev, "Unsupported organizationally unique identifier 0x%02X-%02X-%02X\n", |
|
hdr->OUI[0], |
|
hdr->OUI[1], |
|
hdr->OUI[2]); |
|
dev->stats.rx_errors++; |
|
break; |
|
} |
|
|
|
/* at this point, it's an EtherType frame */ |
|
header = sizeof(struct frhdr); |
|
/* Already in network order ! */ |
|
skb->protocol = hdr->PID; |
|
process = 1; |
|
break; |
|
|
|
case FRAD_P_IP: |
|
header = sizeof(hdr->control) + sizeof(hdr->IP_NLPID); |
|
skb->protocol = htons(ETH_P_IP); |
|
process = 1; |
|
break; |
|
|
|
case FRAD_P_SNAP: |
|
case FRAD_P_Q933: |
|
case FRAD_P_CLNP: |
|
netdev_notice(dev, "Unsupported NLPID 0x%02X\n", |
|
hdr->pad); |
|
dev->stats.rx_errors++; |
|
break; |
|
|
|
default: |
|
netdev_notice(dev, "Invalid pad byte 0x%02X\n", |
|
hdr->pad); |
|
dev->stats.rx_errors++; |
|
break; |
|
} |
|
|
|
if (process) |
|
{ |
|
/* we've set up the protocol, so discard the header */ |
|
skb_reset_mac_header(skb); |
|
skb_pull(skb, header); |
|
dev->stats.rx_bytes += skb->len; |
|
netif_rx(skb); |
|
dev->stats.rx_packets++; |
|
} |
|
else |
|
dev_kfree_skb(skb); |
|
} |
|
|
|
static netdev_tx_t dlci_transmit(struct sk_buff *skb, struct net_device *dev) |
|
{ |
|
struct dlci_local *dlp = netdev_priv(dev); |
|
|
|
if (skb) { |
|
struct netdev_queue *txq = skb_get_tx_queue(dev, skb); |
|
netdev_start_xmit(skb, dlp->slave, txq, false); |
|
} |
|
return NETDEV_TX_OK; |
|
} |
|
|
|
static int dlci_config(struct net_device *dev, struct dlci_conf __user *conf, int get) |
|
{ |
|
struct dlci_conf config; |
|
struct dlci_local *dlp; |
|
struct frad_local *flp; |
|
int err; |
|
|
|
dlp = netdev_priv(dev); |
|
|
|
flp = netdev_priv(dlp->slave); |
|
|
|
if (!get) |
|
{ |
|
if (copy_from_user(&config, conf, sizeof(struct dlci_conf))) |
|
return -EFAULT; |
|
if (config.flags & ~DLCI_VALID_FLAGS) |
|
return -EINVAL; |
|
memcpy(&dlp->config, &config, sizeof(struct dlci_conf)); |
|
dlp->configured = 1; |
|
} |
|
|
|
err = (*flp->dlci_conf)(dlp->slave, dev, get); |
|
if (err) |
|
return err; |
|
|
|
if (get) |
|
{ |
|
if (copy_to_user(conf, &dlp->config, sizeof(struct dlci_conf))) |
|
return -EFAULT; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int dlci_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) |
|
{ |
|
struct dlci_local *dlp; |
|
|
|
if (!capable(CAP_NET_ADMIN)) |
|
return -EPERM; |
|
|
|
dlp = netdev_priv(dev); |
|
|
|
switch (cmd) |
|
{ |
|
case DLCI_GET_SLAVE: |
|
if (!*(short *)(dev->dev_addr)) |
|
return -EINVAL; |
|
|
|
strncpy(ifr->ifr_slave, dlp->slave->name, sizeof(ifr->ifr_slave)); |
|
break; |
|
|
|
case DLCI_GET_CONF: |
|
case DLCI_SET_CONF: |
|
if (!*(short *)(dev->dev_addr)) |
|
return -EINVAL; |
|
|
|
return dlci_config(dev, ifr->ifr_data, cmd == DLCI_GET_CONF); |
|
|
|
default: |
|
return -EOPNOTSUPP; |
|
} |
|
return 0; |
|
} |
|
|
|
static int dlci_change_mtu(struct net_device *dev, int new_mtu) |
|
{ |
|
struct dlci_local *dlp = netdev_priv(dev); |
|
|
|
return dev_set_mtu(dlp->slave, new_mtu); |
|
} |
|
|
|
static int dlci_open(struct net_device *dev) |
|
{ |
|
struct dlci_local *dlp; |
|
struct frad_local *flp; |
|
int err; |
|
|
|
dlp = netdev_priv(dev); |
|
|
|
if (!*(short *)(dev->dev_addr)) |
|
return -EINVAL; |
|
|
|
if (!netif_running(dlp->slave)) |
|
return -ENOTCONN; |
|
|
|
flp = netdev_priv(dlp->slave); |
|
err = (*flp->activate)(dlp->slave, dev); |
|
if (err) |
|
return err; |
|
|
|
netif_start_queue(dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int dlci_close(struct net_device *dev) |
|
{ |
|
struct dlci_local *dlp; |
|
struct frad_local *flp; |
|
|
|
netif_stop_queue(dev); |
|
|
|
dlp = netdev_priv(dev); |
|
|
|
flp = netdev_priv(dlp->slave); |
|
(*flp->deactivate)(dlp->slave, dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int dlci_add(struct dlci_add *dlci) |
|
{ |
|
struct net_device *master, *slave; |
|
struct dlci_local *dlp; |
|
struct frad_local *flp; |
|
int err = -EINVAL; |
|
|
|
|
|
/* validate slave device */ |
|
slave = dev_get_by_name(&init_net, dlci->devname); |
|
if (!slave) |
|
return -ENODEV; |
|
|
|
if (slave->type != ARPHRD_FRAD || netdev_priv(slave) == NULL) |
|
goto err1; |
|
|
|
/* create device name */ |
|
master = alloc_netdev(sizeof(struct dlci_local), "dlci%d", |
|
NET_NAME_UNKNOWN, dlci_setup); |
|
if (!master) { |
|
err = -ENOMEM; |
|
goto err1; |
|
} |
|
|
|
/* make sure same slave not already registered */ |
|
rtnl_lock(); |
|
list_for_each_entry(dlp, &dlci_devs, list) { |
|
if (dlp->slave == slave) { |
|
err = -EBUSY; |
|
goto err2; |
|
} |
|
} |
|
|
|
*(short *)(master->dev_addr) = dlci->dlci; |
|
|
|
dlp = netdev_priv(master); |
|
dlp->slave = slave; |
|
dlp->master = master; |
|
|
|
flp = netdev_priv(slave); |
|
err = (*flp->assoc)(slave, master); |
|
if (err < 0) |
|
goto err2; |
|
|
|
err = register_netdevice(master); |
|
if (err < 0) |
|
goto err2; |
|
|
|
strcpy(dlci->devname, master->name); |
|
|
|
list_add(&dlp->list, &dlci_devs); |
|
rtnl_unlock(); |
|
|
|
return 0; |
|
|
|
err2: |
|
rtnl_unlock(); |
|
free_netdev(master); |
|
err1: |
|
dev_put(slave); |
|
return err; |
|
} |
|
|
|
static int dlci_del(struct dlci_add *dlci) |
|
{ |
|
struct dlci_local *dlp; |
|
struct frad_local *flp; |
|
struct net_device *master, *slave; |
|
int err; |
|
bool found = false; |
|
|
|
rtnl_lock(); |
|
|
|
/* validate slave device */ |
|
master = __dev_get_by_name(&init_net, dlci->devname); |
|
if (!master) { |
|
err = -ENODEV; |
|
goto out; |
|
} |
|
|
|
list_for_each_entry(dlp, &dlci_devs, list) { |
|
if (dlp->master == master) { |
|
found = true; |
|
break; |
|
} |
|
} |
|
if (!found) { |
|
err = -ENODEV; |
|
goto out; |
|
} |
|
|
|
if (netif_running(master)) { |
|
err = -EBUSY; |
|
goto out; |
|
} |
|
|
|
dlp = netdev_priv(master); |
|
slave = dlp->slave; |
|
flp = netdev_priv(slave); |
|
|
|
err = (*flp->deassoc)(slave, master); |
|
if (!err) { |
|
list_del(&dlp->list); |
|
|
|
unregister_netdevice(master); |
|
|
|
dev_put(slave); |
|
} |
|
out: |
|
rtnl_unlock(); |
|
return err; |
|
} |
|
|
|
static int dlci_ioctl(unsigned int cmd, void __user *arg) |
|
{ |
|
struct dlci_add add; |
|
int err; |
|
|
|
if (!capable(CAP_NET_ADMIN)) |
|
return -EPERM; |
|
|
|
if (copy_from_user(&add, arg, sizeof(struct dlci_add))) |
|
return -EFAULT; |
|
|
|
switch (cmd) |
|
{ |
|
case SIOCADDDLCI: |
|
err = dlci_add(&add); |
|
|
|
if (!err) |
|
if (copy_to_user(arg, &add, sizeof(struct dlci_add))) |
|
return -EFAULT; |
|
break; |
|
|
|
case SIOCDELDLCI: |
|
err = dlci_del(&add); |
|
break; |
|
|
|
default: |
|
err = -EINVAL; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static const struct header_ops dlci_header_ops = { |
|
.create = dlci_header, |
|
}; |
|
|
|
static const struct net_device_ops dlci_netdev_ops = { |
|
.ndo_open = dlci_open, |
|
.ndo_stop = dlci_close, |
|
.ndo_do_ioctl = dlci_dev_ioctl, |
|
.ndo_start_xmit = dlci_transmit, |
|
.ndo_change_mtu = dlci_change_mtu, |
|
}; |
|
|
|
static void dlci_setup(struct net_device *dev) |
|
{ |
|
struct dlci_local *dlp = netdev_priv(dev); |
|
|
|
dev->flags = 0; |
|
dev->header_ops = &dlci_header_ops; |
|
dev->netdev_ops = &dlci_netdev_ops; |
|
dev->needs_free_netdev = true; |
|
|
|
dlp->receive = dlci_receive; |
|
|
|
dev->type = ARPHRD_DLCI; |
|
dev->hard_header_len = sizeof(struct frhdr); |
|
dev->addr_len = sizeof(short); |
|
|
|
} |
|
|
|
/* if slave is unregistering, then cleanup master */ |
|
static int dlci_dev_event(struct notifier_block *unused, |
|
unsigned long event, void *ptr) |
|
{ |
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr); |
|
|
|
if (dev_net(dev) != &init_net) |
|
return NOTIFY_DONE; |
|
|
|
if (event == NETDEV_UNREGISTER) { |
|
struct dlci_local *dlp; |
|
|
|
list_for_each_entry(dlp, &dlci_devs, list) { |
|
if (dlp->slave == dev) { |
|
list_del(&dlp->list); |
|
unregister_netdevice(dlp->master); |
|
dev_put(dlp->slave); |
|
break; |
|
} |
|
} |
|
} |
|
return NOTIFY_DONE; |
|
} |
|
|
|
static struct notifier_block dlci_notifier = { |
|
.notifier_call = dlci_dev_event, |
|
}; |
|
|
|
static int __init init_dlci(void) |
|
{ |
|
dlci_ioctl_set(dlci_ioctl); |
|
register_netdevice_notifier(&dlci_notifier); |
|
|
|
printk("%s.\n", version); |
|
|
|
return 0; |
|
} |
|
|
|
static void __exit dlci_exit(void) |
|
{ |
|
struct dlci_local *dlp, *nxt; |
|
|
|
dlci_ioctl_set(NULL); |
|
unregister_netdevice_notifier(&dlci_notifier); |
|
|
|
rtnl_lock(); |
|
list_for_each_entry_safe(dlp, nxt, &dlci_devs, list) { |
|
unregister_netdevice(dlp->master); |
|
dev_put(dlp->slave); |
|
} |
|
rtnl_unlock(); |
|
} |
|
|
|
module_init(init_dlci); |
|
module_exit(dlci_exit); |
|
|
|
MODULE_AUTHOR("Mike McLagan"); |
|
MODULE_DESCRIPTION("Frame Relay DLCI layer"); |
|
MODULE_LICENSE("GPL");
|
|
|