mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
16
net/phonet/Kconfig
Normal file
16
net/phonet/Kconfig
Normal 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
11
net/phonet/Makefile
Normal 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
548
net/phonet/af_phonet.c
Normal 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
214
net/phonet/datagram.c
Normal 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
328
net/phonet/pep-gprs.c
Normal 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
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
431
net/phonet/pn_dev.c
Normal 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
312
net/phonet/pn_netlink.c
Normal 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
823
net/phonet/socket.c
Normal 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
110
net/phonet/sysctl.c
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue