Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2010 Voltaire Inc. All rights reserved. |
| 3 | * |
| 4 | * This software is available to you under a choice of one of two |
| 5 | * licenses. You may choose to be licensed under the terms of the GNU |
| 6 | * General Public License (GPL) Version 2, available from the file |
| 7 | * COPYING in the main directory of this source tree, or the |
| 8 | * OpenIB.org BSD license below: |
| 9 | * |
| 10 | * Redistribution and use in source and binary forms, with or |
| 11 | * without modification, are permitted provided that the following |
| 12 | * conditions are met: |
| 13 | * |
| 14 | * - Redistributions of source code must retain the above |
| 15 | * copyright notice, this list of conditions and the following |
| 16 | * disclaimer. |
| 17 | * |
| 18 | * - Redistributions in binary form must reproduce the above |
| 19 | * copyright notice, this list of conditions and the following |
| 20 | * disclaimer in the documentation and/or other materials |
| 21 | * provided with the distribution. |
| 22 | * |
| 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 26 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| 27 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| 28 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| 29 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 30 | * SOFTWARE. |
| 31 | */ |
| 32 | |
| 33 | #define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ |
| 34 | |
Paul Gortmaker | b108d97 | 2011-05-27 15:29:33 -0400 | [diff] [blame] | 35 | #include <linux/export.h> |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 36 | #include <net/netlink.h> |
| 37 | #include <net/net_namespace.h> |
| 38 | #include <net/sock.h> |
| 39 | #include <rdma/rdma_netlink.h> |
| 40 | |
| 41 | struct ibnl_client { |
| 42 | struct list_head list; |
| 43 | int index; |
| 44 | int nops; |
| 45 | const struct ibnl_client_cbs *cb_table; |
| 46 | }; |
| 47 | |
| 48 | static DEFINE_MUTEX(ibnl_mutex); |
| 49 | static struct sock *nls; |
| 50 | static LIST_HEAD(client_list); |
| 51 | |
Kaike Wan | bc10ed7 | 2015-08-14 08:52:07 -0400 | [diff] [blame] | 52 | int ibnl_chk_listeners(unsigned int group) |
| 53 | { |
| 54 | if (netlink_has_listeners(nls, group) == 0) |
| 55 | return -1; |
| 56 | return 0; |
| 57 | } |
| 58 | EXPORT_SYMBOL(ibnl_chk_listeners); |
| 59 | |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 60 | int ibnl_add_client(int index, int nops, |
| 61 | const struct ibnl_client_cbs cb_table[]) |
| 62 | { |
| 63 | struct ibnl_client *cur; |
| 64 | struct ibnl_client *nl_client; |
| 65 | |
| 66 | nl_client = kmalloc(sizeof *nl_client, GFP_KERNEL); |
| 67 | if (!nl_client) |
| 68 | return -ENOMEM; |
| 69 | |
| 70 | nl_client->index = index; |
| 71 | nl_client->nops = nops; |
| 72 | nl_client->cb_table = cb_table; |
| 73 | |
| 74 | mutex_lock(&ibnl_mutex); |
| 75 | |
| 76 | list_for_each_entry(cur, &client_list, list) { |
| 77 | if (cur->index == index) { |
| 78 | pr_warn("Client for %d already exists\n", index); |
| 79 | mutex_unlock(&ibnl_mutex); |
| 80 | kfree(nl_client); |
| 81 | return -EINVAL; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | list_add_tail(&nl_client->list, &client_list); |
| 86 | |
| 87 | mutex_unlock(&ibnl_mutex); |
| 88 | |
| 89 | return 0; |
| 90 | } |
| 91 | EXPORT_SYMBOL(ibnl_add_client); |
| 92 | |
| 93 | int ibnl_remove_client(int index) |
| 94 | { |
| 95 | struct ibnl_client *cur, *next; |
| 96 | |
| 97 | mutex_lock(&ibnl_mutex); |
| 98 | list_for_each_entry_safe(cur, next, &client_list, list) { |
| 99 | if (cur->index == index) { |
| 100 | list_del(&(cur->list)); |
| 101 | mutex_unlock(&ibnl_mutex); |
| 102 | kfree(cur); |
| 103 | return 0; |
| 104 | } |
| 105 | } |
| 106 | pr_warn("Can't remove callback for client idx %d. Not found\n", index); |
| 107 | mutex_unlock(&ibnl_mutex); |
| 108 | |
| 109 | return -EINVAL; |
| 110 | } |
| 111 | EXPORT_SYMBOL(ibnl_remove_client); |
| 112 | |
| 113 | void *ibnl_put_msg(struct sk_buff *skb, struct nlmsghdr **nlh, int seq, |
Tatyana Nikolova | 30dc5e6 | 2014-03-26 17:07:35 -0500 | [diff] [blame] | 114 | int len, int client, int op, int flags) |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 115 | { |
| 116 | unsigned char *prev_tail; |
| 117 | |
| 118 | prev_tail = skb_tail_pointer(skb); |
David S. Miller | e052733 | 2012-06-26 21:43:19 -0700 | [diff] [blame] | 119 | *nlh = nlmsg_put(skb, 0, seq, RDMA_NL_GET_TYPE(client, op), |
Tatyana Nikolova | 30dc5e6 | 2014-03-26 17:07:35 -0500 | [diff] [blame] | 120 | len, flags); |
David S. Miller | e052733 | 2012-06-26 21:43:19 -0700 | [diff] [blame] | 121 | if (!*nlh) |
| 122 | goto out_nlmsg_trim; |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 123 | (*nlh)->nlmsg_len = skb_tail_pointer(skb) - prev_tail; |
David S. Miller | e052733 | 2012-06-26 21:43:19 -0700 | [diff] [blame] | 124 | return nlmsg_data(*nlh); |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 125 | |
David S. Miller | e052733 | 2012-06-26 21:43:19 -0700 | [diff] [blame] | 126 | out_nlmsg_trim: |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 127 | nlmsg_trim(skb, prev_tail); |
| 128 | return NULL; |
| 129 | } |
| 130 | EXPORT_SYMBOL(ibnl_put_msg); |
| 131 | |
| 132 | int ibnl_put_attr(struct sk_buff *skb, struct nlmsghdr *nlh, |
| 133 | int len, void *data, int type) |
| 134 | { |
| 135 | unsigned char *prev_tail; |
| 136 | |
| 137 | prev_tail = skb_tail_pointer(skb); |
David S. Miller | 4e24ffa | 2012-04-01 20:19:38 -0400 | [diff] [blame] | 138 | if (nla_put(skb, type, len, data)) |
| 139 | goto nla_put_failure; |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 140 | nlh->nlmsg_len += skb_tail_pointer(skb) - prev_tail; |
| 141 | return 0; |
| 142 | |
| 143 | nla_put_failure: |
| 144 | nlmsg_trim(skb, prev_tail - nlh->nlmsg_len); |
| 145 | return -EMSGSIZE; |
| 146 | } |
| 147 | EXPORT_SYMBOL(ibnl_put_attr); |
| 148 | |
| 149 | static int ibnl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) |
| 150 | { |
| 151 | struct ibnl_client *client; |
| 152 | int type = nlh->nlmsg_type; |
| 153 | int index = RDMA_NL_GET_CLIENT(type); |
| 154 | int op = RDMA_NL_GET_OP(type); |
| 155 | |
| 156 | list_for_each_entry(client, &client_list, list) { |
| 157 | if (client->index == index) { |
| 158 | if (op < 0 || op >= client->nops || |
Mathias Krause | 5476781 | 2013-09-30 22:03:54 +0200 | [diff] [blame] | 159 | !client->cb_table[op].dump) |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 160 | return -EINVAL; |
Pablo Neira Ayuso | 80d326f | 2012-02-24 14:30:15 +0000 | [diff] [blame] | 161 | |
Kaike Wan | bc10ed7 | 2015-08-14 08:52:07 -0400 | [diff] [blame] | 162 | /* |
| 163 | * For response or local service set_timeout request, |
| 164 | * there is no need to use netlink_dump_start. |
| 165 | */ |
| 166 | if (!(nlh->nlmsg_flags & NLM_F_REQUEST) || |
| 167 | (index == RDMA_NL_LS && |
| 168 | op == RDMA_NL_LS_OP_SET_TIMEOUT)) { |
| 169 | struct netlink_callback cb = { |
| 170 | .skb = skb, |
| 171 | .nlh = nlh, |
| 172 | .dump = client->cb_table[op].dump, |
| 173 | .module = client->cb_table[op].module, |
| 174 | }; |
| 175 | |
| 176 | return cb.dump(skb, &cb); |
| 177 | } |
| 178 | |
Pablo Neira Ayuso | 80d326f | 2012-02-24 14:30:15 +0000 | [diff] [blame] | 179 | { |
| 180 | struct netlink_dump_control c = { |
| 181 | .dump = client->cb_table[op].dump, |
Gao feng | 809d5fc | 2012-10-04 20:15:49 +0000 | [diff] [blame] | 182 | .module = client->cb_table[op].module, |
Pablo Neira Ayuso | 80d326f | 2012-02-24 14:30:15 +0000 | [diff] [blame] | 183 | }; |
| 184 | return netlink_dump_start(nls, skb, nlh, &c); |
| 185 | } |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 186 | } |
| 187 | } |
| 188 | |
| 189 | pr_info("Index %d wasn't found in client list\n", index); |
| 190 | return -EINVAL; |
| 191 | } |
| 192 | |
Kaike Wan | bc10ed7 | 2015-08-14 08:52:07 -0400 | [diff] [blame] | 193 | static void ibnl_rcv_reply_skb(struct sk_buff *skb) |
| 194 | { |
| 195 | struct nlmsghdr *nlh; |
| 196 | int msglen; |
| 197 | |
| 198 | /* |
| 199 | * Process responses until there is no more message or the first |
| 200 | * request. Generally speaking, it is not recommended to mix responses |
| 201 | * with requests. |
| 202 | */ |
| 203 | while (skb->len >= nlmsg_total_size(0)) { |
| 204 | nlh = nlmsg_hdr(skb); |
| 205 | |
| 206 | if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len) |
| 207 | return; |
| 208 | |
| 209 | /* Handle response only */ |
| 210 | if (nlh->nlmsg_flags & NLM_F_REQUEST) |
| 211 | return; |
| 212 | |
| 213 | ibnl_rcv_msg(skb, nlh); |
| 214 | |
| 215 | msglen = NLMSG_ALIGN(nlh->nlmsg_len); |
| 216 | if (msglen > skb->len) |
| 217 | msglen = skb->len; |
| 218 | skb_pull(skb, msglen); |
| 219 | } |
| 220 | } |
| 221 | |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 222 | static void ibnl_rcv(struct sk_buff *skb) |
| 223 | { |
| 224 | mutex_lock(&ibnl_mutex); |
Kaike Wan | bc10ed7 | 2015-08-14 08:52:07 -0400 | [diff] [blame] | 225 | ibnl_rcv_reply_skb(skb); |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 226 | netlink_rcv_skb(skb, &ibnl_rcv_msg); |
| 227 | mutex_unlock(&ibnl_mutex); |
| 228 | } |
| 229 | |
Tatyana Nikolova | 30dc5e6 | 2014-03-26 17:07:35 -0500 | [diff] [blame] | 230 | int ibnl_unicast(struct sk_buff *skb, struct nlmsghdr *nlh, |
| 231 | __u32 pid) |
| 232 | { |
| 233 | return nlmsg_unicast(nls, skb, pid); |
| 234 | } |
| 235 | EXPORT_SYMBOL(ibnl_unicast); |
| 236 | |
| 237 | int ibnl_multicast(struct sk_buff *skb, struct nlmsghdr *nlh, |
| 238 | unsigned int group, gfp_t flags) |
| 239 | { |
| 240 | return nlmsg_multicast(nls, skb, 0, group, flags); |
| 241 | } |
| 242 | EXPORT_SYMBOL(ibnl_multicast); |
| 243 | |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 244 | int __init ibnl_init(void) |
| 245 | { |
Pablo Neira Ayuso | a31f2d1 | 2012-06-29 06:15:21 +0000 | [diff] [blame] | 246 | struct netlink_kernel_cfg cfg = { |
| 247 | .input = ibnl_rcv, |
| 248 | }; |
| 249 | |
Pablo Neira Ayuso | 9f00d97 | 2012-09-08 02:53:54 +0000 | [diff] [blame] | 250 | nls = netlink_kernel_create(&init_net, NETLINK_RDMA, &cfg); |
Roland Dreier | b2cbae2 | 2011-05-20 11:46:11 -0700 | [diff] [blame] | 251 | if (!nls) { |
| 252 | pr_warn("Failed to create netlink socket\n"); |
| 253 | return -ENOMEM; |
| 254 | } |
| 255 | |
| 256 | return 0; |
| 257 | } |
| 258 | |
| 259 | void ibnl_cleanup(void) |
| 260 | { |
| 261 | struct ibnl_client *cur, *next; |
| 262 | |
| 263 | mutex_lock(&ibnl_mutex); |
| 264 | list_for_each_entry_safe(cur, next, &client_list, list) { |
| 265 | list_del(&(cur->list)); |
| 266 | kfree(cur); |
| 267 | } |
| 268 | mutex_unlock(&ibnl_mutex); |
| 269 | |
| 270 | netlink_kernel_release(nls); |
| 271 | } |