diff options
Diffstat (limited to 'net/phonet/af_phonet.c')
-rw-r--r-- | net/phonet/af_phonet.c | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/net/phonet/af_phonet.c b/net/phonet/af_phonet.c new file mode 100644 index 000000000000..9e9c6fce11aa --- /dev/null +++ b/net/phonet/af_phonet.c | |||
@@ -0,0 +1,477 @@ | |||
1 | /* | ||
2 | * File: af_phonet.c | ||
3 | * | ||
4 | * Phonet protocols family | ||
5 | * | ||
6 | * Copyright (C) 2008 Nokia Corporation. | ||
7 | * | ||
8 | * Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com> | ||
9 | * Original author: Sakari Ailus <sakari.ailus@nokia.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or | ||
12 | * modify it under the terms of the GNU General Public License | ||
13 | * version 2 as published by the Free Software Foundation. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, but | ||
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
18 | * General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | ||
23 | * 02110-1301 USA | ||
24 | */ | ||
25 | |||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <asm/unaligned.h> | ||
29 | #include <net/sock.h> | ||
30 | |||
31 | #include <linux/if_phonet.h> | ||
32 | #include <linux/phonet.h> | ||
33 | #include <net/phonet/phonet.h> | ||
34 | #include <net/phonet/pn_dev.h> | ||
35 | |||
36 | static struct net_proto_family phonet_proto_family; | ||
37 | static struct phonet_protocol *phonet_proto_get(int protocol); | ||
38 | static inline void phonet_proto_put(struct phonet_protocol *pp); | ||
39 | |||
40 | /* protocol family functions */ | ||
41 | |||
42 | static int pn_socket_create(struct net *net, struct socket *sock, int protocol) | ||
43 | { | ||
44 | struct sock *sk; | ||
45 | struct pn_sock *pn; | ||
46 | struct phonet_protocol *pnp; | ||
47 | int err; | ||
48 | |||
49 | if (net != &init_net) | ||
50 | return -EAFNOSUPPORT; | ||
51 | |||
52 | if (!capable(CAP_SYS_ADMIN)) | ||
53 | return -EPERM; | ||
54 | |||
55 | if (protocol == 0) { | ||
56 | /* Default protocol selection */ | ||
57 | switch (sock->type) { | ||
58 | case SOCK_DGRAM: | ||
59 | protocol = PN_PROTO_PHONET; | ||
60 | break; | ||
61 | case SOCK_SEQPACKET: | ||
62 | protocol = PN_PROTO_PIPE; | ||
63 | break; | ||
64 | default: | ||
65 | return -EPROTONOSUPPORT; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | pnp = phonet_proto_get(protocol); | ||
70 | #ifdef CONFIG_KMOD | ||
71 | if (pnp == NULL && | ||
72 | request_module("net-pf-%d-proto-%d", PF_PHONET, protocol) == 0) | ||
73 | pnp = phonet_proto_get(protocol); | ||
74 | #endif | ||
75 | if (pnp == NULL) | ||
76 | return -EPROTONOSUPPORT; | ||
77 | if (sock->type != pnp->sock_type) { | ||
78 | err = -EPROTONOSUPPORT; | ||
79 | goto out; | ||
80 | } | ||
81 | |||
82 | sk = sk_alloc(net, PF_PHONET, GFP_KERNEL, pnp->prot); | ||
83 | if (sk == NULL) { | ||
84 | err = -ENOMEM; | ||
85 | goto out; | ||
86 | } | ||
87 | |||
88 | sock_init_data(sock, sk); | ||
89 | sock->state = SS_UNCONNECTED; | ||
90 | sock->ops = pnp->ops; | ||
91 | sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv; | ||
92 | sk->sk_protocol = protocol; | ||
93 | pn = pn_sk(sk); | ||
94 | pn->sobject = 0; | ||
95 | pn->resource = 0; | ||
96 | sk->sk_prot->init(sk); | ||
97 | err = 0; | ||
98 | |||
99 | out: | ||
100 | phonet_proto_put(pnp); | ||
101 | return err; | ||
102 | } | ||
103 | |||
104 | static struct net_proto_family phonet_proto_family = { | ||
105 | .family = PF_PHONET, | ||
106 | .create = pn_socket_create, | ||
107 | .owner = THIS_MODULE, | ||
108 | }; | ||
109 | |||
110 | /* Phonet device header operations */ | ||
111 | static int pn_header_create(struct sk_buff *skb, struct net_device *dev, | ||
112 | unsigned short type, const void *daddr, | ||
113 | const void *saddr, unsigned len) | ||
114 | { | ||
115 | u8 *media = skb_push(skb, 1); | ||
116 | |||
117 | if (type != ETH_P_PHONET) | ||
118 | return -1; | ||
119 | |||
120 | if (!saddr) | ||
121 | saddr = dev->dev_addr; | ||
122 | *media = *(const u8 *)saddr; | ||
123 | return 1; | ||
124 | } | ||
125 | |||
126 | static int pn_header_parse(const struct sk_buff *skb, unsigned char *haddr) | ||
127 | { | ||
128 | const u8 *media = skb_mac_header(skb); | ||
129 | *haddr = *media; | ||
130 | return 1; | ||
131 | } | ||
132 | |||
133 | struct header_ops phonet_header_ops = { | ||
134 | .create = pn_header_create, | ||
135 | .parse = pn_header_parse, | ||
136 | }; | ||
137 | EXPORT_SYMBOL(phonet_header_ops); | ||
138 | |||
139 | /* | ||
140 | * Prepends an ISI header and sends a datagram. | ||
141 | */ | ||
142 | static int pn_send(struct sk_buff *skb, struct net_device *dev, | ||
143 | u16 dst, u16 src, u8 res, u8 irq) | ||
144 | { | ||
145 | struct phonethdr *ph; | ||
146 | int err; | ||
147 | |||
148 | if (skb->len + 2 > 0xffff) { | ||
149 | /* Phonet length field would overflow */ | ||
150 | err = -EMSGSIZE; | ||
151 | goto drop; | ||
152 | } | ||
153 | |||
154 | skb_reset_transport_header(skb); | ||
155 | WARN_ON(skb_headroom(skb) & 1); /* HW assumes word alignment */ | ||
156 | skb_push(skb, sizeof(struct phonethdr)); | ||
157 | skb_reset_network_header(skb); | ||
158 | ph = pn_hdr(skb); | ||
159 | ph->pn_rdev = pn_dev(dst); | ||
160 | ph->pn_sdev = pn_dev(src); | ||
161 | ph->pn_res = res; | ||
162 | ph->pn_length = __cpu_to_be16(skb->len + 2 - sizeof(*ph)); | ||
163 | ph->pn_robj = pn_obj(dst); | ||
164 | ph->pn_sobj = pn_obj(src); | ||
165 | |||
166 | skb->protocol = htons(ETH_P_PHONET); | ||
167 | skb->priority = 0; | ||
168 | skb->dev = dev; | ||
169 | |||
170 | if (pn_addr(src) == pn_addr(dst)) { | ||
171 | skb_reset_mac_header(skb); | ||
172 | skb->pkt_type = PACKET_LOOPBACK; | ||
173 | skb_orphan(skb); | ||
174 | if (irq) | ||
175 | netif_rx(skb); | ||
176 | else | ||
177 | netif_rx_ni(skb); | ||
178 | err = 0; | ||
179 | } else { | ||
180 | err = dev_hard_header(skb, dev, ntohs(skb->protocol), | ||
181 | NULL, NULL, skb->len); | ||
182 | if (err < 0) { | ||
183 | err = -EHOSTUNREACH; | ||
184 | goto drop; | ||
185 | } | ||
186 | err = dev_queue_xmit(skb); | ||
187 | } | ||
188 | |||
189 | return err; | ||
190 | drop: | ||
191 | kfree_skb(skb); | ||
192 | return err; | ||
193 | } | ||
194 | |||
195 | static int pn_raw_send(const void *data, int len, struct net_device *dev, | ||
196 | u16 dst, u16 src, u8 res) | ||
197 | { | ||
198 | struct sk_buff *skb = alloc_skb(MAX_PHONET_HEADER + len, GFP_ATOMIC); | ||
199 | if (skb == NULL) | ||
200 | return -ENOMEM; | ||
201 | |||
202 | skb_reserve(skb, MAX_PHONET_HEADER); | ||
203 | __skb_put(skb, len); | ||
204 | skb_copy_to_linear_data(skb, data, len); | ||
205 | return pn_send(skb, dev, dst, src, res, 1); | ||
206 | } | ||
207 | |||
208 | /* | ||
209 | * Create a Phonet header for the skb and send it out. Returns | ||
210 | * non-zero error code if failed. The skb is freed then. | ||
211 | */ | ||
212 | int pn_skb_send(struct sock *sk, struct sk_buff *skb, | ||
213 | const struct sockaddr_pn *target) | ||
214 | { | ||
215 | struct net_device *dev; | ||
216 | struct pn_sock *pn = pn_sk(sk); | ||
217 | int err; | ||
218 | u16 src; | ||
219 | u8 daddr = pn_sockaddr_get_addr(target), saddr = PN_NO_ADDR; | ||
220 | |||
221 | err = -EHOSTUNREACH; | ||
222 | if (sk->sk_bound_dev_if) | ||
223 | dev = dev_get_by_index(sock_net(sk), sk->sk_bound_dev_if); | ||
224 | else | ||
225 | dev = phonet_device_get(sock_net(sk)); | ||
226 | if (!dev || !(dev->flags & IFF_UP)) | ||
227 | goto drop; | ||
228 | |||
229 | saddr = phonet_address_get(dev, daddr); | ||
230 | if (saddr == PN_NO_ADDR) | ||
231 | goto drop; | ||
232 | |||
233 | src = pn->sobject; | ||
234 | if (!pn_addr(src)) | ||
235 | src = pn_object(saddr, pn_obj(src)); | ||
236 | |||
237 | err = pn_send(skb, dev, pn_sockaddr_get_object(target), | ||
238 | src, pn_sockaddr_get_resource(target), 0); | ||
239 | dev_put(dev); | ||
240 | return err; | ||
241 | |||
242 | drop: | ||
243 | kfree_skb(skb); | ||
244 | if (dev) | ||
245 | dev_put(dev); | ||
246 | return err; | ||
247 | } | ||
248 | EXPORT_SYMBOL(pn_skb_send); | ||
249 | |||
250 | /* Do not send an error message in response to an error message */ | ||
251 | static inline int can_respond(struct sk_buff *skb) | ||
252 | { | ||
253 | const struct phonethdr *ph; | ||
254 | const struct phonetmsg *pm; | ||
255 | u8 submsg_id; | ||
256 | |||
257 | if (!pskb_may_pull(skb, 3)) | ||
258 | return 0; | ||
259 | |||
260 | ph = pn_hdr(skb); | ||
261 | if (phonet_address_get(skb->dev, ph->pn_rdev) != ph->pn_rdev) | ||
262 | return 0; /* we are not the destination */ | ||
263 | if (ph->pn_res == PN_PREFIX && !pskb_may_pull(skb, 5)) | ||
264 | return 0; | ||
265 | |||
266 | ph = pn_hdr(skb); /* re-acquires the pointer */ | ||
267 | pm = pn_msg(skb); | ||
268 | if (pm->pn_msg_id != PN_COMMON_MESSAGE) | ||
269 | return 1; | ||
270 | submsg_id = (ph->pn_res == PN_PREFIX) | ||
271 | ? pm->pn_e_submsg_id : pm->pn_submsg_id; | ||
272 | if (submsg_id != PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP && | ||
273 | pm->pn_e_submsg_id != PN_COMM_SERVICE_NOT_IDENTIFIED_RESP) | ||
274 | return 1; | ||
275 | return 0; | ||
276 | } | ||
277 | |||
278 | static int send_obj_unreachable(struct sk_buff *rskb) | ||
279 | { | ||
280 | const struct phonethdr *oph = pn_hdr(rskb); | ||
281 | const struct phonetmsg *opm = pn_msg(rskb); | ||
282 | struct phonetmsg resp; | ||
283 | |||
284 | memset(&resp, 0, sizeof(resp)); | ||
285 | resp.pn_trans_id = opm->pn_trans_id; | ||
286 | resp.pn_msg_id = PN_COMMON_MESSAGE; | ||
287 | if (oph->pn_res == PN_PREFIX) { | ||
288 | resp.pn_e_res_id = opm->pn_e_res_id; | ||
289 | resp.pn_e_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP; | ||
290 | resp.pn_e_orig_msg_id = opm->pn_msg_id; | ||
291 | resp.pn_e_status = 0; | ||
292 | } else { | ||
293 | resp.pn_submsg_id = PN_COMM_ISA_ENTITY_NOT_REACHABLE_RESP; | ||
294 | resp.pn_orig_msg_id = opm->pn_msg_id; | ||
295 | resp.pn_status = 0; | ||
296 | } | ||
297 | return pn_raw_send(&resp, sizeof(resp), rskb->dev, | ||
298 | pn_object(oph->pn_sdev, oph->pn_sobj), | ||
299 | pn_object(oph->pn_rdev, oph->pn_robj), | ||
300 | oph->pn_res); | ||
301 | } | ||
302 | |||
303 | static int send_reset_indications(struct sk_buff *rskb) | ||
304 | { | ||
305 | struct phonethdr *oph = pn_hdr(rskb); | ||
306 | static const u8 data[4] = { | ||
307 | 0x00 /* trans ID */, 0x10 /* subscribe msg */, | ||
308 | 0x00 /* subscription count */, 0x00 /* dummy */ | ||
309 | }; | ||
310 | |||
311 | return pn_raw_send(data, sizeof(data), rskb->dev, | ||
312 | pn_object(oph->pn_sdev, 0x00), | ||
313 | pn_object(oph->pn_rdev, oph->pn_robj), 0x10); | ||
314 | } | ||
315 | |||
316 | |||
317 | /* packet type functions */ | ||
318 | |||
319 | /* | ||
320 | * Stuff received packets to associated sockets. | ||
321 | * On error, returns non-zero and releases the skb. | ||
322 | */ | ||
323 | static int phonet_rcv(struct sk_buff *skb, struct net_device *dev, | ||
324 | struct packet_type *pkttype, | ||
325 | struct net_device *orig_dev) | ||
326 | { | ||
327 | struct phonethdr *ph; | ||
328 | struct sock *sk; | ||
329 | struct sockaddr_pn sa; | ||
330 | u16 len; | ||
331 | |||
332 | if (dev_net(dev) != &init_net) | ||
333 | goto out; | ||
334 | |||
335 | /* check we have at least a full Phonet header */ | ||
336 | if (!pskb_pull(skb, sizeof(struct phonethdr))) | ||
337 | goto out; | ||
338 | |||
339 | /* check that the advertised length is correct */ | ||
340 | ph = pn_hdr(skb); | ||
341 | len = get_unaligned_be16(&ph->pn_length); | ||
342 | if (len < 2) | ||
343 | goto out; | ||
344 | len -= 2; | ||
345 | if ((len > skb->len) || pskb_trim(skb, len)) | ||
346 | goto out; | ||
347 | skb_reset_transport_header(skb); | ||
348 | |||
349 | pn_skb_get_dst_sockaddr(skb, &sa); | ||
350 | if (pn_sockaddr_get_addr(&sa) == 0) | ||
351 | goto out; /* currently, we cannot be device 0 */ | ||
352 | |||
353 | sk = pn_find_sock_by_sa(&sa); | ||
354 | if (sk == NULL) { | ||
355 | if (can_respond(skb)) { | ||
356 | send_obj_unreachable(skb); | ||
357 | send_reset_indications(skb); | ||
358 | } | ||
359 | goto out; | ||
360 | } | ||
361 | |||
362 | /* Push data to the socket (or other sockets connected to it). */ | ||
363 | return sk_receive_skb(sk, skb, 0); | ||
364 | |||
365 | out: | ||
366 | kfree_skb(skb); | ||
367 | return NET_RX_DROP; | ||
368 | } | ||
369 | |||
370 | static struct packet_type phonet_packet_type = { | ||
371 | .type = __constant_htons(ETH_P_PHONET), | ||
372 | .dev = NULL, | ||
373 | .func = phonet_rcv, | ||
374 | }; | ||
375 | |||
376 | /* Transport protocol registration */ | ||
377 | static struct phonet_protocol *proto_tab[PHONET_NPROTO] __read_mostly; | ||
378 | static DEFINE_SPINLOCK(proto_tab_lock); | ||
379 | |||
380 | int __init_or_module phonet_proto_register(int protocol, | ||
381 | struct phonet_protocol *pp) | ||
382 | { | ||
383 | int err = 0; | ||
384 | |||
385 | if (protocol >= PHONET_NPROTO) | ||
386 | return -EINVAL; | ||
387 | |||
388 | err = proto_register(pp->prot, 1); | ||
389 | if (err) | ||
390 | return err; | ||
391 | |||
392 | spin_lock(&proto_tab_lock); | ||
393 | if (proto_tab[protocol]) | ||
394 | err = -EBUSY; | ||
395 | else | ||
396 | proto_tab[protocol] = pp; | ||
397 | spin_unlock(&proto_tab_lock); | ||
398 | |||
399 | return err; | ||
400 | } | ||
401 | EXPORT_SYMBOL(phonet_proto_register); | ||
402 | |||
403 | void phonet_proto_unregister(int protocol, struct phonet_protocol *pp) | ||
404 | { | ||
405 | spin_lock(&proto_tab_lock); | ||
406 | BUG_ON(proto_tab[protocol] != pp); | ||
407 | proto_tab[protocol] = NULL; | ||
408 | spin_unlock(&proto_tab_lock); | ||
409 | proto_unregister(pp->prot); | ||
410 | } | ||
411 | EXPORT_SYMBOL(phonet_proto_unregister); | ||
412 | |||
413 | static struct phonet_protocol *phonet_proto_get(int protocol) | ||
414 | { | ||
415 | struct phonet_protocol *pp; | ||
416 | |||
417 | if (protocol >= PHONET_NPROTO) | ||
418 | return NULL; | ||
419 | |||
420 | spin_lock(&proto_tab_lock); | ||
421 | pp = proto_tab[protocol]; | ||
422 | if (pp && !try_module_get(pp->prot->owner)) | ||
423 | pp = NULL; | ||
424 | spin_unlock(&proto_tab_lock); | ||
425 | |||
426 | return pp; | ||
427 | } | ||
428 | |||
429 | static inline void phonet_proto_put(struct phonet_protocol *pp) | ||
430 | { | ||
431 | module_put(pp->prot->owner); | ||
432 | } | ||
433 | |||
434 | /* Module registration */ | ||
435 | static int __init phonet_init(void) | ||
436 | { | ||
437 | int err; | ||
438 | |||
439 | err = sock_register(&phonet_proto_family); | ||
440 | if (err) { | ||
441 | printk(KERN_ALERT | ||
442 | "phonet protocol family initialization failed\n"); | ||
443 | return err; | ||
444 | } | ||
445 | |||
446 | phonet_device_init(); | ||
447 | dev_add_pack(&phonet_packet_type); | ||
448 | phonet_netlink_register(); | ||
449 | phonet_sysctl_init(); | ||
450 | |||
451 | err = isi_register(); | ||
452 | if (err) | ||
453 | goto err; | ||
454 | return 0; | ||
455 | |||
456 | err: | ||
457 | phonet_sysctl_exit(); | ||
458 | sock_unregister(PF_PHONET); | ||
459 | dev_remove_pack(&phonet_packet_type); | ||
460 | phonet_device_exit(); | ||
461 | return err; | ||
462 | } | ||
463 | |||
464 | static void __exit phonet_exit(void) | ||
465 | { | ||
466 | isi_unregister(); | ||
467 | phonet_sysctl_exit(); | ||
468 | sock_unregister(PF_PHONET); | ||
469 | dev_remove_pack(&phonet_packet_type); | ||
470 | phonet_device_exit(); | ||
471 | } | ||
472 | |||
473 | module_init(phonet_init); | ||
474 | module_exit(phonet_exit); | ||
475 | MODULE_DESCRIPTION("Phonet protocol stack for Linux"); | ||
476 | MODULE_LICENSE("GPL"); | ||
477 | MODULE_ALIAS_NETPROTO(PF_PHONET); | ||