[PATCH] Generic PPP for Generic HDLC [1/3]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is part 1 of a 3-message set that adds generic PPP support to the
generic HDLC layer.  These changes were made in PPC Linux 2.4.21-pre4.

These 3 parts DO NOT include changes to the PPP daemon to enable it to
work with WAN HDLC devices.  I shall post those changes to the
linux-ppp email list, under the subject "[PATCH] WAN HDLC Support".

I welcome comments and suggestions (polite or not).  Eventually, this
will be used in the real world ;) and I would like it to work
properly.
______________________________________________________________________

Part 1 is a new drivers/net/wan/hdlc_ppp.c.  I have included it
verbatim because I think it is easier to read than a patch (since
almost all of it is different from the previous version).

Part 2 patches the following:

  * In include/linux/hdlc.h, the PPP state variables have changed
    in accordance with the switch from syncppp.c to ppp_generic.

  * In drivers/net/wan/Makefile, syncppp.c is no longer required
    when CONFIG_HDLC_PPP is defined.

  * In include/linux/hdlc/ioctl.h, there is now a structure
    (ppp_proto) for retrieving PPP protocol information via
    the SIOCWANDEV(IF_GET_PROTO) ioctl.

  * In include/linux/if.h, ppp_proto is part of the union used in
    SIOCWANDEV.

Part 3 patches the "sethdlc" program to use the new ppp_proto
structure.

-- 
Dan Eble <dane@aiinet.com>  _____  .
                           |  _  |/|
Applied Innovation Inc.    | |_| | |
http://www.aiinet.com/     |__/|_|_|

/*
 * Generic HDLC support routines for Linux
 * Point-to-point protocol 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.
 */

#include <linux/config.h>
#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/inetdevice.h>
#include <linux/lapb.h>
#include <linux/rtnetlink.h>
#include <linux/hdlc.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
#include <linux/ppp_channel.h>

/**************************************************************************
 * Prototypes
 *************************************************************************/

static int hdlc_ppp_register(hdlc_device *hdlc);
static void hdlc_ppp_unregister(hdlc_device *hdlc);
static int hdlc_ppp_dont_change_mtu(struct net_device *dev, int new_mtu);
static void hdlc_ppp_netif_rx(struct sk_buff *skb);
static int hdlc_genppp_start_xmit(struct ppp_channel *, struct sk_buff *);
static int hdlc_genppp_ioctl(struct ppp_channel*, unsigned int, unsigned long);

/**************************************************************************
 * Variables
 *************************************************************************/

static struct ppp_channel_ops hdlc_genppp_ops =
{
	.start_xmit =	hdlc_genppp_start_xmit,
	.ioctl =	hdlc_genppp_ioctl
};

/**************************************************************************
 * Inline Functions
 *************************************************************************/

/** Get the PPP channel owned by an HDLC device. */
static __inline__ struct ppp_channel* hdlc_to_chan(hdlc_device *hdlc)
{
	return (struct ppp_channel*)&hdlc->state.ppp.chan;
}

/** Get an HDLC device from its PPP channel. */
static __inline__ hdlc_device* chan_to_hdlc(struct ppp_channel *chan)
{
	return (hdlc_device*)chan->private;
}

/** Check UP, RUNNING, and carrier all at once. */
static __inline__ int netif_good_to_go(struct net_device *dev)
{
	return (dev->flags & IFF_UP) &&
		netif_running(dev) &&
		netif_carrier_ok(dev);
}

/**************************************************************************
 * Functions
 *************************************************************************/

/**
 * Initialize and register a PPP channel with the generic PPP layer.
 *
 * hdlc_ppp_register() is called from process context while the
 * interface is down.
 */
static int hdlc_ppp_register(hdlc_device *hdlc)
{
	struct net_device *const dev = hdlc_to_dev(hdlc);
	struct ppp_channel *const chan = hdlc_to_chan(hdlc);
	int old_mtu;
	int err;

	/**
	 * Save the old change_mtu and use a new one that prevents the
	 * MTU from being changed while the PPP channel is registered.
	 * Then, call the old function to set an MTU adequate for PPP.
	 */
	hdlc->state.ppp.old_change_mtu = dev->change_mtu;
	dev->change_mtu = hdlc_ppp_dont_change_mtu;
	old_mtu = dev->mtu;
	err = hdlc->state.ppp.old_change_mtu(dev, PPP_HDRLEN + PPP_MTU);
	if (err)
	{
		printk(KERN_NOTICE
		       "%s: Changing MTU to %d for PPP failed (%d).\n"
		       "%s: Using current MTU, %d.\n",
		       dev->name, PPP_HDRLEN + PPP_MTU, err,
		       dev->name, dev->mtu);
	}

	/* reset the PPP state */
	memset(&hdlc->state.ppp, sizeof(hdlc->state.ppp), 0);
	chan->private = hdlc;
	chan->ops = &hdlc_genppp_ops;
	chan->mtu = dev->mtu - PPP_HDRLEN;
	chan->hdrlen = 2; /* address & control bytes */

 	/* The ppp_channel object must exist from the time that
	 * ppp_register_channel() is called until after the call to
	 * ppp_unregister_channel() returns.
	 */
	err = ppp_register_channel(chan);
	if (!err)
	{
		hdlc->open = NULL;
		hdlc->stop = NULL;
		hdlc->proto_detach = hdlc_ppp_unregister;
		hdlc->netif_rx = hdlc_ppp_netif_rx;
		hdlc->type_trans = NULL;	/* force use of netif_rx() */
		hdlc->proto = IF_PROTO_PPP;

		dev->hard_start_xmit = hdlc->xmit;
		dev->hard_header = NULL;
		dev->type = ARPHRD_RAWHDLC;
		dev->hard_header_len = 0;
		dev->flags = IFF_POINTOPOINT | IFF_NOARP;
		dev->addr_len = 0;

		hdlc->state.ppp.settings.channel =
			ppp_channel_index(hdlc_to_chan(hdlc));

		goto Success;
	}

	/* restore old MTU */
	hdlc->state.ppp.old_change_mtu(dev, old_mtu);
	dev->change_mtu = hdlc->state.ppp.old_change_mtu;

 Success:
	return err;
}



/**
 * Close the channel to the generic PPP layer.
 *
 * hdlc_ppp_unregister() is called from process context while the
 * interface is down.
 */
static void hdlc_ppp_unregister(hdlc_device *hdlc)
{
	struct net_device *const dev = hdlc_to_dev(hdlc);
	struct ppp_channel *const chan = hdlc_to_chan(hdlc);

	/* No thread may be in a call to any of ppp_input(),
	 * ppp_input_error(), ppp_output_wakeup(), ppp_channel_index()
	 * or ppp_unit_number() for a channel at the time that
	 * ppp_unregister_channel() is called for that channel.
	 */
	/* By the time a call to ppp_unregister_channel() returns, no
	 * thread will be executing in a call from the generic layer
	 * to that channel's start_xmit() or ioctl() function, and the
	 * generic layer will not call either of those functions
	 * subsequently.
	 */
	ppp_unregister_channel(chan);

	dev->change_mtu = hdlc->state.ppp.old_change_mtu;
	hdlc->state.ppp.settings.channel = -1;
}



/**
 * The channel should initialize the `mtu' and `hdrlen' fields before
 * calling ppp_register_channel() and not change them until after
 * ppp_unregister_channel() returns.
 */
static int hdlc_ppp_dont_change_mtu(struct net_device *dev, int new_mtu)
{
	return -EBUSY;
}



/**
 * Receive a buffer from the hardware, strip the PPP header, and pass
 * the rest to the generic PPP layer.
 */
static void hdlc_ppp_netif_rx(struct sk_buff *skb)
{
	struct ppp_channel *const chan = hdlc_to_chan(dev_to_hdlc(skb->dev));
	unsigned char *p;

	/* strip address/control field if present */
	p = skb->data;
	if (p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) {
		/* chop off address/control */
		if (skb->len < 3)
			goto err;
		p = skb_pull(skb, 2);
	}

	/* decompress protocol field if compressed */
	if (p[0] & 1) {
		/* protocol is compressed */
		skb_push(skb, 1)[0] = 0;
	} else if (skb->len < 2)
		goto err;

	/* pass to generic layer */
	ppp_input(chan, skb);
	return;

 err:
	kfree_skb(skb);
	ppp_input_error(chan, 0);
}



/**
 * Send a packet (or multilink fragment) on this channel.
 * Returns 1 if it was accepted, 0 to queue it for later.
 *
 * The generic layer will not call the start_xmit() function for a
 * channel while any thread is already executing in that function for
 * that channel.
 *
 * The generic layer may call the channel start_xmit() function at
 * softirq/BH level but will not call it at interrupt level.  Thus the
 * start_xmit() function may not block.
 */
static int hdlc_genppp_start_xmit(struct ppp_channel *chan,
				  struct sk_buff *skb)
{
	hdlc_device *const hdlc = chan_to_hdlc(chan);
	struct net_device *const dev = hdlc_to_dev(hdlc);
	int proto;
	unsigned char *data;
	int islcp;

	if (!netif_good_to_go(dev)) {
		/** @todo Instead, return 0 to make generic layer
		 * queue the packet.  That will require calling
		 * ppp_output_wakeup() at an appropriate time. */
		kfree_skb(skb);
		++hdlc->stats.tx_dropped;
		return 1;
	}

	data  = skb->data;
	proto = (data[0] << 8) + data[1];

	/* LCP packets with codes between 1 (configure-request)
	 * and 7 (code-reject) must be sent as though no options
	 * have been negotiated.
	 */
	islcp = proto == PPP_LCP && 1 <= data[2] && data[2] <= 7;

	/* compress protocol field if option enabled */
	if (data[0] == 0 && (hdlc->state.ppp.flags & SC_COMP_PROT) && !islcp)
		skb_pull(skb,1);

	/* prepend address/control fields if necessary */
	if ((hdlc->state.ppp.flags & SC_COMP_AC) == 0 || islcp) {
		if (skb_headroom(skb) < 2) {
			struct sk_buff *npkt = dev_alloc_skb(skb->len + 2);
			if (npkt == NULL) {
				kfree_skb(skb);
				++hdlc->stats.tx_dropped;
				return 1;
			}
			skb_reserve(npkt,2);
			memcpy(skb_put(npkt,skb->len), skb->data, skb->len);
			kfree_skb(skb);
			skb = npkt;
		}
		skb_push(skb,2);
		skb->data[0] = PPP_ALLSTATIONS;
		skb->data[1] = PPP_UI;
	}

	skb->dev = dev;
	skb->nh.raw = skb->data;

	dev_queue_xmit(skb);
	return 1;
}



/**
 * Handle an ioctl call that has come in via /dev/ppp.
 *
 * The generic layer will only call the channel ioctl() function in
 * process context.
 *
 * The generic layer will not call the ioctl() function for a channel
 * while any thread is already executing in that function for that
 * channel.
 */
static int hdlc_genppp_ioctl(struct ppp_channel *chan,
			     unsigned int cmd, unsigned long arg)
{
	hdlc_device *const hdlc = chan_to_hdlc(chan);
	struct net_device *const dev = hdlc_to_dev(hdlc);
	int val;
	int err;

	err = -EFAULT;
	switch (cmd) {
	case PPPIOCGMRU:
		if (put_user(dev->mtu - PPP_HDRLEN, (int *) arg))
			break;
		err = 0;
		break;

	case PPPIOCSMRU:
		if (get_user(val, (int *) arg))
			break;

		if (val > dev->mtu - PPP_HDRLEN)
			err = -EINVAL;
		else
			err = 0;
		break;

	case PPPIOCSFLAGS:
		if (get_user(val, (int *) arg))
			break;
		val &= SC_MASK;	/* keep the bits that are allowed to be set */
		hdlc->state.ppp.flags &= ~SC_MASK;
		hdlc->state.ppp.flags |= val;
		err = 0;
		break;

	default:
		err = -ENOTTY;
	}
	return err;
}



int hdlc_ppp_ioctl(hdlc_device *hdlc, struct ifreq *ifr)
{
	struct net_device *dev = hdlc_to_dev(hdlc);
	int err;

	switch (ifr->ifr_settings.type) {
	case IF_GET_PROTO:
		ifr->ifr_settings.type = IF_PROTO_PPP;
		if (ifr->ifr_settings.size < sizeof(ppp_proto)) {
			ifr->ifr_settings.size = sizeof(ppp_proto);
			return -ENOBUFS;
		}
		if (copy_to_user(ifr->ifr_settings.ifs_ifsu.ppp,
				 &hdlc->state.ppp.settings, sizeof(ppp_proto)))
			return -EFAULT;
		return 0;

	case IF_PROTO_PPP:
		if(!capable(CAP_NET_ADMIN))
			return -EPERM;

		if(dev->flags & IFF_UP)
			return -EBUSY;

		/* no settable parameters */

		err = hdlc->attach(hdlc, ENCODING_NRZ,PARITY_CRC16_PR1_CCITT);
		if (!err) {
			hdlc_proto_detach(hdlc);
			err = hdlc_ppp_register(hdlc);
		}

		return err;
	}

	return -EINVAL;
}

-
: send the line "unsubscribe linux-net" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux 802.1Q VLAN]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Git]     [Bugtraq]     [Yosemite News and Information]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux PCI]     [Linux Admin]     [Samba]

  Powered by Linux