Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

16
net/phonet/Kconfig Normal file
View file

@ -0,0 +1,16 @@
#
# Phonet protocol
#
config PHONET
tristate "Phonet protocols family"
help
The Phone Network protocol (PhoNet) is a packet-oriented
communication protocol developed by Nokia for use with its modems.
This is required for Maemo to use cellular data connectivity (if
supported). It can also be used to control Nokia phones
from a Linux computer, although AT commands may be easier to use.
To compile this driver as a module, choose M here: the module
will be called phonet. If unsure, say N.

11
net/phonet/Makefile Normal file
View file

@ -0,0 +1,11 @@
obj-$(CONFIG_PHONET) += phonet.o pn_pep.o
phonet-y := \
pn_dev.o \
pn_netlink.o \
socket.o \
datagram.o \
sysctl.o \
af_phonet.o
pn_pep-y := pep.o pep-gprs.o

548
net/phonet/af_phonet.c Normal file
View file

@ -0,0 +1,548 @@
/*
* File: af_phonet.c
*
* Phonet protocols family
*
* Copyright (C) 2008 Nokia Corporation.
*
* Authors: Sakari Ailus <sakari.ailus@nokia.com>
* Rémi Denis-Courmont
*
* 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/module.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#include <net/sock.h>
#include <linux/if_phonet.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
#include <net/phonet/pn_dev.h>
/* Transport protocol registration */
static struct phonet_protocol *proto_tab[PHONET_NPROTO] __read_mostly;
static struct phonet_protocol *phonet_proto_get(unsigned int protocol)
{
struct phonet_protocol *pp;
if (protocol >= PHONET_NPROTO)
return NULL;
rcu_read_lock();
pp = rcu_dereference(proto_tab[protocol]);
if (pp && !try_module_get(pp->prot->owner))
pp = NULL;
rcu_read_unlock();
return pp;
}
static inline void phonet_proto_put(struct phonet_protocol *pp)
{
module_put(pp->prot->owner);
}
/* protocol family functions */
static int pn_socket_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
struct sock *sk;
struct pn_sock *pn;
struct phonet_protocol *pnp;
int err;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (protocol == 0) {
/* Default protocol selection */
switch (sock->type) {
case SOCK_DGRAM:
protocol = PN_PROTO_PHONET;
break;
case SOCK_SEQPACKET:
protocol = PN_PROTO_PIPE;
break;
default:
return -EPROTONOSUPPORT;
}
}
pnp = phonet_proto_get(protocol);
if (pnp == NULL &&
request_module("net-pf-%d-proto-%d", PF_PHONET, protocol) == 0)
pnp = phonet_proto_get(protocol);
if (pnp == NULL)
return -EPROTONOSUPPORT;
if (sock->type != pnp->sock_type) {
err = -EPROTONOSUPPORT;
goto out;
}
sk = sk_alloc(net, PF_PHONET, GFP_KERNEL, pnp->prot);
if (sk == NULL) {
err = -ENOMEM;
goto out;
}
sock_init_data(sock, sk);
sock->state = SS_UNCONNECTED;
sock->ops = pnp->ops;
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
sk->sk_protocol = protocol;
pn = pn_sk(sk);
pn->sobject = 0;
pn->dobject = 0;
pn->resource = 0;
sk->sk_prot->init(sk);
err = 0;
out:
phonet_proto_put(pnp);
return err;
}
static const struct net_proto_family phonet_proto_family = {
.family = PF_PHONET,
.create = pn_socket_create,
.owner = THIS_MODULE,
};
/* Phonet device header operations */
static int pn_header_create(struct sk_buff *skb, struct net_device *dev,
unsigned short type, const void *daddr,
const void *saddr, unsigned int len)
{
u8 *media = skb_push(skb, 1);
if (type != ETH_P_PHONET)
return -1;
if (!saddr)
saddr = dev->dev_addr;
*media = *(const u8 *)saddr;
return 1;
}
static int pn_header_parse(const struct sk_buff *skb, unsigned char *haddr)
{
const u8 *media = skb_mac_header(skb);
*haddr = *media;
return 1;
}
struct header_ops phonet_header_ops = {
.create = pn_header_create,
.parse = pn_header_parse,
};
EXPORT_SYMBOL(phonet_header_ops);
/*
* Prepends an ISI header and sends a datagram.
*/
static int pn_send(struct sk_buff *skb, struct net_device *dev,
u16 dst, u16 src, u8 res, u8 irq)
{
struct phonethdr *ph;
int err;
if (skb->len + 2 > 0xffff /* Phonet length field limit */ ||
skb->len + sizeof(struct phonethdr) > dev->mtu) {
err = -EMSGSIZE;
goto drop;
}
/* Broadcast sending is not implemented */
if (pn_addr(dst) == PNADDR_BROADCAST) {
err = -EOPNOTSUPP;
goto drop;
}
skb_reset_transport_header(skb);
WARN_ON(skb_headroom(skb) & 1); /* HW assumes word alignment */
skb_push(skb, sizeof(struct phonethdr));
skb_reset_network_header(skb);
ph = pn_hdr(skb);
ph->pn_rdev = pn_dev(dst);
ph->pn_sdev = pn_dev(src);
ph->pn_res = res;
ph->pn_length = __cpu_to_be16(skb->len + 2 - sizeof(*ph));
ph->pn_robj = pn_obj(dst);
ph->pn_sobj = pn_obj(src);
skb->protocol = htons(ETH_P_PHONET);
skb->priority = 0;
skb->dev = dev;
if (skb->pkt_type == PACKET_LOOPBACK) {
skb_reset_mac_header(skb);
skb_orphan(skb);
err = (irq ? netif_rx(skb) : netif_rx_ni(skb)) ? -ENOBUFS : 0;
} else {
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
NULL, NULL, skb->len);
if (err < 0) {
err = -EHOSTUNREACH;
goto drop;
}
err = dev_queue_xmit(skb);
if (unlikely(err > 0))
err = net_xmit_errno(err);
}
return err;
drop:
kfree_skb(skb);
return err;
}
static int pn_raw_send(const void *data, int len, struct net_device *dev,
u16 dst, u16 src, u8 res)
{
struct sk_buff *skb = alloc_skb(MAX_PHONET_HEADER + len, GFP_ATOMIC);
if (skb == NULL)
return -ENOMEM;
if (phonet_address_lookup(dev_net(dev), pn_addr(dst)) == 0)
skb->pkt_type = PACKET_LOOPBACK;
skb_reserve(skb, MAX_PHONET_HEADER);
__skb_put(skb, len);
skb_copy_to_linear_data(skb, data, len);
return pn_send(skb, dev, dst, src, res, 1);
}
/*
* Create a Phonet header for the skb and send it out. Returns
* non-zero error code if failed. The skb is freed then.
*/
int pn_skb_send(struct sock *sk, struct sk_buff *skb,
const struct sockaddr_pn *target)
{
struct net *net = sock_net(sk);
struct net_device *dev;
struct pn_sock *pn = pn_sk(sk);
int err;
u16 src, dst;
u8 daddr, saddr, res;
src = pn->sobject;
if (target != NULL) {
dst = pn_sockaddr_get_object(target);
res = pn_sockaddr_get_resource(target);
} else {
dst = pn->dobject;
res = pn->resource;
}
daddr = pn_addr(dst);
err = -EHOSTUNREACH;
if (sk->sk_bound_dev_if)
dev = dev_get_by_index(net, sk->sk_bound_dev_if);
else if (phonet_address_lookup(net, daddr) == 0) {
dev = phonet_device_get(net);
skb->pkt_type = PACKET_LOOPBACK;
} else if (dst == 0) {
/* Resource routing (small race until phonet_rcv()) */
struct sock *sk = pn_find_sock_by_res(net, res);
if (sk) {
sock_put(sk);
dev = phonet_device_get(net);
skb->pkt_type = PACKET_LOOPBACK;
} else
dev = phonet_route_output(net, daddr);
} else
dev = phonet_route_output(net, daddr);
if (!dev || !(dev->flags & IFF_UP))
goto drop;
saddr = phonet_address_get(dev, daddr);
if (saddr == PN_NO_ADDR)
goto drop;
if (!pn_addr(src))
src = pn_object(saddr, pn_obj(src));
err = pn_send(skb, dev, dst, src, res, 0);
dev_put(dev);
return err;
drop:
kfree_skb(skb);
if (dev)
dev_put(dev);
return err;
}
EXPORT_SYMBOL(pn_skb_send);
/* Do not send an error message in response to an error message */
static inline int can_respond(struct sk_buff *skb)
{
const struct phonethdr *ph;
const struct phonetmsg *pm;
u8 submsg_id;
if (!pskb_may_pull(skb, 3))
return 0;
ph = pn_hdr(skb);
if (ph->pn_res == PN_PREFIX && !pskb_may_pull(skb, 5))
return 0;
if (ph->pn_res == PN_COMMGR) /* indications */
return 0;
ph = pn_hdr(skb); /* re-acquires the pointer */
pm = pn_msg(skb);
if (pm->pn_msg_id != PN_COMMON_MESSAGE)
return 1;
submsg_id = (ph->pn_res == PN_PREFIX)
? pm->pn_e_submsg_id : pm->pn_submsg_id;
if (submsg_id != PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP &&
pm->pn_e_submsg_id != PN_COMM_SERVICE_NOT_IDENTIFIED_RESP)
return 1;
return 0;
}
static int send_obj_unreachable(struct sk_buff *rskb)
{
const struct phonethdr *oph = pn_hdr(rskb);
const struct phonetmsg *opm = pn_msg(rskb);
struct phonetmsg resp;
memset(&resp, 0, sizeof(resp));
resp.pn_trans_id = opm->pn_trans_id;
resp.pn_msg_id = PN_COMMON_MESSAGE;
if (oph->pn_res == PN_PREFIX) {
resp.pn_e_res_id = opm->pn_e_res_id;
resp.pn_e_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP;
resp.pn_e_orig_msg_id = opm->pn_msg_id;
resp.pn_e_status = 0;
} else {
resp.pn_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP;
resp.pn_orig_msg_id = opm->pn_msg_id;
resp.pn_status = 0;
}
return pn_raw_send(&resp, sizeof(resp), rskb->dev,
pn_object(oph->pn_sdev, oph->pn_sobj),
pn_object(oph->pn_rdev, oph->pn_robj),
oph->pn_res);
}
static int send_reset_indications(struct sk_buff *rskb)
{
struct phonethdr *oph = pn_hdr(rskb);
static const u8 data[4] = {
0x00 /* trans ID */, 0x10 /* subscribe msg */,
0x00 /* subscription count */, 0x00 /* dummy */
};
return pn_raw_send(data, sizeof(data), rskb->dev,
pn_object(oph->pn_sdev, 0x00),
pn_object(oph->pn_rdev, oph->pn_robj),
PN_COMMGR);
}
/* packet type functions */
/*
* Stuff received packets to associated sockets.
* On error, returns non-zero and releases the skb.
*/
static int phonet_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pkttype,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);
struct phonethdr *ph;
struct sockaddr_pn sa;
u16 len;
/* check we have at least a full Phonet header */
if (!pskb_pull(skb, sizeof(struct phonethdr)))
goto out;
/* check that the advertised length is correct */
ph = pn_hdr(skb);
len = get_unaligned_be16(&ph->pn_length);
if (len < 2)
goto out;
len -= 2;
if ((len > skb->len) || pskb_trim(skb, len))
goto out;
skb_reset_transport_header(skb);
pn_skb_get_dst_sockaddr(skb, &sa);
/* check if this is broadcasted */
if (pn_sockaddr_get_addr(&sa) == PNADDR_BROADCAST) {
pn_deliver_sock_broadcast(net, skb);
goto out;
}
/* resource routing */
if (pn_sockaddr_get_object(&sa) == 0) {
struct sock *sk = pn_find_sock_by_res(net, sa.spn_resource);
if (sk)
return sk_receive_skb(sk, skb, 0);
}
/* check if we are the destination */
if (phonet_address_lookup(net, pn_sockaddr_get_addr(&sa)) == 0) {
/* Phonet packet input */
struct sock *sk = pn_find_sock_by_sa(net, &sa);
if (sk)
return sk_receive_skb(sk, skb, 0);
if (can_respond(skb)) {
send_obj_unreachable(skb);
send_reset_indications(skb);
}
} else if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
goto out; /* Race between address deletion and loopback */
else {
/* Phonet packet routing */
struct net_device *out_dev;
out_dev = phonet_route_output(net, pn_sockaddr_get_addr(&sa));
if (!out_dev) {
LIMIT_NETDEBUG(KERN_WARNING"No Phonet route to %02X\n",
pn_sockaddr_get_addr(&sa));
goto out;
}
__skb_push(skb, sizeof(struct phonethdr));
skb->dev = out_dev;
if (out_dev == dev) {
LIMIT_NETDEBUG(KERN_ERR"Phonet loop to %02X on %s\n",
pn_sockaddr_get_addr(&sa), dev->name);
goto out_dev;
}
/* Some drivers (e.g. TUN) do not allocate HW header space */
if (skb_cow_head(skb, out_dev->hard_header_len))
goto out_dev;
if (dev_hard_header(skb, out_dev, ETH_P_PHONET, NULL, NULL,
skb->len) < 0)
goto out_dev;
dev_queue_xmit(skb);
dev_put(out_dev);
return NET_RX_SUCCESS;
out_dev:
dev_put(out_dev);
}
out:
kfree_skb(skb);
return NET_RX_DROP;
}
static struct packet_type phonet_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_PHONET),
.func = phonet_rcv,
};
static DEFINE_MUTEX(proto_tab_lock);
int __init_or_module phonet_proto_register(unsigned int protocol,
struct phonet_protocol *pp)
{
int err = 0;
if (protocol >= PHONET_NPROTO)
return -EINVAL;
err = proto_register(pp->prot, 1);
if (err)
return err;
mutex_lock(&proto_tab_lock);
if (proto_tab[protocol])
err = -EBUSY;
else
rcu_assign_pointer(proto_tab[protocol], pp);
mutex_unlock(&proto_tab_lock);
return err;
}
EXPORT_SYMBOL(phonet_proto_register);
void phonet_proto_unregister(unsigned int protocol, struct phonet_protocol *pp)
{
mutex_lock(&proto_tab_lock);
BUG_ON(proto_tab[protocol] != pp);
RCU_INIT_POINTER(proto_tab[protocol], NULL);
mutex_unlock(&proto_tab_lock);
synchronize_rcu();
proto_unregister(pp->prot);
}
EXPORT_SYMBOL(phonet_proto_unregister);
/* Module registration */
static int __init phonet_init(void)
{
int err;
err = phonet_device_init();
if (err)
return err;
pn_sock_init();
err = sock_register(&phonet_proto_family);
if (err) {
printk(KERN_ALERT
"phonet protocol family initialization failed\n");
goto err_sock;
}
dev_add_pack(&phonet_packet_type);
phonet_sysctl_init();
err = isi_register();
if (err)
goto err;
return 0;
err:
phonet_sysctl_exit();
sock_unregister(PF_PHONET);
dev_remove_pack(&phonet_packet_type);
err_sock:
phonet_device_exit();
return err;
}
static void __exit phonet_exit(void)
{
isi_unregister();
phonet_sysctl_exit();
sock_unregister(PF_PHONET);
dev_remove_pack(&phonet_packet_type);
phonet_device_exit();
}
module_init(phonet_init);
module_exit(phonet_exit);
MODULE_DESCRIPTION("Phonet protocol stack for Linux");
MODULE_LICENSE("GPL");
MODULE_ALIAS_NETPROTO(PF_PHONET);

214
net/phonet/datagram.c Normal file
View file

@ -0,0 +1,214 @@
/*
* File: datagram.c
*
* Datagram (ISI) Phonet sockets
*
* Copyright (C) 2008 Nokia Corporation.
*
* Authors: Sakari Ailus <sakari.ailus@nokia.com>
* Rémi Denis-Courmont
*
* 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/slab.h>
#include <linux/socket.h>
#include <asm/ioctls.h>
#include <net/sock.h>
#include <linux/phonet.h>
#include <linux/export.h>
#include <net/phonet/phonet.h>
static int pn_backlog_rcv(struct sock *sk, struct sk_buff *skb);
/* associated socket ceases to exist */
static void pn_sock_close(struct sock *sk, long timeout)
{
sk_common_release(sk);
}
static int pn_ioctl(struct sock *sk, int cmd, unsigned long arg)
{
struct sk_buff *skb;
int answ;
switch (cmd) {
case SIOCINQ:
lock_sock(sk);
skb = skb_peek(&sk->sk_receive_queue);
answ = skb ? skb->len : 0;
release_sock(sk);
return put_user(answ, (int __user *)arg);
case SIOCPNADDRESOURCE:
case SIOCPNDELRESOURCE: {
u32 res;
if (get_user(res, (u32 __user *)arg))
return -EFAULT;
if (res >= 256)
return -EINVAL;
if (cmd == SIOCPNADDRESOURCE)
return pn_sock_bind_res(sk, res);
else
return pn_sock_unbind_res(sk, res);
}
}
return -ENOIOCTLCMD;
}
/* Destroy socket. All references are gone. */
static void pn_destruct(struct sock *sk)
{
skb_queue_purge(&sk->sk_receive_queue);
}
static int pn_init(struct sock *sk)
{
sk->sk_destruct = pn_destruct;
return 0;
}
static int pn_sendmsg(struct kiocb *iocb, struct sock *sk,
struct msghdr *msg, size_t len)
{
DECLARE_SOCKADDR(struct sockaddr_pn *, target, msg->msg_name);
struct sk_buff *skb;
int err;
if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR|MSG_NOSIGNAL|
MSG_CMSG_COMPAT))
return -EOPNOTSUPP;
if (target == NULL)
return -EDESTADDRREQ;
if (msg->msg_namelen < sizeof(struct sockaddr_pn))
return -EINVAL;
if (target->spn_family != AF_PHONET)
return -EAFNOSUPPORT;
skb = sock_alloc_send_skb(sk, MAX_PHONET_HEADER + len,
msg->msg_flags & MSG_DONTWAIT, &err);
if (skb == NULL)
return err;
skb_reserve(skb, MAX_PHONET_HEADER);
err = memcpy_fromiovec((void *)skb_put(skb, len), msg->msg_iov, len);
if (err < 0) {
kfree_skb(skb);
return err;
}
/*
* Fill in the Phonet header and
* finally pass the packet forwards.
*/
err = pn_skb_send(sk, skb, target);
/* If ok, return len. */
return (err >= 0) ? len : err;
}
static int pn_recvmsg(struct kiocb *iocb, struct sock *sk,
struct msghdr *msg, size_t len, int noblock,
int flags, int *addr_len)
{
struct sk_buff *skb = NULL;
struct sockaddr_pn sa;
int rval = -EOPNOTSUPP;
int copylen;
if (flags & ~(MSG_PEEK|MSG_TRUNC|MSG_DONTWAIT|MSG_NOSIGNAL|
MSG_CMSG_COMPAT))
goto out_nofree;
skb = skb_recv_datagram(sk, flags, noblock, &rval);
if (skb == NULL)
goto out_nofree;
pn_skb_get_src_sockaddr(skb, &sa);
copylen = skb->len;
if (len < copylen) {
msg->msg_flags |= MSG_TRUNC;
copylen = len;
}
rval = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copylen);
if (rval) {
rval = -EFAULT;
goto out;
}
rval = (flags & MSG_TRUNC) ? skb->len : copylen;
if (msg->msg_name != NULL) {
__sockaddr_check_size(sizeof(sa));
memcpy(msg->msg_name, &sa, sizeof(sa));
*addr_len = sizeof(sa);
}
out:
skb_free_datagram(sk, skb);
out_nofree:
return rval;
}
/* Queue an skb for a sock. */
static int pn_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
int err = sock_queue_rcv_skb(sk, skb);
if (err < 0)
kfree_skb(skb);
return err ? NET_RX_DROP : NET_RX_SUCCESS;
}
/* Module registration */
static struct proto pn_proto = {
.close = pn_sock_close,
.ioctl = pn_ioctl,
.init = pn_init,
.sendmsg = pn_sendmsg,
.recvmsg = pn_recvmsg,
.backlog_rcv = pn_backlog_rcv,
.hash = pn_sock_hash,
.unhash = pn_sock_unhash,
.get_port = pn_sock_get_port,
.obj_size = sizeof(struct pn_sock),
.owner = THIS_MODULE,
.name = "PHONET",
};
static struct phonet_protocol pn_dgram_proto = {
.ops = &phonet_dgram_ops,
.prot = &pn_proto,
.sock_type = SOCK_DGRAM,
};
int __init isi_register(void)
{
return phonet_proto_register(PN_PROTO_PHONET, &pn_dgram_proto);
}
void __exit isi_unregister(void)
{
phonet_proto_unregister(PN_PROTO_PHONET, &pn_dgram_proto);
}

328
net/phonet/pep-gprs.c Normal file
View file

@ -0,0 +1,328 @@
/*
* File: pep-gprs.c
*
* GPRS over Phonet pipe end point socket
*
* Copyright (C) 2008 Nokia Corporation.
*
* Author: Rémi Denis-Courmont
*
* 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 *);
void (*old_write_space)(struct sock *);
struct net_device *dev;
};
static __be16 gprs_type_trans(struct sk_buff *skb)
{
const u8 *pvfc;
u8 buf;
pvfc = skb_header_pointer(skb, 0, 1, &buf);
if (!pvfc)
return htons(0);
/* Look at IP version field */
switch (*pvfc >> 4) {
case 4:
return htons(ETH_P_IP);
case 6:
return htons(ETH_P_IPV6);
}
return htons(0);
}
static void gprs_writeable(struct gprs_dev *gp)
{
struct net_device *dev = gp->dev;
if (pep_writeable(gp->sk))
netif_wake_queue(dev);
}
/*
* Socket callbacks
*/
static void gprs_state_change(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
if (sk->sk_state == TCP_CLOSE_WAIT) {
struct net_device *dev = gp->dev;
netif_stop_queue(dev);
netif_carrier_off(dev);
}
}
static int gprs_recv(struct gprs_dev *gp, struct sk_buff *skb)
{
struct net_device *dev = gp->dev;
int err = 0;
__be16 protocol = gprs_type_trans(skb);
if (!protocol) {
err = -EINVAL;
goto drop;
}
if (skb_headroom(skb) & 3) {
struct sk_buff *rskb, *fs;
int flen = 0;
/* Phonet Pipe data header may be 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, 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 */
skb_walk_frags(skb, fs)
flen += fs->len;
skb->next = skb_shinfo(skb)->frag_list;
skb_frag_list_init(skb);
skb->len -= flen;
skb->data_len -= flen;
skb->truesize -= flen;
skb = rskb;
}
skb->protocol = protocol;
skb_reset_mac_header(skb);
skb->dev = dev;
if (likely(dev->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)
{
struct gprs_dev *gp = sk->sk_user_data;
struct sk_buff *skb;
while ((skb = pep_read(sk)) != NULL) {
skb_orphan(skb);
gprs_recv(gp, skb);
}
}
static void gprs_write_space(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
if (netif_running(gp->dev))
gprs_writeable(gp);
}
/*
* Network device callbacks
*/
static int gprs_open(struct net_device *dev)
{
struct gprs_dev *gp = netdev_priv(dev);
gprs_writeable(gp);
return 0;
}
static int gprs_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static netdev_tx_t gprs_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct gprs_dev *gp = netdev_priv(dev);
struct sock *sk = gp->sk;
int len, err;
switch (skb->protocol) {
case htons(ETH_P_IP):
case htons(ETH_P_IPV6):
break;
default:
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
skb_orphan(skb);
skb_set_owner_w(skb, sk);
len = skb->len;
err = pep_write(sk, skb);
if (err) {
LIMIT_NETDEBUG(KERN_WARNING"%s: TX error (%d)\n",
dev->name, err);
dev->stats.tx_aborted_errors++;
dev->stats.tx_errors++;
} else {
dev->stats.tx_packets++;
dev->stats.tx_bytes += len;
}
netif_stop_queue(dev);
if (pep_writeable(sk))
netif_wake_queue(dev);
return NETDEV_TX_OK;
}
static int gprs_set_mtu(struct net_device *dev, int new_mtu)
{
if ((new_mtu < 576) || (new_mtu > (PHONET_MAX_MTU - 11)))
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
static const struct net_device_ops gprs_netdev_ops = {
.ndo_open = gprs_open,
.ndo_stop = gprs_close,
.ndo_start_xmit = gprs_xmit,
.ndo_change_mtu = gprs_set_mtu,
};
static void gprs_setup(struct net_device *dev)
{
dev->features = NETIF_F_FRAGLIST;
dev->type = ARPHRD_PHONET_PIPE;
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = GPRS_DEFAULT_MTU;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->tx_queue_len = 10;
dev->netdev_ops = &gprs_netdev_ops;
dev->destructor = free_netdev;
}
/*
* 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 *gp;
struct net_device *dev;
int err;
if (unlikely(sk->sk_type == SOCK_STREAM))
return -EINVAL; /* need packet boundaries */
/* Create net device */
dev = alloc_netdev(sizeof(*gp), ifname, NET_NAME_UNKNOWN, gprs_setup);
if (!dev)
return -ENOMEM;
gp = netdev_priv(dev);
gp->sk = sk;
gp->dev = dev;
netif_stop_queue(dev);
err = register_netdev(dev);
if (err) {
free_netdev(dev);
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 = gp;
gp->old_state_change = sk->sk_state_change;
gp->old_data_ready = sk->sk_data_ready;
gp->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);
printk(KERN_DEBUG"%s: attached\n", dev->name);
return dev->ifindex;
out_rel:
release_sock(sk);
unregister_netdev(dev);
return err;
}
void gprs_detach(struct sock *sk)
{
struct gprs_dev *gp = sk->sk_user_data;
struct net_device *dev = gp->dev;
lock_sock(sk);
sk->sk_user_data = NULL;
sk->sk_state_change = gp->old_state_change;
sk->sk_data_ready = gp->old_data_ready;
sk->sk_write_space = gp->old_write_space;
release_sock(sk);
printk(KERN_DEBUG"%s: detached\n", dev->name);
unregister_netdev(dev);
sock_put(sk);
}

1375
net/phonet/pep.c Normal file

File diff suppressed because it is too large Load diff

431
net/phonet/pn_dev.c Normal file
View file

@ -0,0 +1,431 @@
/*
* File: pn_dev.c
*
* Phonet network device
*
* Copyright (C) 2008 Nokia Corporation.
*
* Authors: Sakari Ailus <sakari.ailus@nokia.com>
* Rémi Denis-Courmont
*
* 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/net.h>
#include <linux/slab.h>
#include <linux/netdevice.h>
#include <linux/phonet.h>
#include <linux/proc_fs.h>
#include <linux/if_arp.h>
#include <net/sock.h>
#include <net/netns/generic.h>
#include <net/phonet/pn_dev.h>
struct phonet_routes {
struct mutex lock;
struct net_device __rcu *table[64];
};
struct phonet_net {
struct phonet_device_list pndevs;
struct phonet_routes routes;
};
static int phonet_net_id __read_mostly;
static struct phonet_net *phonet_pernet(struct net *net)
{
BUG_ON(!net);
return net_generic(net, phonet_net_id);
}
struct phonet_device_list *phonet_device_list(struct net *net)
{
struct phonet_net *pnn = phonet_pernet(net);
return &pnn->pndevs;
}
/* Allocate new Phonet device. */
static struct phonet_device *__phonet_device_alloc(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd = kmalloc(sizeof(*pnd), GFP_ATOMIC);
if (pnd == NULL)
return NULL;
pnd->netdev = dev;
bitmap_zero(pnd->addrs, 64);
BUG_ON(!mutex_is_locked(&pndevs->lock));
list_add_rcu(&pnd->list, &pndevs->list);
return pnd;
}
static struct phonet_device *__phonet_get(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
BUG_ON(!mutex_is_locked(&pndevs->lock));
list_for_each_entry(pnd, &pndevs->list, list) {
if (pnd->netdev == dev)
return pnd;
}
return NULL;
}
static struct phonet_device *__phonet_get_rcu(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
list_for_each_entry_rcu(pnd, &pndevs->list, list) {
if (pnd->netdev == dev)
return pnd;
}
return NULL;
}
static void phonet_device_destroy(struct net_device *dev)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
ASSERT_RTNL();
mutex_lock(&pndevs->lock);
pnd = __phonet_get(dev);
if (pnd)
list_del_rcu(&pnd->list);
mutex_unlock(&pndevs->lock);
if (pnd) {
u8 addr;
for_each_set_bit(addr, pnd->addrs, 64)
phonet_address_notify(RTM_DELADDR, dev, addr);
kfree(pnd);
}
}
struct net_device *phonet_device_get(struct net *net)
{
struct phonet_device_list *pndevs = phonet_device_list(net);
struct phonet_device *pnd;
struct net_device *dev = NULL;
rcu_read_lock();
list_for_each_entry_rcu(pnd, &pndevs->list, list) {
dev = pnd->netdev;
BUG_ON(!dev);
if ((dev->reg_state == NETREG_REGISTERED) &&
((pnd->netdev->flags & IFF_UP)) == IFF_UP)
break;
dev = NULL;
}
if (dev)
dev_hold(dev);
rcu_read_unlock();
return dev;
}
int phonet_address_add(struct net_device *dev, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
int err = 0;
mutex_lock(&pndevs->lock);
/* Find or create Phonet-specific device data */
pnd = __phonet_get(dev);
if (pnd == NULL)
pnd = __phonet_device_alloc(dev);
if (unlikely(pnd == NULL))
err = -ENOMEM;
else if (test_and_set_bit(addr >> 2, pnd->addrs))
err = -EEXIST;
mutex_unlock(&pndevs->lock);
return err;
}
int phonet_address_del(struct net_device *dev, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(dev_net(dev));
struct phonet_device *pnd;
int err = 0;
mutex_lock(&pndevs->lock);
pnd = __phonet_get(dev);
if (!pnd || !test_and_clear_bit(addr >> 2, pnd->addrs)) {
err = -EADDRNOTAVAIL;
pnd = NULL;
} else if (bitmap_empty(pnd->addrs, 64))
list_del_rcu(&pnd->list);
else
pnd = NULL;
mutex_unlock(&pndevs->lock);
if (pnd)
kfree_rcu(pnd, rcu);
return err;
}
/* Gets a source address toward a destination, through a interface. */
u8 phonet_address_get(struct net_device *dev, u8 daddr)
{
struct phonet_device *pnd;
u8 saddr;
rcu_read_lock();
pnd = __phonet_get_rcu(dev);
if (pnd) {
BUG_ON(bitmap_empty(pnd->addrs, 64));
/* Use same source address as destination, if possible */
if (test_bit(daddr >> 2, pnd->addrs))
saddr = daddr;
else
saddr = find_first_bit(pnd->addrs, 64) << 2;
} else
saddr = PN_NO_ADDR;
rcu_read_unlock();
if (saddr == PN_NO_ADDR) {
/* Fallback to another device */
struct net_device *def_dev;
def_dev = phonet_device_get(dev_net(dev));
if (def_dev) {
if (def_dev != dev)
saddr = phonet_address_get(def_dev, daddr);
dev_put(def_dev);
}
}
return saddr;
}
int phonet_address_lookup(struct net *net, u8 addr)
{
struct phonet_device_list *pndevs = phonet_device_list(net);
struct phonet_device *pnd;
int err = -EADDRNOTAVAIL;
rcu_read_lock();
list_for_each_entry_rcu(pnd, &pndevs->list, list) {
/* Don't allow unregistering devices! */
if ((pnd->netdev->reg_state != NETREG_REGISTERED) ||
((pnd->netdev->flags & IFF_UP)) != IFF_UP)
continue;
if (test_bit(addr >> 2, pnd->addrs)) {
err = 0;
goto found;
}
}
found:
rcu_read_unlock();
return err;
}
/* automatically configure a Phonet device, if supported */
static int phonet_device_autoconf(struct net_device *dev)
{
struct if_phonet_req req;
int ret;
if (!dev->netdev_ops->ndo_do_ioctl)
return -EOPNOTSUPP;
ret = dev->netdev_ops->ndo_do_ioctl(dev, (struct ifreq *)&req,
SIOCPNGAUTOCONF);
if (ret < 0)
return ret;
ASSERT_RTNL();
ret = phonet_address_add(dev, req.ifr_phonet_autoconf.device);
if (ret)
return ret;
phonet_address_notify(RTM_NEWADDR, dev,
req.ifr_phonet_autoconf.device);
return 0;
}
static void phonet_route_autodel(struct net_device *dev)
{
struct phonet_net *pnn = phonet_pernet(dev_net(dev));
unsigned int i;
DECLARE_BITMAP(deleted, 64);
/* Remove left-over Phonet routes */
bitmap_zero(deleted, 64);
mutex_lock(&pnn->routes.lock);
for (i = 0; i < 64; i++)
if (rcu_access_pointer(pnn->routes.table[i]) == dev) {
RCU_INIT_POINTER(pnn->routes.table[i], NULL);
set_bit(i, deleted);
}
mutex_unlock(&pnn->routes.lock);
if (bitmap_empty(deleted, 64))
return; /* short-circuit RCU */
synchronize_rcu();
for_each_set_bit(i, deleted, 64) {
rtm_phonet_notify(RTM_DELROUTE, dev, i);
dev_put(dev);
}
}
/* notify Phonet of device events */
static int phonet_device_notify(struct notifier_block *me, unsigned long what,
void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
switch (what) {
case NETDEV_REGISTER:
if (dev->type == ARPHRD_PHONET)
phonet_device_autoconf(dev);
break;
case NETDEV_UNREGISTER:
phonet_device_destroy(dev);
phonet_route_autodel(dev);
break;
}
return 0;
}
static struct notifier_block phonet_device_notifier = {
.notifier_call = phonet_device_notify,
.priority = 0,
};
/* Per-namespace Phonet devices handling */
static int __net_init phonet_init_net(struct net *net)
{
struct phonet_net *pnn = phonet_pernet(net);
if (!proc_create("phonet", 0, net->proc_net, &pn_sock_seq_fops))
return -ENOMEM;
INIT_LIST_HEAD(&pnn->pndevs.list);
mutex_init(&pnn->pndevs.lock);
mutex_init(&pnn->routes.lock);
return 0;
}
static void __net_exit phonet_exit_net(struct net *net)
{
remove_proc_entry("phonet", net->proc_net);
}
static struct pernet_operations phonet_net_ops = {
.init = phonet_init_net,
.exit = phonet_exit_net,
.id = &phonet_net_id,
.size = sizeof(struct phonet_net),
};
/* Initialize Phonet devices list */
int __init phonet_device_init(void)
{
int err = register_pernet_subsys(&phonet_net_ops);
if (err)
return err;
proc_create("pnresource", 0, init_net.proc_net, &pn_res_seq_fops);
register_netdevice_notifier(&phonet_device_notifier);
err = phonet_netlink_register();
if (err)
phonet_device_exit();
return err;
}
void phonet_device_exit(void)
{
rtnl_unregister_all(PF_PHONET);
unregister_netdevice_notifier(&phonet_device_notifier);
unregister_pernet_subsys(&phonet_net_ops);
remove_proc_entry("pnresource", init_net.proc_net);
}
int phonet_route_add(struct net_device *dev, u8 daddr)
{
struct phonet_net *pnn = phonet_pernet(dev_net(dev));
struct phonet_routes *routes = &pnn->routes;
int err = -EEXIST;
daddr = daddr >> 2;
mutex_lock(&routes->lock);
if (routes->table[daddr] == NULL) {
rcu_assign_pointer(routes->table[daddr], dev);
dev_hold(dev);
err = 0;
}
mutex_unlock(&routes->lock);
return err;
}
int phonet_route_del(struct net_device *dev, u8 daddr)
{
struct phonet_net *pnn = phonet_pernet(dev_net(dev));
struct phonet_routes *routes = &pnn->routes;
daddr = daddr >> 2;
mutex_lock(&routes->lock);
if (rcu_access_pointer(routes->table[daddr]) == dev)
RCU_INIT_POINTER(routes->table[daddr], NULL);
else
dev = NULL;
mutex_unlock(&routes->lock);
if (!dev)
return -ENOENT;
synchronize_rcu();
dev_put(dev);
return 0;
}
struct net_device *phonet_route_get_rcu(struct net *net, u8 daddr)
{
struct phonet_net *pnn = phonet_pernet(net);
struct phonet_routes *routes = &pnn->routes;
struct net_device *dev;
daddr >>= 2;
dev = rcu_dereference(routes->table[daddr]);
return dev;
}
struct net_device *phonet_route_output(struct net *net, u8 daddr)
{
struct phonet_net *pnn = phonet_pernet(net);
struct phonet_routes *routes = &pnn->routes;
struct net_device *dev;
daddr >>= 2;
rcu_read_lock();
dev = rcu_dereference(routes->table[daddr]);
if (dev)
dev_hold(dev);
rcu_read_unlock();
if (!dev)
dev = phonet_device_get(net); /* Default route */
return dev;
}

312
net/phonet/pn_netlink.c Normal file
View file

@ -0,0 +1,312 @@
/*
* File: pn_netlink.c
*
* Phonet netlink interface
*
* Copyright (C) 2008 Nokia Corporation.
*
* Authors: Sakari Ailus <sakari.ailus@nokia.com>
* Remi Denis-Courmont
*
* 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/netlink.h>
#include <linux/phonet.h>
#include <linux/slab.h>
#include <net/sock.h>
#include <net/phonet/pn_dev.h>
/* Device address handling */
static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
u32 portid, u32 seq, int event);
void phonet_address_notify(int event, struct net_device *dev, u8 addr)
{
struct sk_buff *skb;
int err = -ENOBUFS;
skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
nla_total_size(1), GFP_KERNEL);
if (skb == NULL)
goto errout;
err = fill_addr(skb, dev, addr, 0, 0, event);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
kfree_skb(skb);
goto errout;
}
rtnl_notify(skb, dev_net(dev), 0,
RTNLGRP_PHONET_IFADDR, NULL, GFP_KERNEL);
return;
errout:
rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_IFADDR, err);
}
static const struct nla_policy ifa_phonet_policy[IFA_MAX+1] = {
[IFA_LOCAL] = { .type = NLA_U8 },
};
static int addr_doit(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct net *net = sock_net(skb->sk);
struct nlattr *tb[IFA_MAX+1];
struct net_device *dev;
struct ifaddrmsg *ifm;
int err;
u8 pnaddr;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!netlink_capable(skb, CAP_SYS_ADMIN))
return -EPERM;
ASSERT_RTNL();
err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_phonet_policy);
if (err < 0)
return err;
ifm = nlmsg_data(nlh);
if (tb[IFA_LOCAL] == NULL)
return -EINVAL;
pnaddr = nla_get_u8(tb[IFA_LOCAL]);
if (pnaddr & 3)
/* Phonet addresses only have 6 high-order bits */
return -EINVAL;
dev = __dev_get_by_index(net, ifm->ifa_index);
if (dev == NULL)
return -ENODEV;
if (nlh->nlmsg_type == RTM_NEWADDR)
err = phonet_address_add(dev, pnaddr);
else
err = phonet_address_del(dev, pnaddr);
if (!err)
phonet_address_notify(nlh->nlmsg_type, dev, pnaddr);
return err;
}
static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
u32 portid, u32 seq, int event)
{
struct ifaddrmsg *ifm;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*ifm), 0);
if (nlh == NULL)
return -EMSGSIZE;
ifm = nlmsg_data(nlh);
ifm->ifa_family = AF_PHONET;
ifm->ifa_prefixlen = 0;
ifm->ifa_flags = IFA_F_PERMANENT;
ifm->ifa_scope = RT_SCOPE_LINK;
ifm->ifa_index = dev->ifindex;
if (nla_put_u8(skb, IFA_LOCAL, addr))
goto nla_put_failure;
return nlmsg_end(skb, nlh);
nla_put_failure:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
static int getaddr_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct phonet_device_list *pndevs;
struct phonet_device *pnd;
int dev_idx = 0, dev_start_idx = cb->args[0];
int addr_idx = 0, addr_start_idx = cb->args[1];
pndevs = phonet_device_list(sock_net(skb->sk));
rcu_read_lock();
list_for_each_entry_rcu(pnd, &pndevs->list, list) {
u8 addr;
if (dev_idx > dev_start_idx)
addr_start_idx = 0;
if (dev_idx++ < dev_start_idx)
continue;
addr_idx = 0;
for_each_set_bit(addr, pnd->addrs, 64) {
if (addr_idx++ < addr_start_idx)
continue;
if (fill_addr(skb, pnd->netdev, addr << 2,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, RTM_NEWADDR) < 0)
goto out;
}
}
out:
rcu_read_unlock();
cb->args[0] = dev_idx;
cb->args[1] = addr_idx;
return skb->len;
}
/* Routes handling */
static int fill_route(struct sk_buff *skb, struct net_device *dev, u8 dst,
u32 portid, u32 seq, int event)
{
struct rtmsg *rtm;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), 0);
if (nlh == NULL)
return -EMSGSIZE;
rtm = nlmsg_data(nlh);
rtm->rtm_family = AF_PHONET;
rtm->rtm_dst_len = 6;
rtm->rtm_src_len = 0;
rtm->rtm_tos = 0;
rtm->rtm_table = RT_TABLE_MAIN;
rtm->rtm_protocol = RTPROT_STATIC;
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
rtm->rtm_type = RTN_UNICAST;
rtm->rtm_flags = 0;
if (nla_put_u8(skb, RTA_DST, dst) ||
nla_put_u32(skb, RTA_OIF, dev->ifindex))
goto nla_put_failure;
return nlmsg_end(skb, nlh);
nla_put_failure:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
void rtm_phonet_notify(int event, struct net_device *dev, u8 dst)
{
struct sk_buff *skb;
int err = -ENOBUFS;
skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
nla_total_size(1) + nla_total_size(4), GFP_KERNEL);
if (skb == NULL)
goto errout;
err = fill_route(skb, dev, dst, 0, 0, event);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
kfree_skb(skb);
goto errout;
}
rtnl_notify(skb, dev_net(dev), 0,
RTNLGRP_PHONET_ROUTE, NULL, GFP_KERNEL);
return;
errout:
rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_ROUTE, err);
}
static const struct nla_policy rtm_phonet_policy[RTA_MAX+1] = {
[RTA_DST] = { .type = NLA_U8 },
[RTA_OIF] = { .type = NLA_U32 },
};
static int route_doit(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct net *net = sock_net(skb->sk);
struct nlattr *tb[RTA_MAX+1];
struct net_device *dev;
struct rtmsg *rtm;
int err;
u8 dst;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!netlink_capable(skb, CAP_SYS_ADMIN))
return -EPERM;
ASSERT_RTNL();
err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_phonet_policy);
if (err < 0)
return err;
rtm = nlmsg_data(nlh);
if (rtm->rtm_table != RT_TABLE_MAIN || rtm->rtm_type != RTN_UNICAST)
return -EINVAL;
if (tb[RTA_DST] == NULL || tb[RTA_OIF] == NULL)
return -EINVAL;
dst = nla_get_u8(tb[RTA_DST]);
if (dst & 3) /* Phonet addresses only have 6 high-order bits */
return -EINVAL;
dev = __dev_get_by_index(net, nla_get_u32(tb[RTA_OIF]));
if (dev == NULL)
return -ENODEV;
if (nlh->nlmsg_type == RTM_NEWROUTE)
err = phonet_route_add(dev, dst);
else
err = phonet_route_del(dev, dst);
if (!err)
rtm_phonet_notify(nlh->nlmsg_type, dev, dst);
return err;
}
static int route_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
{
struct net *net = sock_net(skb->sk);
u8 addr, addr_idx = 0, addr_start_idx = cb->args[0];
rcu_read_lock();
for (addr = 0; addr < 64; addr++) {
struct net_device *dev;
dev = phonet_route_get_rcu(net, addr << 2);
if (!dev)
continue;
if (addr_idx++ < addr_start_idx)
continue;
if (fill_route(skb, dev, addr << 2, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, RTM_NEWROUTE))
goto out;
}
out:
rcu_read_unlock();
cb->args[0] = addr_idx;
cb->args[1] = 0;
return skb->len;
}
int __init phonet_netlink_register(void)
{
int err = __rtnl_register(PF_PHONET, RTM_NEWADDR, addr_doit,
NULL, NULL);
if (err)
return err;
/* Further __rtnl_register() cannot fail */
__rtnl_register(PF_PHONET, RTM_DELADDR, addr_doit, NULL, NULL);
__rtnl_register(PF_PHONET, RTM_GETADDR, NULL, getaddr_dumpit, NULL);
__rtnl_register(PF_PHONET, RTM_NEWROUTE, route_doit, NULL, NULL);
__rtnl_register(PF_PHONET, RTM_DELROUTE, route_doit, NULL, NULL);
__rtnl_register(PF_PHONET, RTM_GETROUTE, NULL, route_dumpit, NULL);
return 0;
}

823
net/phonet/socket.c Normal file
View file

@ -0,0 +1,823 @@
/*
* File: socket.c
*
* Phonet sockets
*
* Copyright (C) 2008 Nokia Corporation.
*
* Authors: Sakari Ailus <sakari.ailus@nokia.com>
* Rémi Denis-Courmont
*
* 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/gfp.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/poll.h>
#include <net/sock.h>
#include <net/tcp_states.h>
#include <linux/phonet.h>
#include <linux/export.h>
#include <net/phonet/phonet.h>
#include <net/phonet/pep.h>
#include <net/phonet/pn_dev.h>
static int pn_socket_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk) {
sock->sk = NULL;
sk->sk_prot->close(sk, 0);
}
return 0;
}
#define PN_HASHSIZE 16
#define PN_HASHMASK (PN_HASHSIZE-1)
static struct {
struct hlist_head hlist[PN_HASHSIZE];
struct mutex lock;
} pnsocks;
void __init pn_sock_init(void)
{
unsigned int i;
for (i = 0; i < PN_HASHSIZE; i++)
INIT_HLIST_HEAD(pnsocks.hlist + i);
mutex_init(&pnsocks.lock);
}
static struct hlist_head *pn_hash_list(u16 obj)
{
return pnsocks.hlist + (obj & PN_HASHMASK);
}
/*
* Find address based on socket address, match only certain fields.
* Also grab sock if it was found. Remember to sock_put it later.
*/
struct sock *pn_find_sock_by_sa(struct net *net, const struct sockaddr_pn *spn)
{
struct sock *sknode;
struct sock *rval = NULL;
u16 obj = pn_sockaddr_get_object(spn);
u8 res = spn->spn_resource;
struct hlist_head *hlist = pn_hash_list(obj);
rcu_read_lock();
sk_for_each_rcu(sknode, hlist) {
struct pn_sock *pn = pn_sk(sknode);
BUG_ON(!pn->sobject); /* unbound socket */
if (!net_eq(sock_net(sknode), net))
continue;
if (pn_port(obj)) {
/* Look up socket by port */
if (pn_port(pn->sobject) != pn_port(obj))
continue;
} else {
/* If port is zero, look up by resource */
if (pn->resource != res)
continue;
}
if (pn_addr(pn->sobject) &&
pn_addr(pn->sobject) != pn_addr(obj))
continue;
rval = sknode;
sock_hold(sknode);
break;
}
rcu_read_unlock();
return rval;
}
/* Deliver a broadcast packet (only in bottom-half) */
void pn_deliver_sock_broadcast(struct net *net, struct sk_buff *skb)
{
struct hlist_head *hlist = pnsocks.hlist;
unsigned int h;
rcu_read_lock();
for (h = 0; h < PN_HASHSIZE; h++) {
struct sock *sknode;
sk_for_each(sknode, hlist) {
struct sk_buff *clone;
if (!net_eq(sock_net(sknode), net))
continue;
if (!sock_flag(sknode, SOCK_BROADCAST))
continue;
clone = skb_clone(skb, GFP_ATOMIC);
if (clone) {
sock_hold(sknode);
sk_receive_skb(sknode, clone, 0);
}
}
hlist++;
}
rcu_read_unlock();
}
void pn_sock_hash(struct sock *sk)
{
struct hlist_head *hlist = pn_hash_list(pn_sk(sk)->sobject);
mutex_lock(&pnsocks.lock);
sk_add_node_rcu(sk, hlist);
mutex_unlock(&pnsocks.lock);
}
EXPORT_SYMBOL(pn_sock_hash);
void pn_sock_unhash(struct sock *sk)
{
mutex_lock(&pnsocks.lock);
sk_del_node_init_rcu(sk);
mutex_unlock(&pnsocks.lock);
pn_sock_unbind_all_res(sk);
synchronize_rcu();
}
EXPORT_SYMBOL(pn_sock_unhash);
static DEFINE_MUTEX(port_mutex);
static int pn_socket_bind(struct socket *sock, struct sockaddr *addr, int len)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
struct sockaddr_pn *spn = (struct sockaddr_pn *)addr;
int err;
u16 handle;
u8 saddr;
if (sk->sk_prot->bind)
return sk->sk_prot->bind(sk, addr, len);
if (len < sizeof(struct sockaddr_pn))
return -EINVAL;
if (spn->spn_family != AF_PHONET)
return -EAFNOSUPPORT;
handle = pn_sockaddr_get_object((struct sockaddr_pn *)addr);
saddr = pn_addr(handle);
if (saddr && phonet_address_lookup(sock_net(sk), saddr))
return -EADDRNOTAVAIL;
lock_sock(sk);
if (sk->sk_state != TCP_CLOSE || pn_port(pn->sobject)) {
err = -EINVAL; /* attempt to rebind */
goto out;
}
WARN_ON(sk_hashed(sk));
mutex_lock(&port_mutex);
err = sk->sk_prot->get_port(sk, pn_port(handle));
if (err)
goto out_port;
/* get_port() sets the port, bind() sets the address if applicable */
pn->sobject = pn_object(saddr, pn_port(pn->sobject));
pn->resource = spn->spn_resource;
/* Enable RX on the socket */
sk->sk_prot->hash(sk);
out_port:
mutex_unlock(&port_mutex);
out:
release_sock(sk);
return err;
}
static int pn_socket_autobind(struct socket *sock)
{
struct sockaddr_pn sa;
int err;
memset(&sa, 0, sizeof(sa));
sa.spn_family = AF_PHONET;
err = pn_socket_bind(sock, (struct sockaddr *)&sa,
sizeof(struct sockaddr_pn));
if (err != -EINVAL)
return err;
BUG_ON(!pn_port(pn_sk(sock->sk)->sobject));
return 0; /* socket was already bound */
}
static int pn_socket_connect(struct socket *sock, struct sockaddr *addr,
int len, int flags)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
struct sockaddr_pn *spn = (struct sockaddr_pn *)addr;
struct task_struct *tsk = current;
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
int err;
if (pn_socket_autobind(sock))
return -ENOBUFS;
if (len < sizeof(struct sockaddr_pn))
return -EINVAL;
if (spn->spn_family != AF_PHONET)
return -EAFNOSUPPORT;
lock_sock(sk);
switch (sock->state) {
case SS_UNCONNECTED:
if (sk->sk_state != TCP_CLOSE) {
err = -EISCONN;
goto out;
}
break;
case SS_CONNECTING:
err = -EALREADY;
goto out;
default:
err = -EISCONN;
goto out;
}
pn->dobject = pn_sockaddr_get_object(spn);
pn->resource = pn_sockaddr_get_resource(spn);
sock->state = SS_CONNECTING;
err = sk->sk_prot->connect(sk, addr, len);
if (err) {
sock->state = SS_UNCONNECTED;
pn->dobject = 0;
goto out;
}
while (sk->sk_state == TCP_SYN_SENT) {
DEFINE_WAIT(wait);
if (!timeo) {
err = -EINPROGRESS;
goto out;
}
if (signal_pending(tsk)) {
err = sock_intr_errno(timeo);
goto out;
}
prepare_to_wait_exclusive(sk_sleep(sk), &wait,
TASK_INTERRUPTIBLE);
release_sock(sk);
timeo = schedule_timeout(timeo);
lock_sock(sk);
finish_wait(sk_sleep(sk), &wait);
}
if ((1 << sk->sk_state) & (TCPF_SYN_RECV|TCPF_ESTABLISHED))
err = 0;
else if (sk->sk_state == TCP_CLOSE_WAIT)
err = -ECONNRESET;
else
err = -ECONNREFUSED;
sock->state = err ? SS_UNCONNECTED : SS_CONNECTED;
out:
release_sock(sk);
return err;
}
static int pn_socket_accept(struct socket *sock, struct socket *newsock,
int flags)
{
struct sock *sk = sock->sk;
struct sock *newsk;
int err;
if (unlikely(sk->sk_state != TCP_LISTEN))
return -EINVAL;
newsk = sk->sk_prot->accept(sk, flags, &err);
if (!newsk)
return err;
lock_sock(newsk);
sock_graft(newsk, newsock);
newsock->state = SS_CONNECTED;
release_sock(newsk);
return 0;
}
static int pn_socket_getname(struct socket *sock, struct sockaddr *addr,
int *sockaddr_len, int peer)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
memset(addr, 0, sizeof(struct sockaddr_pn));
addr->sa_family = AF_PHONET;
if (!peer) /* Race with bind() here is userland's problem. */
pn_sockaddr_set_object((struct sockaddr_pn *)addr,
pn->sobject);
*sockaddr_len = sizeof(struct sockaddr_pn);
return 0;
}
static unsigned int pn_socket_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
struct sock *sk = sock->sk;
struct pep_sock *pn = pep_sk(sk);
unsigned int mask = 0;
poll_wait(file, sk_sleep(sk), wait);
if (sk->sk_state == TCP_CLOSE)
return POLLERR;
if (!skb_queue_empty(&sk->sk_receive_queue))
mask |= POLLIN | POLLRDNORM;
if (!skb_queue_empty(&pn->ctrlreq_queue))
mask |= POLLPRI;
if (!mask && sk->sk_state == TCP_CLOSE_WAIT)
return POLLHUP;
if (sk->sk_state == TCP_ESTABLISHED &&
atomic_read(&sk->sk_wmem_alloc) < sk->sk_sndbuf &&
atomic_read(&pn->tx_credits))
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
return mask;
}
static int pn_socket_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg)
{
struct sock *sk = sock->sk;
struct pn_sock *pn = pn_sk(sk);
if (cmd == SIOCPNGETOBJECT) {
struct net_device *dev;
u16 handle;
u8 saddr;
if (get_user(handle, (__u16 __user *)arg))
return -EFAULT;
lock_sock(sk);
if (sk->sk_bound_dev_if)
dev = dev_get_by_index(sock_net(sk),
sk->sk_bound_dev_if);
else
dev = phonet_device_get(sock_net(sk));
if (dev && (dev->flags & IFF_UP))
saddr = phonet_address_get(dev, pn_addr(handle));
else
saddr = PN_NO_ADDR;
release_sock(sk);
if (dev)
dev_put(dev);
if (saddr == PN_NO_ADDR)
return -EHOSTUNREACH;
handle = pn_object(saddr, pn_port(pn->sobject));
return put_user(handle, (__u16 __user *)arg);
}
return sk->sk_prot->ioctl(sk, cmd, arg);
}
static int pn_socket_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
int err = 0;
if (pn_socket_autobind(sock))
return -ENOBUFS;
lock_sock(sk);
if (sock->state != SS_UNCONNECTED) {
err = -EINVAL;
goto out;
}
if (sk->sk_state != TCP_LISTEN) {
sk->sk_state = TCP_LISTEN;
sk->sk_ack_backlog = 0;
}
sk->sk_max_ack_backlog = backlog;
out:
release_sock(sk);
return err;
}
static int pn_socket_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *m, size_t total_len)
{
struct sock *sk = sock->sk;
if (pn_socket_autobind(sock))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, m, total_len);
}
const struct proto_ops phonet_dgram_ops = {
.family = AF_PHONET,
.owner = THIS_MODULE,
.release = pn_socket_release,
.bind = pn_socket_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = pn_socket_getname,
.poll = datagram_poll,
.ioctl = pn_socket_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_no_setsockopt,
.getsockopt = sock_no_getsockopt,
#ifdef CONFIG_COMPAT
.compat_setsockopt = sock_no_setsockopt,
.compat_getsockopt = sock_no_getsockopt,
#endif
.sendmsg = pn_socket_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
const struct proto_ops phonet_stream_ops = {
.family = AF_PHONET,
.owner = THIS_MODULE,
.release = pn_socket_release,
.bind = pn_socket_bind,
.connect = pn_socket_connect,
.socketpair = sock_no_socketpair,
.accept = pn_socket_accept,
.getname = pn_socket_getname,
.poll = pn_socket_poll,
.ioctl = pn_socket_ioctl,
.listen = pn_socket_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_sock_common_setsockopt,
.compat_getsockopt = compat_sock_common_getsockopt,
#endif
.sendmsg = pn_socket_sendmsg,
.recvmsg = sock_common_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
EXPORT_SYMBOL(phonet_stream_ops);
/* allocate port for a socket */
int pn_sock_get_port(struct sock *sk, unsigned short sport)
{
static int port_cur;
struct net *net = sock_net(sk);
struct pn_sock *pn = pn_sk(sk);
struct sockaddr_pn try_sa;
struct sock *tmpsk;
memset(&try_sa, 0, sizeof(struct sockaddr_pn));
try_sa.spn_family = AF_PHONET;
WARN_ON(!mutex_is_locked(&port_mutex));
if (!sport) {
/* search free port */
int port, pmin, pmax;
phonet_get_local_port_range(&pmin, &pmax);
for (port = pmin; port <= pmax; port++) {
port_cur++;
if (port_cur < pmin || port_cur > pmax)
port_cur = pmin;
pn_sockaddr_set_port(&try_sa, port_cur);
tmpsk = pn_find_sock_by_sa(net, &try_sa);
if (tmpsk == NULL) {
sport = port_cur;
goto found;
} else
sock_put(tmpsk);
}
} else {
/* try to find specific port */
pn_sockaddr_set_port(&try_sa, sport);
tmpsk = pn_find_sock_by_sa(net, &try_sa);
if (tmpsk == NULL)
/* No sock there! We can use that port... */
goto found;
else
sock_put(tmpsk);
}
/* the port must be in use already */
return -EADDRINUSE;
found:
pn->sobject = pn_object(pn_addr(pn->sobject), sport);
return 0;
}
EXPORT_SYMBOL(pn_sock_get_port);
#ifdef CONFIG_PROC_FS
static struct sock *pn_sock_get_idx(struct seq_file *seq, loff_t pos)
{
struct net *net = seq_file_net(seq);
struct hlist_head *hlist = pnsocks.hlist;
struct sock *sknode;
unsigned int h;
for (h = 0; h < PN_HASHSIZE; h++) {
sk_for_each_rcu(sknode, hlist) {
if (!net_eq(net, sock_net(sknode)))
continue;
if (!pos)
return sknode;
pos--;
}
hlist++;
}
return NULL;
}
static struct sock *pn_sock_get_next(struct seq_file *seq, struct sock *sk)
{
struct net *net = seq_file_net(seq);
do
sk = sk_next(sk);
while (sk && !net_eq(net, sock_net(sk)));
return sk;
}
static void *pn_sock_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(rcu)
{
rcu_read_lock();
return *pos ? pn_sock_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
}
static void *pn_sock_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct sock *sk;
if (v == SEQ_START_TOKEN)
sk = pn_sock_get_idx(seq, 0);
else
sk = pn_sock_get_next(seq, v);
(*pos)++;
return sk;
}
static void pn_sock_seq_stop(struct seq_file *seq, void *v)
__releases(rcu)
{
rcu_read_unlock();
}
static int pn_sock_seq_show(struct seq_file *seq, void *v)
{
seq_setwidth(seq, 127);
if (v == SEQ_START_TOKEN)
seq_puts(seq, "pt loc rem rs st tx_queue rx_queue "
" uid inode ref pointer drops");
else {
struct sock *sk = v;
struct pn_sock *pn = pn_sk(sk);
seq_printf(seq, "%2d %04X:%04X:%02X %02X %08X:%08X %5d %lu "
"%d %pK %d",
sk->sk_protocol, pn->sobject, pn->dobject,
pn->resource, sk->sk_state,
sk_wmem_alloc_get(sk), sk_rmem_alloc_get(sk),
from_kuid_munged(seq_user_ns(seq), sock_i_uid(sk)),
sock_i_ino(sk),
atomic_read(&sk->sk_refcnt), sk,
atomic_read(&sk->sk_drops));
}
seq_pad(seq, '\n');
return 0;
}
static const struct seq_operations pn_sock_seq_ops = {
.start = pn_sock_seq_start,
.next = pn_sock_seq_next,
.stop = pn_sock_seq_stop,
.show = pn_sock_seq_show,
};
static int pn_sock_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &pn_sock_seq_ops,
sizeof(struct seq_net_private));
}
const struct file_operations pn_sock_seq_fops = {
.owner = THIS_MODULE,
.open = pn_sock_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
#endif
static struct {
struct sock *sk[256];
} pnres;
/*
* Find and hold socket based on resource.
*/
struct sock *pn_find_sock_by_res(struct net *net, u8 res)
{
struct sock *sk;
if (!net_eq(net, &init_net))
return NULL;
rcu_read_lock();
sk = rcu_dereference(pnres.sk[res]);
if (sk)
sock_hold(sk);
rcu_read_unlock();
return sk;
}
static DEFINE_MUTEX(resource_mutex);
int pn_sock_bind_res(struct sock *sk, u8 res)
{
int ret = -EADDRINUSE;
if (!net_eq(sock_net(sk), &init_net))
return -ENOIOCTLCMD;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
if (pn_socket_autobind(sk->sk_socket))
return -EAGAIN;
mutex_lock(&resource_mutex);
if (pnres.sk[res] == NULL) {
sock_hold(sk);
rcu_assign_pointer(pnres.sk[res], sk);
ret = 0;
}
mutex_unlock(&resource_mutex);
return ret;
}
int pn_sock_unbind_res(struct sock *sk, u8 res)
{
int ret = -ENOENT;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
mutex_lock(&resource_mutex);
if (pnres.sk[res] == sk) {
RCU_INIT_POINTER(pnres.sk[res], NULL);
ret = 0;
}
mutex_unlock(&resource_mutex);
if (ret == 0) {
synchronize_rcu();
sock_put(sk);
}
return ret;
}
void pn_sock_unbind_all_res(struct sock *sk)
{
unsigned int res, match = 0;
mutex_lock(&resource_mutex);
for (res = 0; res < 256; res++) {
if (pnres.sk[res] == sk) {
RCU_INIT_POINTER(pnres.sk[res], NULL);
match++;
}
}
mutex_unlock(&resource_mutex);
while (match > 0) {
__sock_put(sk);
match--;
}
/* Caller is responsible for RCU sync before final sock_put() */
}
#ifdef CONFIG_PROC_FS
static struct sock **pn_res_get_idx(struct seq_file *seq, loff_t pos)
{
struct net *net = seq_file_net(seq);
unsigned int i;
if (!net_eq(net, &init_net))
return NULL;
for (i = 0; i < 256; i++) {
if (pnres.sk[i] == NULL)
continue;
if (!pos)
return pnres.sk + i;
pos--;
}
return NULL;
}
static struct sock **pn_res_get_next(struct seq_file *seq, struct sock **sk)
{
struct net *net = seq_file_net(seq);
unsigned int i;
BUG_ON(!net_eq(net, &init_net));
for (i = (sk - pnres.sk) + 1; i < 256; i++)
if (pnres.sk[i])
return pnres.sk + i;
return NULL;
}
static void *pn_res_seq_start(struct seq_file *seq, loff_t *pos)
__acquires(resource_mutex)
{
mutex_lock(&resource_mutex);
return *pos ? pn_res_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
}
static void *pn_res_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct sock **sk;
if (v == SEQ_START_TOKEN)
sk = pn_res_get_idx(seq, 0);
else
sk = pn_res_get_next(seq, v);
(*pos)++;
return sk;
}
static void pn_res_seq_stop(struct seq_file *seq, void *v)
__releases(resource_mutex)
{
mutex_unlock(&resource_mutex);
}
static int pn_res_seq_show(struct seq_file *seq, void *v)
{
seq_setwidth(seq, 63);
if (v == SEQ_START_TOKEN)
seq_puts(seq, "rs uid inode");
else {
struct sock **psk = v;
struct sock *sk = *psk;
seq_printf(seq, "%02X %5u %lu",
(int) (psk - pnres.sk),
from_kuid_munged(seq_user_ns(seq), sock_i_uid(sk)),
sock_i_ino(sk));
}
seq_pad(seq, '\n');
return 0;
}
static const struct seq_operations pn_res_seq_ops = {
.start = pn_res_seq_start,
.next = pn_res_seq_next,
.stop = pn_res_seq_stop,
.show = pn_res_seq_show,
};
static int pn_res_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &pn_res_seq_ops,
sizeof(struct seq_net_private));
}
const struct file_operations pn_res_seq_fops = {
.owner = THIS_MODULE,
.open = pn_res_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
#endif

110
net/phonet/sysctl.c Normal file
View file

@ -0,0 +1,110 @@
/*
* File: sysctl.c
*
* Phonet /proc/sys/net/phonet interface implementation
*
* Copyright (C) 2008 Nokia Corporation.
*
* Author: Rémi Denis-Courmont
*
* 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/seqlock.h>
#include <linux/sysctl.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/phonet.h>
#include <net/phonet/phonet.h>
#define DYNAMIC_PORT_MIN 0x40
#define DYNAMIC_PORT_MAX 0x7f
static DEFINE_SEQLOCK(local_port_range_lock);
static int local_port_range_min[2] = {0, 0};
static int local_port_range_max[2] = {1023, 1023};
static int local_port_range[2] = {DYNAMIC_PORT_MIN, DYNAMIC_PORT_MAX};
static struct ctl_table_header *phonet_table_hrd;
static void set_local_port_range(int range[2])
{
write_seqlock(&local_port_range_lock);
local_port_range[0] = range[0];
local_port_range[1] = range[1];
write_sequnlock(&local_port_range_lock);
}
void phonet_get_local_port_range(int *min, int *max)
{
unsigned int seq;
do {
seq = read_seqbegin(&local_port_range_lock);
if (min)
*min = local_port_range[0];
if (max)
*max = local_port_range[1];
} while (read_seqretry(&local_port_range_lock, seq));
}
static int proc_local_port_range(struct ctl_table *table, int write,
void __user *buffer,
size_t *lenp, loff_t *ppos)
{
int ret;
int range[2] = {local_port_range[0], local_port_range[1]};
struct ctl_table tmp = {
.data = &range,
.maxlen = sizeof(range),
.mode = table->mode,
.extra1 = &local_port_range_min,
.extra2 = &local_port_range_max,
};
ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);
if (write && ret == 0) {
if (range[1] < range[0])
ret = -EINVAL;
else
set_local_port_range(range);
}
return ret;
}
static struct ctl_table phonet_table[] = {
{
.procname = "local_port_range",
.data = &local_port_range,
.maxlen = sizeof(local_port_range),
.mode = 0644,
.proc_handler = proc_local_port_range,
},
{ }
};
int __init phonet_sysctl_init(void)
{
phonet_table_hrd = register_net_sysctl(&init_net, "net/phonet", phonet_table);
return phonet_table_hrd == NULL ? -ENOMEM : 0;
}
void phonet_sysctl_exit(void)
{
unregister_net_sysctl_table(phonet_table_hrd);
}