freewtp/src/ac/kmod/netlinkapp.c

438 lines
12 KiB
C

#include "config.h"
#include <linux/module.h>
#include <linux/version.h>
#include <linux/version.h>
#include <linux/socket.h>
#include <linux/rtnetlink.h>
#include <linux/netlink.h>
#include <net/genetlink.h>
#include <linux/rcupdate.h>
#include <linux/err.h>
#include <net/mac80211.h>
#include <linux/ieee80211.h>
#include "nlsmartcapwap.h"
#include "netlinkapp.h"
#include "capwap.h"
/* */
static u32 sc_netlink_usermodeid;
/* */
static int sc_netlink_pre_doit(struct genl_ops* ops, struct sk_buff* skb, struct genl_info* info) {
TRACEKMOD("### sc_netlink_pre_doit\n");
rtnl_lock();
return 0;
}
/* */
static void sc_netlink_post_doit(struct genl_ops* ops, struct sk_buff* skb, struct genl_info* info) {
TRACEKMOD("### sc_netlink_post_doit\n");
rtnl_unlock();
}
/* Netlink Family */
static struct genl_family sc_netlink_family = {
.id = GENL_ID_GENERATE,
.name = NLSMARTCAPWAP_GENL_NAME,
.hdrsize = 0,
.version = 1,
.maxattr = NLSMARTCAPWAP_ATTR_MAX,
.netnsok = true,
.pre_doit = sc_netlink_pre_doit,
.post_doit = sc_netlink_post_doit,
};
/* */
static int sc_netlink_bind(struct sk_buff* skb, struct genl_info* info) {
union capwap_addr sockaddr;
TRACEKMOD("### sc_netlink_bind\n");
/* Check Link */
if (sc_netlink_usermodeid != info->snd_portid) {
return -ENOLINK;
}
/* Get bind address */
if (!info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS] || (nla_len(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]) != sizeof(struct sockaddr_storage))) {
return -EINVAL;
}
memcpy(&sockaddr.ss, nla_data(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]), sizeof(struct sockaddr_storage));
if ((sockaddr.ss.ss_family != AF_INET) && (sockaddr.ss.ss_family != AF_INET6)) {
return -EINVAL;
}
/* Bind socket */
return sc_capwap_bind(&sockaddr);
}
/* */
static int sc_netlink_send_keepalive(struct sk_buff* skb, struct genl_info* info) {
int ret;
union capwap_addr sockaddr;
TRACEKMOD("### sc_netlink_send_keepalive\n");
/* Check Link */
if (sc_netlink_usermodeid != info->snd_portid) {
return -ENOLINK;
}
/* Check Session address */
if (!info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]) {
return -EINVAL;
}
/* */
memcpy(&sockaddr.ss, nla_data(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]), sizeof(struct sockaddr_storage));
if ((sockaddr.ss.ss_family != AF_INET) && (sockaddr.ss.ss_family != AF_INET6)) {
return -EINVAL;
}
/* Send keep-alive packet */
ret = sc_capwap_sendkeepalive(&sockaddr);
if (ret < 0) {
return ret;
}
return 0;
}
/* */
static int sc_netlink_send_data(struct sk_buff* skb, struct genl_info* info) {
int length;
struct sk_buff* skbdata;
union capwap_addr sockaddr;
struct sc_skb_capwap_cb* cb;
TRACEKMOD("### sc_netlink_send_data\n");
/* Check Link */
if (sc_netlink_usermodeid != info->snd_portid) {
return -ENOLINK;
} else if (!info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS] || !info->attrs[NLSMARTCAPWAP_ATTR_RADIOID] || !info->attrs[NLSMARTCAPWAP_ATTR_BINDING] || !info->attrs[NLSMARTCAPWAP_ATTR_DATA_FRAME]) {
return -EINVAL;
}
/* */
memcpy(&sockaddr.ss, nla_data(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]), sizeof(struct sockaddr_storage));
if ((sockaddr.ss.ss_family != AF_INET) && (sockaddr.ss.ss_family != AF_INET6)) {
return -EINVAL;
}
/* Create socket buffer */
length = nla_len(info->attrs[NLSMARTCAPWAP_ATTR_DATA_FRAME]);
skbdata = alloc_skb(length + CAPWAP_HEADER_MAX_LENGTH, GFP_KERNEL);
if (!skbdata) {
return -ENOMEM;
}
/* Reserve space for Capwap Header */
skb_reserve(skbdata, CAPWAP_HEADER_MAX_LENGTH);
/* Copy data into socket buffer */
memcpy(skb_put(skbdata, length), nla_data(info->attrs[NLSMARTCAPWAP_ATTR_DATA_FRAME]), length);
/* */
cb = CAPWAP_SKB_CB(skbdata);
cb->flags = SKB_CAPWAP_FLAG_FROM_USER_SPACE | SKB_CAPWAP_FLAG_PEERADDRESS | SKB_CAPWAP_FLAG_RADIOID | SKB_CAPWAP_FLAG_BINDING;
sc_addr_tolittle(&sockaddr, &cb->peeraddr);
cb->radioid = nla_get_u8(info->attrs[NLSMARTCAPWAP_ATTR_RADIOID]);
cb->binding = nla_get_u8(info->attrs[NLSMARTCAPWAP_ATTR_BINDING]);
/* */
sc_capwap_recvpacket(skbdata);
return 0;
}
/* */
static int sc_netlink_new_session(struct sk_buff* skb, struct genl_info* info) {
uint16_t mtu = DEFAULT_MTU;
TRACEKMOD("### sc_netlink_new_session\n");
/* Check Link */
if (sc_netlink_usermodeid != info->snd_portid) {
return -ENOLINK;
}
/* Check Session ID */
if (!info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID] || (nla_len(info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID]) != sizeof(struct sc_capwap_sessionid_element))) {
return -EINVAL;
}
/* Get MTU */
if (info->attrs[NLSMARTCAPWAP_ATTR_MTU]) {
mtu = nla_get_u16(info->attrs[NLSMARTCAPWAP_ATTR_MTU]);
if ((mtu < MIN_MTU) || (mtu > MAX_MTU)) {
return -EINVAL;
}
}
/* New session */
return sc_capwap_newsession((struct sc_capwap_sessionid_element*)nla_data(info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID]), mtu);
}
/* */
static int sc_netlink_delete_session(struct sk_buff* skb, struct genl_info* info) {
union capwap_addr sockaddr;
TRACEKMOD("### sc_netlink_delete_session\n");
/* Check Link */
if (sc_netlink_usermodeid != info->snd_portid) {
return -ENOLINK;
}
/* Check Session ID */
if (!info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID] || (nla_len(info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID]) != sizeof(struct sc_capwap_sessionid_element))) {
return -EINVAL;
}
/* Check Address */
if (info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS] && (nla_len(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]) == sizeof(struct sockaddr_storage))) {
memcpy(&sockaddr.ss, nla_data(info->attrs[NLSMARTCAPWAP_ATTR_ADDRESS]), sizeof(struct sockaddr_storage));
} else {
sockaddr.ss.ss_family = AF_UNSPEC;
}
/* Delete session */
return sc_capwap_deletesession(((sockaddr.ss.ss_family == AF_UNSPEC) ? NULL : &sockaddr), (struct sc_capwap_sessionid_element*)nla_data(info->attrs[NLSMARTCAPWAP_ATTR_SESSION_ID]));
}
/* */
static int sc_netlink_link(struct sk_buff* skb, struct genl_info* info) {
int ret = 0;
TRACEKMOD("### sc_netlink_link\n");
if (!info->attrs[NLSMARTCAPWAP_ATTR_HASH_SESSION_BITFIELD] || !info->attrs[NLSMARTCAPWAP_ATTR_SESSION_THREADS_COUNT]) {
TRACEKMOD("*** Invalid link argument\n");
return -EINVAL;
}
if (!sc_netlink_usermodeid) {
uint32_t hash = nla_get_u32(info->attrs[NLSMARTCAPWAP_ATTR_HASH_SESSION_BITFIELD]);
uint32_t threads = nla_get_u32(info->attrs[NLSMARTCAPWAP_ATTR_SESSION_THREADS_COUNT]);
if (!hash || !threads) {
TRACEKMOD("*** Invalid link argument: %u %u\n", hash, threads);
return -EINVAL;
}
/* Initialize library */
ret = sc_capwap_init(hash, threads);
if (!ret) {
sc_netlink_usermodeid = info->snd_portid;
/* Deny unload module */
try_module_get(THIS_MODULE);
}
} else if (sc_netlink_usermodeid == info->snd_portid) {
TRACEKMOD("*** Already link\n");
ret = -EALREADY;
} else {
TRACEKMOD("*** Busy kernel link\n");
ret = -EBUSY;
}
return ret;
}
/* */
static int sc_netlink_notify(struct notifier_block* nb, unsigned long state, void* _notify) {
struct netlink_notify* notify = (struct netlink_notify*)_notify;
/* */
if (state == NETLINK_URELEASE) {
rtnl_lock();
if (sc_netlink_usermodeid == notify->portid) {
/* Close capwap engine */
sc_capwap_close();
/* Allow unload module */
module_put(THIS_MODULE);
sc_netlink_usermodeid = 0;
}
rtnl_unlock();
}
return NOTIFY_DONE;
}
/* */
static const struct nla_policy sc_netlink_policy[NLSMARTCAPWAP_ATTR_MAX + 1] = {
[NLSMARTCAPWAP_ATTR_FLAGS] = { .type = NLA_U32 },
[NLSMARTCAPWAP_ATTR_SESSION_ID] = { .type = NLA_BINARY, .len = sizeof(struct sc_capwap_sessionid_element) },
[NLSMARTCAPWAP_ATTR_RADIOID] = { .type = NLA_U8 },
[NLSMARTCAPWAP_ATTR_BINDING] = { .type = NLA_U8 },
[NLSMARTCAPWAP_ATTR_ADDRESS] = { .type = NLA_BINARY, .len = sizeof(struct sockaddr_storage) },
[NLSMARTCAPWAP_ATTR_DATA_FRAME] = { .type = NLA_BINARY, .len = IEEE80211_MAX_DATA_LEN + CAPWAP_HEADER_MAX_LENGTH },
[NLSMARTCAPWAP_ATTR_MTU] = { .type = NLA_U16 },
[NLSMARTCAPWAP_ATTR_HASH_SESSION_BITFIELD] = { .type = NLA_U32 },
[NLSMARTCAPWAP_ATTR_SESSION_THREADS_COUNT] = { .type = NLA_U32 },
};
/* Netlink Ops */
static struct genl_ops sc_netlink_ops[] = {
{
.cmd = NLSMARTCAPWAP_CMD_LINK,
.doit = sc_netlink_link,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NLSMARTCAPWAP_CMD_BIND,
.doit = sc_netlink_bind,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NLSMARTCAPWAP_CMD_SEND_KEEPALIVE,
.doit = sc_netlink_send_keepalive,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NLSMARTCAPWAP_CMD_SEND_DATA,
.doit = sc_netlink_send_data,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NLSMARTCAPWAP_CMD_NEW_SESSION,
.doit = sc_netlink_new_session,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NLSMARTCAPWAP_CMD_DELETE_SESSION,
.doit = sc_netlink_delete_session,
.policy = sc_netlink_policy,
.flags = GENL_ADMIN_PERM,
},
};
/* Netlink notify */
static struct notifier_block sc_netlink_notifier = {
.notifier_call = sc_netlink_notify,
};
/* */
int sc_netlink_notify_recv_keepalive(const union capwap_addr* sockaddr, struct sc_capwap_sessionid_element* sessionid) {
void* msg;
struct sk_buff* sk_msg;
TRACEKMOD("### sc_netlink_notify_recv_keepalive\n");
/* Alloc message */
sk_msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (!sk_msg) {
return -ENOMEM;
}
/* Set command */
msg = genlmsg_put(sk_msg, 0, 0, &sc_netlink_family, 0, NLSMARTCAPWAP_CMD_RECV_KEEPALIVE);
if (!msg) {
goto error;
}
/* */
if (nla_put(sk_msg, NLSMARTCAPWAP_ATTR_ADDRESS, sizeof(struct sockaddr_storage), &sockaddr->ss) ||
nla_put(sk_msg, NLSMARTCAPWAP_ATTR_SESSION_ID, sizeof(struct sc_capwap_sessionid_element), sessionid)) {
goto error2;
}
/* Send message */
genlmsg_end(sk_msg, msg);
return genlmsg_unicast(&init_net, sk_msg, sc_netlink_usermodeid);
error2:
genlmsg_cancel(sk_msg, msg);
error:
nlmsg_free(sk_msg);
return -ENOMEM;
}
/* */
int sc_netlink_notify_recv_data(struct sc_capwap_sessionid_element* sessionid, uint8_t* packet, int length) {
void* msg;
struct sk_buff* sk_msg;
TRACEKMOD("### sc_netlink_notify_recv_data\n");
/* Alloc message */
sk_msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (!sk_msg) {
return -ENOMEM;
}
/* Set command */
msg = genlmsg_put(sk_msg, 0, 0, &sc_netlink_family, 0, NLSMARTCAPWAP_CMD_RECV_DATA);
if (!msg) {
goto error;
}
/* */
if (nla_put(sk_msg, NLSMARTCAPWAP_ATTR_SESSION_ID, sizeof(struct sc_capwap_sessionid_element), sessionid) ||
nla_put(sk_msg, NLSMARTCAPWAP_ATTR_DATA_FRAME, length, packet)) {
goto error2;
}
/* Send message */
genlmsg_end(sk_msg, msg);
return genlmsg_unicast(&init_net, sk_msg, sc_netlink_usermodeid);
error2:
genlmsg_cancel(sk_msg, msg);
error:
nlmsg_free(sk_msg);
return -ENOMEM;
}
/* */
int sc_netlink_init(void) {
int ret;
TRACEKMOD("### sc_netlink_init\n");
/* */
sc_netlink_usermodeid = 0;
/* Register netlink family */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
ret = genl_register_family_with_ops(&sc_netlink_family, sc_netlink_ops, sizeof(sc_netlink_ops) / sizeof(sc_netlink_ops[0]));
#else
ret = genl_register_family_with_ops(&sc_netlink_family, sc_netlink_ops);
#endif
if (ret) {
goto error;
}
/* Register netlink notifier */
ret = netlink_register_notifier(&sc_netlink_notifier);
if (ret) {
goto error2;
}
return 0;
error2:
genl_unregister_family(&sc_netlink_family);
error:
return ret;
}
/* */
void sc_netlink_exit(void) {
TRACEKMOD("### sc_netlink_exit\n");
netlink_unregister_notifier(&sc_netlink_notifier);
genl_unregister_family(&sc_netlink_family);
}