From: <eri...@er...> - 2014-12-18 16:48:45
|
From: Erik Hugne <eri...@er...> The ip/udp bearer can be configured in a point-to-point mode by specifying a remote ip/hostname: tipc-config -be=udp:eth0[:<port>:<remip>:<remport>] If not specified, the UDP port used is 6118 (iana assigned). Or, it can be enabled in multicast mode: tipc-config -be=udp:eth0 In this mode, links will be established to all tipc nodes that are member in the multicast group. The multicast group is generated based on the TIPC network ID but can be overridden by specifying a multicast address as remote ip. Signed-off-by: Erik Hugne <eri...@er...> --- net/tipc/Makefile | 2 +- net/tipc/bearer.c | 1 + net/tipc/bearer.h | 5 +- net/tipc/core.c | 5 + net/tipc/udp_media.c | 503 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 net/tipc/udp_media.c diff --git a/net/tipc/Makefile b/net/tipc/Makefile index 333e459..ad50a84 100644 --- a/net/tipc/Makefile +++ b/net/tipc/Makefile @@ -8,7 +8,7 @@ tipc-y += addr.o bcast.o bearer.o config.o \ core.o link.o discover.o msg.o \ name_distr.o subscr.o name_table.o net.o \ netlink.o node.o socket.o log.o eth_media.o \ - server.o + server.o udp_media.o tipc-$(CONFIG_TIPC_MEDIA_IB) += ib_media.o tipc-$(CONFIG_SYSCTL) += sysctl.o diff --git a/net/tipc/bearer.c b/net/tipc/bearer.c index 463db5b..20d7773 100644 --- a/net/tipc/bearer.c +++ b/net/tipc/bearer.c @@ -47,6 +47,7 @@ static struct tipc_media * const media_info_array[] = { #ifdef CONFIG_TIPC_MEDIA_IB &ib_media_info, #endif + &udp_media_info, NULL }; diff --git a/net/tipc/bearer.h b/net/tipc/bearer.h index 4f0907e..fdaba9b 100644 --- a/net/tipc/bearer.h +++ b/net/tipc/bearer.h @@ -42,7 +42,7 @@ #include <net/genetlink.h> #define MAX_BEARERS 2 -#define MAX_MEDIA 2 +#define MAX_MEDIA 3 /* Identifiers associated with TIPC message header media address info * - address info field is 32 bytes long @@ -59,6 +59,7 @@ */ #define TIPC_MEDIA_TYPE_ETH 1 #define TIPC_MEDIA_TYPE_IB 2 +#define TIPC_MEDIA_TYPE_UDP 3 /** * struct tipc_media_addr - destination address used by TIPC bearers @@ -179,6 +180,8 @@ extern struct tipc_media eth_media_info; #ifdef CONFIG_TIPC_MEDIA_IB extern struct tipc_media ib_media_info; #endif +extern struct tipc_media udp_media_info; +extern struct workqueue_struct *tipc_udp_send_wq; int tipc_nl_bearer_disable(struct sk_buff *skb, struct genl_info *info); int tipc_nl_bearer_enable(struct sk_buff *skb, struct genl_info *info); diff --git a/net/tipc/core.c b/net/tipc/core.c index a5737b8..e1ba0cd 100644 --- a/net/tipc/core.c +++ b/net/tipc/core.c @@ -79,6 +79,7 @@ struct sk_buff *tipc_buf_acquire(u32 size) */ static void tipc_core_stop(void) { + destroy_workqueue(tipc_udp_send_wq); tipc_net_stop(); tipc_bearer_cleanup(); tipc_netlink_stop(); @@ -98,6 +99,10 @@ static int tipc_core_start(void) get_random_bytes(&tipc_random, sizeof(tipc_random)); + tipc_udp_send_wq = alloc_workqueue("tipc_udp_send", 0, 0); + if (IS_ERR(tipc_udp_send_wq)) + return -ENOMEM; + err = tipc_sk_ref_table_init(tipc_max_ports, tipc_random); if (err) goto out_reftbl; diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c new file mode 100644 index 0000000..8ddb163 --- /dev/null +++ b/net/tipc/udp_media.c @@ -0,0 +1,503 @@ +/* net/tipc/udp_media.c: IP bearer support for TIPC + * + * Copyright (c) 2013, Ericsson AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/socket.h> +#include <linux/ip.h> +#include <linux/udp.h> +#include <linux/inet.h> +#include <linux/inetdevice.h> +#include <linux/kernel.h> +#include <linux/workqueue.h> +#include <linux/list.h> +#include <net/sock.h> +#include "core.h" +#include "bearer.h" + +/* IANA assigned UDP port */ +#define UDP_PORT_DEFAULT 6118 + +/** + * struct udp_skb_parms - bearer representation of a packet + * @dst: the remote bearer address + * @skb: tipc packet payload + * @next: list pointer + */ +struct udp_skb_parms { + struct sockaddr_in dst; + struct sk_buff *skb; + struct list_head next; +}; + +/** + * struct udp_bearer - ip/udp bearer data structure + * @txq_lock: transmit queue lock + * @txq: transmit queue + * @bearer: associated generic tipc bearer + * @listen: bearer listener socket + * @transmit: transmit socket + * @next: list pointer + * @work: used to schedule deferred work on a bearer + * @tx_work: used to defer packet transmission to socket + */ +struct udp_bearer { + spinlock_t txq_lock; + struct list_head txq; + struct tipc_bearer __rcu *bearer; + struct socket *listen; + struct socket *transmit; + struct work_struct work; + struct work_struct tx_work; +}; + +struct workqueue_struct *tipc_udp_send_wq = NULL; + +/* udp_media_addr_set - convert a ip/udp address to a TIPC media address */ +static void tipc_udp_media_addr_set(struct tipc_media_addr *addr, + struct sockaddr_in *sin) +{ + memset(addr, 0, sizeof(struct tipc_media_addr)); + addr->media_id = TIPC_MEDIA_TYPE_UDP; + memcpy(addr->value, sin, sizeof(struct sockaddr_in)); + if (ipv4_is_multicast(sin->sin_addr.s_addr)) + addr->broadcast = 1; +} + +/* tipc_udp_addr2str - convert ip/udp address to string */ +static int tipc_udp_addr2str(struct tipc_media_addr *a, char *buf, int size) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)&a->value; + + snprintf(buf, size, "%pI4:%u", &sin->sin_addr, + htons(sin->sin_port)); + return 0; +} + +/* tipc_udp_msg2addr - extract an ip/udp address from a TIPC ndisc message */ +static int tipc_udp_msg2addr(struct tipc_bearer *b, struct tipc_media_addr *a, + char *msg) +{ + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)(msg + TIPC_MEDIA_ADDR_OFFSET); + if (msg[TIPC_MEDIA_TYPE_OFFSET] != TIPC_MEDIA_TYPE_UDP) + return -EINVAL; + tipc_udp_media_addr_set(a, sin); + return 0; +} + +/* tipc_udp_addr2msg - write an ip/udp address to a TIPC ndisc message */ +static int tipc_udp_addr2msg(char *msg, struct tipc_media_addr *a) +{ + memset(msg, 0, TIPC_MEDIA_INFO_SIZE); + msg[TIPC_MEDIA_TYPE_OFFSET] = TIPC_MEDIA_TYPE_UDP; + memcpy(msg + TIPC_MEDIA_ADDR_OFFSET, &a->value, + sizeof(struct sockaddr_in)); + return 0; +} + +/* tipc_udp_send - send a TIPC buffer to the bearer socket */ +static void tipc_udp_send(struct work_struct *work) +{ + struct msghdr msg; + struct kvec iov; + struct sk_buff *skb; + struct udp_skb_parms *parms; + struct udp_bearer *ub; + int err; + + ub = container_of(work, struct udp_bearer, tx_work); + memset(&msg, 0, sizeof(struct msghdr)); + while (!list_empty(&ub->txq)) { + spin_lock_bh(&ub->txq_lock); + parms = list_first_entry(&ub->txq, struct udp_skb_parms, next); + list_del(&parms->next); + spin_unlock_bh(&ub->txq_lock); + skb = parms->skb; + iov.iov_base = skb->data; + iov.iov_len = skb->len; + msg.msg_iter.iov = (struct iovec *)&iov; + msg.msg_name = &parms->dst; + msg.msg_namelen = sizeof(struct sockaddr_in); + err = kernel_sendmsg(ub->transmit, &msg, &iov, 1, iov.iov_len); + /* no route to host, interface down or other underlying error */ + if (unlikely(err < 0)) + pr_warn_ratelimited("sendmsg failed with error: %d\n", + err); + kfree(parms); + consume_skb(skb); + cond_resched(); + } +} + +/** + * tipc_send_msg - enqueue a send request + * + * The send request need to be deferred since we cannot call kernel + * socket API functions while holding the node spinlock. + */ +static int tipc_udp_send_msg(struct sk_buff *skb, struct tipc_bearer *b, + struct tipc_media_addr *dest) +{ + struct udp_bearer *ub = b->media_ptr; + struct sk_buff *clone; + struct udp_skb_parms *parms; + + clone = skb_clone(skb, GFP_ATOMIC); + if (!clone) + return -ENOMEM; + parms = kmalloc(sizeof(*parms), GFP_ATOMIC); + if (!parms) { + kfree_skb(clone); + return -ENOMEM; + } + + /* Ndisc code uses temporary stack allocated media_addr + * so we must copy it before deferring + */ + memcpy(&parms->dst, dest, sizeof(struct sockaddr_in)); + parms->skb = clone; + spin_lock_bh(&ub->txq_lock); + list_add_tail(&parms->next, &ub->txq); + spin_unlock_bh(&ub->txq_lock); + queue_work(tipc_udp_send_wq, &ub->tx_work); + return 0; +} + +/* tipc_udp_recv - read data from bearer socket */ +static void tipc_udp_recv(struct sock *sk) +{ + struct sk_buff *skb; + struct udp_bearer *ub; + struct tipc_bearer *b; + int err; + + skb = skb_recv_datagram(sk, 0, MSG_DONTWAIT, &err); + if (err == -EAGAIN) + return; + read_lock(&sk->sk_callback_lock); + ub = sk->sk_user_data; + read_unlock(&sk->sk_callback_lock); + if (!ub) { + pr_err_ratelimited("failed to get UDP bearer reference"); + kfree_skb(skb); + return; + } + skb_pull(skb, sizeof(struct udphdr)); + skb->next = NULL; + skb_orphan(skb); + rcu_read_lock(); + b = rcu_dereference_rtnl(ub->bearer); + if (b) { + tipc_rcv(skb, b); + rcu_read_unlock(); + return; + } + rcu_read_unlock(); + kfree_skb(skb); +} + +/* init_mcast_bearer - set up igmp membership for a bearer */ +static int init_mcast_bearer(struct sockaddr_in *ifaddr, + struct udp_bearer *ub) +{ + struct ip_mreq mreq; + struct tipc_bearer *b; + struct sockaddr_in *bcast; + const int off = 0; + int err; + + b = ub->bearer; + bcast = (struct sockaddr_in *)&b->bcast_addr.value; + mreq.imr_multiaddr.s_addr = bcast->sin_addr.s_addr; + mreq.imr_interface.s_addr = ifaddr->sin_addr.s_addr; + /* Join the multicast group for our TIPC network. */ + err = kernel_setsockopt(ub->transmit, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *)&mreq, sizeof(mreq)); + if (err) { + pr_err("Failed to join multicast group\n"); + return err; + } + /* Turn off multicast loop to avoid getting our own ndisc messages */ + err = kernel_setsockopt(ub->transmit, IPPROTO_IP, IP_MULTICAST_LOOP, + (char *)&off, sizeof(off)); + if (err) { + pr_err("Failed to disable multicast loop\n"); + return err; + } + /* Assign the egress interface for tipc discovery mcast messages */ + err = kernel_setsockopt(ub->transmit, IPPROTO_IP, IP_MULTICAST_IF, + (char *)&mreq.imr_interface.s_addr, + sizeof(struct in_addr)); + if (err) { + pr_err("Failed to set egress multicast interface\n"); + return err; + } + err = kernel_bind(ub->transmit, (struct sockaddr *)bcast, + sizeof(struct sockaddr_in)); + if (err) + pr_err("Failed to bind multicast bearer socket\n"); + return err; +} + +/** + * setup_bearer - deferred udp bearer initialization + * @work: work struct holding the udp bearer pointer + * + * create and initialize the listen and transmit udp sockets + */ +static void setup_bearer(struct work_struct *work) +{ + struct net_device *dev; + struct sockaddr_in *listen; + struct sockaddr_in *bcast; + struct udp_bearer *ub; + struct tipc_bearer *b; + int err; + + ub = container_of(work, struct udp_bearer, work); + rcu_read_lock(); + b = rcu_dereference_rtnl(ub->bearer); + listen = (struct sockaddr_in *)&b->addr; + dev = __ip_dev_find(&init_net, listen->sin_addr.s_addr, false); + if (!dev) { + pr_err("Device lookup for %pI4 failed\n", + &listen->sin_addr.s_addr); + err = -ENODEV; + goto out; + } + b->mtu = dev->mtu - sizeof(struct iphdr) - sizeof(struct udphdr); + err = sock_create_kern(AF_INET, SOCK_DGRAM, 0, &ub->listen); + if (err) { + pr_err("Failed to create bearer listen socket"); + goto out; + } + err = kernel_bind(ub->listen, (struct sockaddr *)listen, + sizeof(struct sockaddr_in)); + if (err) { + pr_err("Failed to bind bearer listen socket"); + goto out; + } + if (sock_create_kern(AF_INET, SOCK_DGRAM, 0, &ub->transmit)) + goto out; + bcast = (struct sockaddr_in *)&b->bcast_addr.value; + if (ipv4_is_multicast(bcast->sin_addr.s_addr)) { + err = init_mcast_bearer(listen, ub); + if (err) + goto out; + } + write_lock_bh(&ub->transmit->sk->sk_callback_lock); + ub->transmit->sk->sk_data_ready = tipc_udp_recv; + ub->transmit->sk->sk_user_data = ub; + write_unlock_bh(&ub->transmit->sk->sk_callback_lock); + + write_lock_bh(&ub->listen->sk->sk_callback_lock); + ub->listen->sk->sk_data_ready = tipc_udp_recv; + ub->listen->sk->sk_user_data = ub; + write_unlock_bh(&ub->listen->sk->sk_callback_lock); + + rcu_read_unlock(); + return; +out: + pr_err("UDP bearer setup failed (errno=%d)\n", err); + rtnl_lock(); + tipc_disable_bearer(b->name); + rtnl_unlock(); +} + +/** + * parse_options - parse udp bearer configuration + * @arg: bearer configuration string, including media name + * @local: output struct holding local ip/port + * @remote: output struct holding remote ip/port + */ +static int parse_options(char *arg, struct sockaddr_in *local, + struct sockaddr_in *remote) +{ + char *opt = NULL; + char str[TIPC_MAX_BEARER_NAME]; + char *tmp; + unsigned long port; + + local->sin_family = AF_INET; + remote->sin_family = AF_INET; + strlcpy(str, arg, TIPC_MAX_BEARER_NAME); + tmp = str; + /* Skip media name */ + opt = strsep(&tmp, ":"); + if (!opt) + return -EINVAL; + /* Get the local address (mandatory) */ + opt = strsep(&tmp, ":"); + if (!opt || + !in4_pton(opt, -1, (u8 *)&local->sin_addr.s_addr, 0, NULL)) { + pr_err("Invalid local address %s\n", opt); + return -EINVAL; + } + /* Get the local port (optional) */ + opt = strsep(&tmp, ":"); + if (opt) { + if (0 == kstrtoul(opt, 10, &port) && port > 0 && port < 65535) { + local->sin_port = htons(port); + } else { + pr_err("Invalid local port %s\n", opt); + return -EINVAL; + } + } else { + local->sin_port = htons(UDP_PORT_DEFAULT); + } + /* Get the discovery/peer address (optional) */ + opt = strsep(&tmp, ":"); + if (opt) { + if (!in4_pton(opt, -1, (u8 *)&remote->sin_addr.s_addr, + 0, NULL)) { + pr_err("Invalid discover/peer address %s\n", opt); + return -EINVAL; + } + } else { + /* Generate discovery address based on network ID */ + remote->sin_addr.s_addr = htonl((228 << 24) | + ((tipc_net_id >> 8) << 8) | + (tipc_net_id & 0xFF)); + } + /* Get the remote port (optional) */ + opt = strsep(&tmp, ":"); + if (opt) { + if (0 == kstrtoul(opt, 10, &port) && port > 0 && port < 65535) { + remote->sin_port = htons(port); + } else { + pr_err("Invalid remote port %s\n", opt); + return -EINVAL; + } + } else { + remote->sin_port = htons(UDP_PORT_DEFAULT); + } + return 0; +} + +/** + * tipc_udp_enable - callback to create a new udp bearer instance + * @b: pointer to generic tipc_bearer + * + * validate the bearer parameters and perform basic initialization of the + * udp_bearer, the kernel socket setup is deferred + */ +static int tipc_udp_enable(struct tipc_bearer *b) +{ + struct udp_bearer *ub; + struct sockaddr_in listen; + struct sockaddr_in *bcast; + + ub = kzalloc(sizeof(*ub), GFP_ATOMIC); + if (!ub) + return -ENOMEM; + + bcast = (struct sockaddr_in *)&b->bcast_addr.value; + memset(bcast, 0, sizeof(b->bcast_addr.value)); + memset(&listen, 0, sizeof(listen)); + if (parse_options(b->name, &listen, bcast) == -EINVAL) { + kfree(ub); + return -EINVAL; + } + b->bcast_addr.media_id = TIPC_MEDIA_TYPE_UDP; + b->bcast_addr.broadcast = 1; + INIT_LIST_HEAD(&ub->txq); + spin_lock_init(&ub->txq_lock); + INIT_WORK(&ub->tx_work, tipc_udp_send); + b->media_ptr = ub; + rcu_assign_pointer(ub->bearer, b); + tipc_udp_media_addr_set(&b->addr, &listen); + + INIT_WORK(&ub->work, setup_bearer); + schedule_work(&ub->work); + return 0; +} + +static void clean_bearer_txq(struct udp_bearer *ub) +{ + struct udp_skb_parms *parm, *safe; + + spin_lock_bh(&ub->txq_lock); + list_for_each_entry_safe(parm, safe, &ub->txq, next) { + list_del(&parm->next); + kfree_skb(parm->skb); + kfree(parm); + } + spin_unlock_bh(&ub->txq_lock); +} + +/* cleanup_bearer - break the socket/bearer association */ +static void cleanup_bearer(struct work_struct *work) +{ + struct udp_bearer *ub = container_of(work, struct udp_bearer, work); + + if (ub->listen) + sock_release(ub->listen); + if (ub->transmit) + sock_release(ub->transmit); + clean_bearer_txq(ub); + flush_work(&ub->tx_work); + kfree(ub); +} + +/* tipc_udp_disable - detach bearer from socket */ +static void tipc_udp_disable(struct tipc_bearer *b) +{ + struct udp_bearer *ub = b->media_ptr; + + if (ub->listen) + sock_set_flag(ub->listen->sk, SOCK_DEAD); + if (ub->transmit) + sock_set_flag(ub->transmit->sk, SOCK_DEAD); + b->media_ptr = NULL; + rcu_assign_pointer(ub->bearer, NULL); + INIT_WORK(&ub->work, cleanup_bearer); + schedule_work(&ub->work); +} + +struct tipc_media udp_media_info = { + .send_msg = tipc_udp_send_msg, + .enable_media = tipc_udp_enable, + .disable_media = tipc_udp_disable, + .addr2str = tipc_udp_addr2str, + .addr2msg = tipc_udp_addr2msg, + .msg2addr = tipc_udp_msg2addr, + .priority = TIPC_DEF_LINK_PRI, + .tolerance = TIPC_DEF_LINK_TOL, + .window = TIPC_DEF_LINK_WIN, + .type_id = TIPC_MEDIA_TYPE_UDP, + .hwaddr_len = 20, + .name = "udp" +}; -- 2.1.3 |