Phonet: implement GPRS virtual interface over PEP socket
Rémi Denis-Courmont [Sun, 5 Oct 2008 18:16:16 +0000 (11:16 -0700)]
Signed-off-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/linux/phonet.h
include/linux/socket.h
include/net/phonet/gprs.h [new file with mode: 0644]
include/net/phonet/pep.h
net/phonet/Makefile
net/phonet/pep-gprs.c [new file with mode: 0644]
net/phonet/pep.c
net/phonet/socket.c

index f921852..c9609f9 100644 (file)
 #define PN_PROTO_PIPE          2
 #define PHONET_NPROTO          3
 
+/* Socket options for SOL_PNPIPE level */
+#define PNPIPE_ENCAP           1
+#define PNPIPE_IFINDEX         2
+
 #define PNADDR_ANY             0
 #define PNPORT_RESOURCE_ROUTING        0
 
+/* Values for PNPIPE_ENCAP option */
+#define PNPIPE_ENCAP_NONE      0
+#define PNPIPE_ENCAP_IP                1
+
 /* ioctls */
 #define SIOCPNGETOBJECT                (SIOCPROTOPRIVATE + 0)
 
index 818ca33..20fc4bb 100644 (file)
@@ -297,6 +297,7 @@ struct ucred {
 #define SOL_RXRPC      272
 #define SOL_PPPOL2TP   273
 #define SOL_BLUETOOTH  274
+#define SOL_PNPIPE     275
 
 /* IPX options */
 #define IPX_TYPE       1
diff --git a/include/net/phonet/gprs.h b/include/net/phonet/gprs.h
new file mode 100644 (file)
index 0000000..928daf5
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * File: pep_gprs.h
+ *
+ * GPRS over Phonet pipe end point socket
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Author: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef NET_PHONET_GPRS_H
+#define NET_PHONET_GPRS_H
+
+struct sock;
+struct sk_buff;
+
+int pep_writeable(struct sock *sk);
+int pep_write(struct sock *sk, struct sk_buff *skb);
+struct sk_buff *pep_read(struct sock *sk);
+
+int gprs_attach(struct sock *sk);
+void gprs_detach(struct sock *sk);
+
+#endif
index 4d79564..fcd7930 100644 (file)
@@ -35,6 +35,7 @@ struct pep_sock {
        struct sock             *listener;
        struct sk_buff_head     ctrlreq_queue;
 #define PNPIPE_CTRLREQ_MAX     10
+       int                     ifindex;
        u16                     peer_type;      /* peer type/subtype */
        u8                      pipe_handle;
 
index 505df2a..d62bbba 100644 (file)
@@ -8,4 +8,4 @@ phonet-objs := \
        sysctl.o \
        af_phonet.o
 
-pn_pep-objs := pep.o
+pn_pep-objs := pep.o pep-gprs.o
diff --git a/net/phonet/pep-gprs.c b/net/phonet/pep-gprs.c
new file mode 100644 (file)
index 0000000..9978afb
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * File: pep-gprs.c
+ *
+ * GPRS over Phonet pipe end point socket
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Author: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <net/sock.h>
+
+#include <linux/if_phonet.h>
+#include <net/tcp_states.h>
+#include <net/phonet/gprs.h>
+
+#define GPRS_DEFAULT_MTU 1400
+
+struct gprs_dev {
+       struct sock             *sk;
+       void                    (*old_state_change)(struct sock *);
+       void                    (*old_data_ready)(struct sock *, int);
+       void                    (*old_write_space)(struct sock *);
+
+       struct net_device       *net;
+       struct net_device_stats stats;
+
+       struct sk_buff_head     tx_queue;
+       struct work_struct      tx_work;
+       spinlock_t              tx_lock;
+       unsigned                tx_max;
+};
+
+static int gprs_type_trans(struct sk_buff *skb)
+{
+       const u8 *pvfc;
+       u8 buf;
+
+       pvfc = skb_header_pointer(skb, 0, 1, &buf);
+       if (!pvfc)
+               return 0;
+       /* Look at IP version field */
+       switch (*pvfc >> 4) {
+       case 4:
+               return htons(ETH_P_IP);
+       case 6:
+               return htons(ETH_P_IPV6);
+       }
+       return 0;
+}
+
+/*
+ * Socket callbacks
+ */
+
+static void gprs_state_change(struct sock *sk)
+{
+       struct gprs_dev *dev = sk->sk_user_data;
+
+       if (sk->sk_state == TCP_CLOSE_WAIT) {
+               netif_stop_queue(dev->net);
+               netif_carrier_off(dev->net);
+       }
+}
+
+static int gprs_recv(struct gprs_dev *dev, struct sk_buff *skb)
+{
+       int err = 0;
+       u16 protocol = gprs_type_trans(skb);
+
+       if (!protocol) {
+               err = -EINVAL;
+               goto drop;
+       }
+
+       if (likely(skb_headroom(skb) & 3)) {
+               struct sk_buff *rskb, *fs;
+               int flen = 0;
+
+               /* Phonet Pipe data header is misaligned (3 bytes),
+                * so wrap the IP packet as a single fragment of an head-less
+                * socket buffer. The network stack will pull what it needs,
+                * but at least, the whole IP payload is not memcpy'd. */
+               rskb = netdev_alloc_skb(dev->net, 0);
+               if (!rskb) {
+                       err = -ENOBUFS;
+                       goto drop;
+               }
+               skb_shinfo(rskb)->frag_list = skb;
+               rskb->len += skb->len;
+               rskb->data_len += rskb->len;
+               rskb->truesize += rskb->len;
+
+               /* Avoid nested fragments */
+               for (fs = skb_shinfo(skb)->frag_list; fs; fs = fs->next)
+                       flen += fs->len;
+               skb->next = skb_shinfo(skb)->frag_list;
+               skb_shinfo(skb)->frag_list = NULL;
+               skb->len -= flen;
+               skb->data_len -= flen;
+               skb->truesize -= flen;
+
+               skb = rskb;
+       }
+
+       skb->protocol = protocol;
+       skb_reset_mac_header(skb);
+       skb->dev = dev->net;
+
+       if (likely(dev->net->flags & IFF_UP)) {
+               dev->stats.rx_packets++;
+               dev->stats.rx_bytes += skb->len;
+               netif_rx(skb);
+               skb = NULL;
+       } else
+               err = -ENODEV;
+
+drop:
+       if (skb) {
+               dev_kfree_skb(skb);
+               dev->stats.rx_dropped++;
+       }
+       return err;
+}
+
+static void gprs_data_ready(struct sock *sk, int len)
+{
+       struct gprs_dev *dev = sk->sk_user_data;
+       struct sk_buff *skb;
+
+       while ((skb = pep_read(sk)) != NULL) {
+               skb_orphan(skb);
+               gprs_recv(dev, skb);
+       }
+}
+
+static void gprs_write_space(struct sock *sk)
+{
+       struct gprs_dev *dev = sk->sk_user_data;
+       unsigned credits = pep_writeable(sk);
+
+       spin_lock_bh(&dev->tx_lock);
+       dev->tx_max = credits;
+       if (credits > skb_queue_len(&dev->tx_queue))
+               netif_wake_queue(dev->net);
+       spin_unlock_bh(&dev->tx_lock);
+}
+
+/*
+ * Network device callbacks
+ */
+
+static int gprs_xmit(struct sk_buff *skb, struct net_device *net)
+{
+       struct gprs_dev *dev = netdev_priv(net);
+
+       switch (skb->protocol) {
+       case  htons(ETH_P_IP):
+       case  htons(ETH_P_IPV6):
+               break;
+       default:
+               dev_kfree_skb(skb);
+               return 0;
+       }
+
+       spin_lock(&dev->tx_lock);
+       if (likely(skb_queue_len(&dev->tx_queue) < dev->tx_max)) {
+               skb_queue_tail(&dev->tx_queue, skb);
+               skb = NULL;
+       }
+       if (skb_queue_len(&dev->tx_queue) >= dev->tx_max)
+               netif_stop_queue(net);
+       spin_unlock(&dev->tx_lock);
+
+       schedule_work(&dev->tx_work);
+       if (unlikely(skb))
+               dev_kfree_skb(skb);
+       return 0;
+}
+
+static void gprs_tx(struct work_struct *work)
+{
+       struct gprs_dev *dev = container_of(work, struct gprs_dev, tx_work);
+       struct sock *sk = dev->sk;
+       struct sk_buff *skb;
+
+       while ((skb = skb_dequeue(&dev->tx_queue)) != NULL) {
+               int err;
+
+               dev->stats.tx_bytes += skb->len;
+               dev->stats.tx_packets++;
+
+               skb_orphan(skb);
+               skb_set_owner_w(skb, sk);
+
+               lock_sock(sk);
+               err = pep_write(sk, skb);
+               if (err) {
+                       LIMIT_NETDEBUG(KERN_WARNING"%s: TX error (%d)\n",
+                                       dev->net->name, err);
+                       dev->stats.tx_aborted_errors++;
+                       dev->stats.tx_errors++;
+               }
+               release_sock(sk);
+       }
+
+       lock_sock(sk);
+       gprs_write_space(sk);
+       release_sock(sk);
+}
+
+static int gprs_set_mtu(struct net_device *net, int new_mtu)
+{
+       if ((new_mtu < 576) || (new_mtu > (PHONET_MAX_MTU - 11)))
+               return -EINVAL;
+
+       net->mtu = new_mtu;
+       return 0;
+}
+
+static struct net_device_stats *gprs_get_stats(struct net_device *net)
+{
+       struct gprs_dev *dev = netdev_priv(net);
+
+       return &dev->stats;
+}
+
+static void gprs_setup(struct net_device *net)
+{
+       net->features           = NETIF_F_FRAGLIST;
+       net->type               = ARPHRD_NONE;
+       net->flags              = IFF_POINTOPOINT | IFF_NOARP;
+       net->mtu                = GPRS_DEFAULT_MTU;
+       net->hard_header_len    = 0;
+       net->addr_len           = 0;
+       net->tx_queue_len       = 10;
+
+       net->destructor         = free_netdev;
+       net->hard_start_xmit    = gprs_xmit; /* mandatory */
+       net->change_mtu         = gprs_set_mtu;
+       net->get_stats          = gprs_get_stats;
+}
+
+/*
+ * External interface
+ */
+
+/*
+ * Attach a GPRS interface to a datagram socket.
+ * Returns the interface index on success, negative error code on error.
+ */
+int gprs_attach(struct sock *sk)
+{
+       static const char ifname[] = "gprs%d";
+       struct gprs_dev *dev;
+       struct net_device *net;
+       int err;
+
+       if (unlikely(sk->sk_type == SOCK_STREAM))
+               return -EINVAL; /* need packet boundaries */
+
+       /* Create net device */
+       net = alloc_netdev(sizeof(*dev), ifname, gprs_setup);
+       if (!net)
+               return -ENOMEM;
+       dev = netdev_priv(net);
+       dev->net = net;
+       dev->tx_max = 0;
+       spin_lock_init(&dev->tx_lock);
+       skb_queue_head_init(&dev->tx_queue);
+       INIT_WORK(&dev->tx_work, gprs_tx);
+
+       netif_stop_queue(net);
+       err = register_netdev(net);
+       if (err) {
+               free_netdev(net);
+               return err;
+       }
+
+       lock_sock(sk);
+       if (unlikely(sk->sk_user_data)) {
+               err = -EBUSY;
+               goto out_rel;
+       }
+       if (unlikely((1 << sk->sk_state & (TCPF_CLOSE|TCPF_LISTEN)) ||
+                       sock_flag(sk, SOCK_DEAD))) {
+               err = -EINVAL;
+               goto out_rel;
+       }
+       sk->sk_user_data        = dev;
+       dev->old_state_change   = sk->sk_state_change;
+       dev->old_data_ready     = sk->sk_data_ready;
+       dev->old_write_space    = sk->sk_write_space;
+       sk->sk_state_change     = gprs_state_change;
+       sk->sk_data_ready       = gprs_data_ready;
+       sk->sk_write_space      = gprs_write_space;
+       release_sock(sk);
+
+       sock_hold(sk);
+       dev->sk = sk;
+
+       printk(KERN_DEBUG"%s: attached\n", net->name);
+       gprs_write_space(sk); /* kick off TX */
+       return net->ifindex;
+
+out_rel:
+       release_sock(sk);
+       unregister_netdev(net);
+       return err;
+}
+
+void gprs_detach(struct sock *sk)
+{
+       struct gprs_dev *dev = sk->sk_user_data;
+       struct net_device *net = dev->net;
+
+       lock_sock(sk);
+       sk->sk_user_data        = NULL;
+       sk->sk_state_change     = dev->old_state_change;
+       sk->sk_data_ready       = dev->old_data_ready;
+       sk->sk_write_space      = dev->old_write_space;
+       release_sock(sk);
+
+       printk(KERN_DEBUG"%s: detached\n", net->name);
+       unregister_netdev(net);
+       flush_scheduled_work();
+       sock_put(sk);
+       skb_queue_purge(&dev->tx_queue);
+}
index d564d07..bc6d50f 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/phonet.h>
 #include <net/phonet/phonet.h>
 #include <net/phonet/pep.h>
+#include <net/phonet/gprs.h>
 
 /* sk_state values:
  * TCP_CLOSE           sock not in use yet
@@ -612,6 +613,7 @@ drop:
 static void pep_sock_close(struct sock *sk, long timeout)
 {
        struct pep_sock *pn = pep_sk(sk);
+       int ifindex = 0;
 
        sk_common_release(sk);
 
@@ -625,7 +627,12 @@ static void pep_sock_close(struct sock *sk, long timeout)
                        sk_del_node_init(sknode);
                sk->sk_state = TCP_CLOSE;
        }
+       ifindex = pn->ifindex;
+       pn->ifindex = 0;
        release_sock(sk);
+
+       if (ifindex)
+               gprs_detach(sk);
 }
 
 static int pep_wait_connreq(struct sock *sk, int noblock)
@@ -730,12 +737,107 @@ static int pep_init(struct sock *sk)
        return 0;
 }
 
+static int pep_setsockopt(struct sock *sk, int level, int optname,
+                               char __user *optval, int optlen)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       int val = 0, err = 0;
+
+       if (level != SOL_PNPIPE)
+               return -ENOPROTOOPT;
+       if (optlen >= sizeof(int)) {
+               if (get_user(val, (int __user *) optval))
+                       return -EFAULT;
+       }
+
+       lock_sock(sk);
+       switch (optname) {
+       case PNPIPE_ENCAP:
+               if (val && val != PNPIPE_ENCAP_IP) {
+                       err = -EINVAL;
+                       break;
+               }
+               if (!pn->ifindex == !val)
+                       break; /* Nothing to do! */
+               if (!capable(CAP_NET_ADMIN)) {
+                       err = -EPERM;
+                       break;
+               }
+               if (val) {
+                       release_sock(sk);
+                       err = gprs_attach(sk);
+                       if (err > 0) {
+                               pn->ifindex = err;
+                               err = 0;
+                       }
+               } else {
+                       pn->ifindex = 0;
+                       release_sock(sk);
+                       gprs_detach(sk);
+                       err = 0;
+               }
+               goto out_norel;
+       default:
+               err = -ENOPROTOOPT;
+       }
+       release_sock(sk);
+
+out_norel:
+       return err;
+}
+
+static int pep_getsockopt(struct sock *sk, int level, int optname,
+                               char __user *optval, int __user *optlen)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       int len, val;
+
+       if (level != SOL_PNPIPE)
+               return -ENOPROTOOPT;
+       if (get_user(len, optlen))
+               return -EFAULT;
+
+       switch (optname) {
+       case PNPIPE_ENCAP:
+               val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE;
+               break;
+       case PNPIPE_IFINDEX:
+               val = pn->ifindex;
+               break;
+       default:
+               return -ENOPROTOOPT;
+       }
+
+       len = min_t(unsigned int, sizeof(int), len);
+       if (put_user(len, optlen))
+               return -EFAULT;
+       if (put_user(val, (int __user *) optval))
+               return -EFAULT;
+       return 0;
+}
+
+static int pipe_skb_send(struct sock *sk, struct sk_buff *skb)
+{
+       struct pep_sock *pn = pep_sk(sk);
+       struct pnpipehdr *ph;
+
+       skb_push(skb, 3);
+       skb_reset_transport_header(skb);
+       ph = pnp_hdr(skb);
+       ph->utid = 0;
+       ph->message_id = PNS_PIPE_DATA;
+       ph->pipe_handle = pn->pipe_handle;
+       if (pn_flow_safe(pn->tx_fc) && pn->tx_credits)
+               pn->tx_credits--;
+
+       return pn_skb_send(sk, skb, &pipe_srv);
+}
+
 static int pep_sendmsg(struct kiocb *iocb, struct sock *sk,
                        struct msghdr *msg, size_t len)
 {
        struct pep_sock *pn = pep_sk(sk);
        struct sk_buff *skb = NULL;
-       struct pnpipehdr *ph;
        long timeo;
        int flags = msg->msg_flags;
        int err, done;
@@ -801,16 +903,7 @@ disabled:
        if (err < 0)
                goto out;
 
-       __skb_push(skb, 3);
-       skb_reset_transport_header(skb);
-       ph = pnp_hdr(skb);
-       ph->utid = 0;
-       ph->message_id = PNS_PIPE_DATA;
-       ph->pipe_handle = pn->pipe_handle;
-       if (pn_flow_safe(pn->tx_fc)) /* credit-based flow control */
-               pn->tx_credits--;
-
-       err = pn_skb_send(sk, skb, &pipe_srv);
+       err = pipe_skb_send(sk, skb);
        if (err >= 0)
                err = len; /* success! */
        skb = NULL;
@@ -820,6 +913,50 @@ out:
        return err;
 }
 
+int pep_writeable(struct sock *sk)
+{
+       struct pep_sock *pn = pep_sk(sk);
+
+       return (sk->sk_state == TCP_ESTABLISHED) ? pn->tx_credits : 0;
+}
+
+int pep_write(struct sock *sk, struct sk_buff *skb)
+{
+       struct sk_buff *rskb, *fs;
+       int flen = 0;
+
+       rskb = alloc_skb(MAX_PNPIPE_HEADER, GFP_ATOMIC);
+       if (!rskb) {
+               kfree_skb(skb);
+               return -ENOMEM;
+       }
+       skb_shinfo(rskb)->frag_list = skb;
+       rskb->len += skb->len;
+       rskb->data_len += rskb->len;
+       rskb->truesize += rskb->len;
+
+       /* Avoid nested fragments */
+       for (fs = skb_shinfo(skb)->frag_list; fs; fs = fs->next)
+               flen += fs->len;
+       skb->next = skb_shinfo(skb)->frag_list;
+       skb_shinfo(skb)->frag_list = NULL;
+       skb->len -= flen;
+       skb->data_len -= flen;
+       skb->truesize -= flen;
+
+       skb_reserve(rskb, MAX_PHONET_HEADER + 3);
+       return pipe_skb_send(sk, rskb);
+}
+
+struct sk_buff *pep_read(struct sock *sk)
+{
+       struct sk_buff *skb = skb_dequeue(&sk->sk_receive_queue);
+
+       if (sk->sk_state == TCP_ESTABLISHED)
+               pipe_grant_credits(sk);
+       return skb;
+}
+
 static int pep_recvmsg(struct kiocb *iocb, struct sock *sk,
                        struct msghdr *msg, size_t len, int noblock,
                        int flags, int *addr_len)
@@ -902,6 +1039,8 @@ static struct proto pep_proto = {
        .accept         = pep_sock_accept,
        .ioctl          = pep_ioctl,
        .init           = pep_init,
+       .setsockopt     = pep_setsockopt,
+       .getsockopt     = pep_getsockopt,
        .sendmsg        = pep_sendmsg,
        .recvmsg        = pep_recvmsg,
        .backlog_rcv    = pep_do_rcv,
index a9c3d1f..d817401 100644 (file)
@@ -342,11 +342,11 @@ const struct proto_ops phonet_stream_ops = {
        .ioctl          = pn_socket_ioctl,
        .listen         = pn_socket_listen,
        .shutdown       = sock_no_shutdown,
-       .setsockopt     = sock_no_setsockopt,
-       .getsockopt     = sock_no_getsockopt,
+       .setsockopt     = sock_common_setsockopt,
+       .getsockopt     = sock_common_getsockopt,
 #ifdef CONFIG_COMPAT
-       .compat_setsockopt = sock_no_setsockopt,
-       .compat_getsockopt = compat_sock_no_getsockopt,
+       .compat_setsockopt = compat_sock_common_setsockopt,
+       .compat_getsockopt = compat_sock_common_getsockopt,
 #endif
        .sendmsg        = pn_socket_sendmsg,
        .recvmsg        = sock_common_recvmsg,