Message ID | 1222855985-22859-5-git-send-email-remi.denis-courmont@nokia.com |
---|---|
State | Superseded, archived |
Delegated to: | David Miller |
Headers | show |
Em Wed, Oct 01, 2008 at 01:13:04PM +0300, Remi Denis-Courmont escreveu: > From: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> > > Signed-off-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com> > --- > include/linux/phonet.h | 8 + > include/linux/socket.h | 1 + > include/net/phonet/gprs.h | 38 +++++ > include/net/phonet/pep.h | 1 + > net/phonet/Makefile | 2 +- > net/phonet/pep-gprs.c | 341 +++++++++++++++++++++++++++++++++++++++++++++ > net/phonet/pep.c | 157 ++++++++++++++++++++-- > net/phonet/socket.c | 8 +- > 8 files changed, 542 insertions(+), 14 deletions(-) > create mode 100644 include/net/phonet/gprs.h > create mode 100644 net/phonet/pep-gprs.c > > diff --git a/include/linux/phonet.h b/include/linux/phonet.h > index f921852..c9609f9 100644 > --- a/include/linux/phonet.h > +++ b/include/linux/phonet.h > @@ -31,9 +31,17 @@ > #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) > > diff --git a/include/linux/socket.h b/include/linux/socket.h > index 818ca33..20fc4bb 100644 > --- a/include/linux/socket.h > +++ b/include/linux/socket.h > @@ -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 > index 0000000..928daf5 > --- /dev/null > +++ b/include/net/phonet/gprs.h > @@ -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 > diff --git a/include/net/phonet/pep.h b/include/net/phonet/pep.h > index b06852b..a202adc 100644 > --- a/include/net/phonet/pep.h > +++ b/include/net/phonet/pep.h > @@ -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; > > diff --git a/net/phonet/Makefile b/net/phonet/Makefile > index 505df2a..d62bbba 100644 > --- a/net/phonet/Makefile > +++ b/net/phonet/Makefile > @@ -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 > index 0000000..3341346 > --- /dev/null > +++ b/net/phonet/pep-gprs.c > @@ -0,0 +1,341 @@ > +/* > + * 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; > +}; > + > +/* > + * 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; > + > + if (!pskb_may_pull(skb, 1)) > + goto drop; > + > + /* Look at IP version field */ > + switch (skb->data[0] >> 4) { > + case 4: > + protocol = htons(ETH_P_IP); > + break; > + case 6: > + protocol = htons(ETH_P_IPV6); > + break; > + default: > + 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; Perhaps introduce gprs_type_trans() and do: skb->protocol = gprs_type_trans() ? Look at: [acme@doppio linux-2.6]$ grep _type_trans tags | cut -f 1 | sort -u ax25_type_trans cisco_type_trans dvb_net_eth_type_trans ether1394_type_trans eth_type_trans farsync_type_trans fddi_type_trans hdlc_type_trans hippi_type_trans isdn_net_type_trans lan_type_trans mpt_lan_type_trans myri_type_trans plip_type_trans ppp_type_trans raw_type_trans tr_type_trans x25_type_trans [acme@doppio linux-2.6]$ > + > + 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) { > + if (net_ratelimit()) > + printk(KERN_WARNING"%s: TX error (%d)\n", > + dev->net->name, err); LIMIT_NETDEBUG() > + 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); > +} > diff --git a/net/phonet/pep.c b/net/phonet/pep.c > index 9a2ed45..f0c212f 100644 > --- a/net/phonet/pep.c > +++ b/net/phonet/pep.c > @@ -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 > @@ -610,6 +611,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); > > @@ -623,7 +625,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) > @@ -728,12 +735,105 @@ 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; > + > + ph = (struct pnpipehdr *)skb_push(skb, 3); skb_reset_transport_header + pnp_hdr()? > + 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; > @@ -799,14 +899,7 @@ disabled: > if (err < 0) > goto out; > > - ph = (struct pnpipehdr *)skb_push(skb, 3); > - 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; > @@ -817,6 +910,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) > @@ -899,6 +1036,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, > diff --git a/net/phonet/socket.c b/net/phonet/socket.c > index a9c3d1f..d817401 100644 > --- a/net/phonet/socket.c > +++ b/net/phonet/socket.c > @@ -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, > -- > 1.5.4.3 > > -- > To unsubscribe from this list: send the line "unsubscribe netdev" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/include/linux/phonet.h b/include/linux/phonet.h index f921852..c9609f9 100644 --- a/include/linux/phonet.h +++ b/include/linux/phonet.h @@ -31,9 +31,17 @@ #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) diff --git a/include/linux/socket.h b/include/linux/socket.h index 818ca33..20fc4bb 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -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 index 0000000..928daf5 --- /dev/null +++ b/include/net/phonet/gprs.h @@ -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 diff --git a/include/net/phonet/pep.h b/include/net/phonet/pep.h index b06852b..a202adc 100644 --- a/include/net/phonet/pep.h +++ b/include/net/phonet/pep.h @@ -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; diff --git a/net/phonet/Makefile b/net/phonet/Makefile index 505df2a..d62bbba 100644 --- a/net/phonet/Makefile +++ b/net/phonet/Makefile @@ -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 index 0000000..3341346 --- /dev/null +++ b/net/phonet/pep-gprs.c @@ -0,0 +1,341 @@ +/* + * 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; +}; + +/* + * 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; + + if (!pskb_may_pull(skb, 1)) + goto drop; + + /* Look at IP version field */ + switch (skb->data[0] >> 4) { + case 4: + protocol = htons(ETH_P_IP); + break; + case 6: + protocol = htons(ETH_P_IPV6); + break; + default: + 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) { + if (net_ratelimit()) + printk(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); +} diff --git a/net/phonet/pep.c b/net/phonet/pep.c index 9a2ed45..f0c212f 100644 --- a/net/phonet/pep.c +++ b/net/phonet/pep.c @@ -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 @@ -610,6 +611,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); @@ -623,7 +625,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) @@ -728,12 +735,105 @@ 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; + + ph = (struct pnpipehdr *)skb_push(skb, 3); + 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; @@ -799,14 +899,7 @@ disabled: if (err < 0) goto out; - ph = (struct pnpipehdr *)skb_push(skb, 3); - 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; @@ -817,6 +910,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) @@ -899,6 +1036,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, diff --git a/net/phonet/socket.c b/net/phonet/socket.c index a9c3d1f..d817401 100644 --- a/net/phonet/socket.c +++ b/net/phonet/socket.c @@ -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,