代碼位置:
kernel/net/netlink/genetlink.c
kernel/include/net/genetlink.h
GENL簡介
netlink僅支持32種協議類型,這在實際應用中可能並不足夠。因此產生了generic netlink(以下簡稱爲genl)。
generic netlink支持1024(前10個保留不用)個子協議號,彌補了netlink協議類型較少的缺陷。
1 架構及工作原理:
Generic Netlink是以自定義的genl_family爲基本單位的,每一個想使用genl通信的模塊都要自己創建一個genl_family然後註冊到genl模塊(總線)中,最多可以添加1024個。
用戶空間在使用genl時,可以將指定將數據發送給你掛載到genl上的family,family再做具體的處理
每個family在註冊時都被分配了唯一的family_id,用戶空間在發送數據時,將family_id寫入nlmsghdr.nlmsg_type字段,genl就可以對應的family。
genl中共分配了16個鏈表,每個鏈表都可以掛載自定義的family
static struct list_head family_ht[GENL_FAM_TAB_SIZE=16];
這16個鏈表中元素的總個數是1024.
結構體中重要參數意義如下:
- id: 註冊時指定,若爲0則有genl分配一個未佔用的id,id的範圍是10~1023,0~10保留未使用
id和鏈表數組family_ht[16]的對應關係首先對id取16的餘數 id&0x1111,然後根據餘數確定添加到哪一條鏈表中。這樣的方式,比單獨一條鏈表搜索時要節省時間,比建一個1024的數組要節省空間。看函數名字是genl_family_hash(),不知道hash表是不是這麼創建的。 - name: 一個字符串,當內核模塊和用戶空間採用同樣的name,纔可以建立通信
- ops_list:該family的操縱函數
- mcast_groups:多播組鏈表,可以往該family中再註冊多播組
- family_list:該family在genl模塊中的鏈表索引
struct genl_family {
unsigned int id;
unsigned int hdrsize;
char name[GENL_NAMSIZ];
unsigned int version;
unsigned int maxattr;
bool netnsok;
bool parallel_ops;
int (*pre_doit)(struct genl_ops *ops,
struct sk_buff *skb,
struct genl_info *info);
void (*post_doit)(struct genl_ops *ops,
struct sk_buff *skb,
struct genl_info *info);
struct nlattr ** attrbuf; /* private */
struct list_head ops_list; /* private */
struct list_head family_list; /* private */
struct list_head mcast_groups; /* private */
struct module *module;
};
genl_family 操作函數
cmd:是個整形,用戶空間發送消息時,首先定義好faimily的name,然後再定義好cmd,就可以調用相應的處理函數doit();
struct genl_ops {
u8 cmd;
u8 internal_flags;
unsigned int flags;
const struct nla_policy *policy;
int (*doit)(struct sk_buff *skb,
struct genl_info *info);
int (*dumpit)(struct sk_buff *skb,
struct netlink_callback *cb);
int (*done)(struct netlink_callback *cb);
struct list_head ops_list;
};
2 genl初始化過程
鏈表的初始化和消息接收函數
將創建的netlnik socket掛載到net_namespace(不懂這個東東)中
static int __init genl_init(void)
{
int i, err;
//初始化16條鏈表
for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
INIT_LIST_HEAD(&family_ht[i]);
//註冊一個自己用的family,共上層使用
err = genl_register_family_with_ops(&genl_ctrl, &genl_ctrl_ops, 1);
//真正的初始化函數,其中的genl_pernet_init()會創建netlink,定義接收函數genl_rcv()
err = register_pernet_subsys(&genl_pernet_ops);
genl_pernet_init()
{
struct netlink_kernel_cfg cfg = {
.input = genl_rcv, //接收函數
.flags = NL_CFG_F_NONROOT_RECV,
};
/* we'll bump the group number right afterwards */
net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);
}
//註冊到多播組中
err = genl_register_mc_group(&genl_ctrl, ¬ify_grp);
}
2.1 接收數據genl_rcv_msg
genl_rcv()接收到數據會直接調用genl_rcv_msg()
然後根據nlmsghdr中的family_id(type)找到對應的family,
再根據genlmsghdr中的cmd找到family中對應的ops,然後調用doit做相應的處理
static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
//nlmsghdr中的type應該和family的id一致,
//但是內核中genl註冊family時,id是自動非配的,那用戶空間發送的消息怎麼確認id,
family = genl_family_find_byid(nlh->nlmsg_type);
//根據nlh中定義的cmd類型決定
genl_family_rcv_msg(family, skb, nlh);
{
//在傳入的nlh的載荷中包含着geml的頭genlmsghdr,
struct genlmsghdr *hdr = nlmsg_data(nlh);
//genl 信息,裏面有netlnik head,genl head,user head等信息,最終會由用戶(nl80211)定義的ops處理
struct genl_info info;
//如果有family有體檢需要處理的,可以放在該處
err = family->pre_doit(ops, skb, &info);
//通過cmd找到ops,對傳入的數據進行處理
ops = genl_get_cmd(hdr->cmd, family);
//ops處理數據
err = ops->doit(skb, &info);
//family的後續處理
family->post_doit(ops, skb, &info);
}
}
3 內核中使用genl
3.1 genl family 註冊
生成family_id,並將其加載到(android M中已經更改此API)
static inline int genl_register_family_with_ops(struct genl_family *family, struct genl_ops *ops, size_t n_ops)
{
//將每個family按照id順序加載到genl的16條鏈表中
err = __genl_register_family(family);
//將genl_ops註冊到family的ops_list上
for (i = 0; i < n_ops; ++i, ++ops) {
err = genl_register_ops(family, ops);
}
}
3.2 genl 多播組註冊
family指向屬於該多播組的family,
id:是多播組id,由內核分配,在genlmsg_multicast()中使用
struct genl_multicast_group {
struct genl_family *family; /* private */
struct list_head list; /* private */
char name[GENL_NAMSIZ];
u32 id;
};
int genl_register_mc_group(struct genl_family *family, struct genl_multicast_group *grp)
{
....
//有一大堆算id的代碼,看不懂, 通過傳入的group的name計算出唯一的一個id,作爲netlink多播組的group id
....
grp->id = id;
set_bit(id, mc_groups);
list_add_tail(&grp->list, &family->mcast_groups);
grp->family = family;
}
3.3 發送單播數據
static inline int genlmsg_reply(struct sk_buff *skb, struct genl_info *info)
最終會調用netlnik api nlmsg_unicast()
3.4 發送多播數據
genlmsg_multicast_netns(struct net *net, struct sk_buff *skb, u32 portid, unsigned int group, gfp_t flags);
4 用戶空間使用genl
代碼位置:external/libnl
官方文檔 http://www.carisma.slowglass.com/~tgr/libnl/doc/core.html#_socket_structure_struct_nl_sock
netlink通信時同樣可以使用libnl中的api,之前看netlink那個實例的時候,就感覺代碼很亂,libnl中提供了比較精簡的api
4.1 生成socket
#include <netlink/socket.h>
struct nl_sock *nl_socket_alloc(void)
void nl_socket_free(struct nl_sock *sk)
//與kernel中的 genl模塊相連接
genl_connect(struct nl_sk *)
//獲取genl中要使用的family的id
int genl_ctrl_resolve(struct nl_sock *sk, const char *name)
{
msg = nlmsg_alloc();
//"nlctrl"是由kernel 的genl模塊住的一個family,目前只支持獲取genel中 family的id
genlmsg_put(msg, 0, 0, genl_ctrl_resolve(global->nl, "nlctrl"), 0, 0, CTRL_CMD_GETFAMILY, 0);
}
4.2 發送數據
struct nl_msg *msg = nlmsg_alloc();
//msg中填充family ops中定義的CMD
//wpa_s 封裝爲 static void * nl80211_cmd(struct wpa_driver_nl80211_data *drv, struct nl_msg *msg, int flags, uint8_t cmd)
//該函數在生成nlmsg時,在其中添加genlmsghdr(genl的頭)
void *genlmsg_put(struct nl_msg *msg, uint32_t pid, uint32_t seq, int family, int hdrlen, int flags, uint8_t cmd, uint8_t version)
示例:genlmsg_put(msg, 0, 0, family_id, 0, 0, cmd, 0);
//msg中放置不同類型的屬性 attribute,整形或一塊內存
NLA_PUT_U32(msg, attrtype, value);
NLA_PUT(msg, attrtype, attrlen, data)
4.3 發送單播
返回值爲發送的字符數,若錯誤返回負
int nl_send_auto_complete(struct nl_sock *sk, struct nl_msg *msg)
該函數最終調用socket API sendmsg(),函數內部會利用nl_msg中的信息生成 sendmsg 所需的msghdr結構體
4.4 接收多播
nl_socket_add_membership(struct nl_sock *, group_id);
5 GENL 流程圖
6 使用實例
只實現了用戶空間向內核中的family發送單播消息,
使用在ops對應的cmd doit函數中讀取上層傳送下來的attr內容
內核向用戶空間發送多播消息還沒有實現,因爲不知道如何拿到genl創建的sock,
6.1 內核空間
//genl test code by cuijiyue
#include <.h>
#define GENL_TEST_CMD 6
#define GENL_TEST_ATRR_TYPE 66
char *msg_send = "hello form genl kernel!";
static struct genl_family genl_test_fam = {
.id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
.name = "genl_test", /* have users key off the name instead */
.hdrsize = 0, /* no private header */
.version = 1, /* no particular meaning now */
.maxattr = NL80211_ATTR_MAX,
.netnsok = true,
};
//genl_info *info 由genl genl_rcv()時生成的
static int genl_test_cmd_process(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
struct nlmsghdr *nlh;
printk("%s: genl_info nlhdr: len:%u, pid%u, snd_portid:%u\n", "genl_test", info->nlhdr->nlmsg_len, info->nlhdr->nlmsg_pid, info->snd_portid);
printk("%s: genl_info genlgdr: cmd:%u\n", "genl_test", info->genlhdr->cmd);
//printk("%s: genl_info userhdr:%s\n", "genl_test", (char*)(info->userhdr));
if(info->attrs[GENL_TEST_ATRR_TYPE]) {
printk("%s: GENL_TEST_ATRR_TYPE data:%s\n", "genl_test", (char*)nla_data(info->attrs[GENL_TEST_ATRR_TYPE]));
}
msg = nlmsg_new(4096, GFP_KERNEL);
if (!msg)
return -ENOMEM;
nlh = nlmsg_put(msg, 0, 0, NLMSG_DONE, strlen(msg_send), 0);
strcpy(nlmsg_data(nlh), msg_send);
//use netlnik unicast發送消息到用戶空間
//return genlmsg_reply(msg, info);
//multi fail, cann't find this genl sock, it is in first_device
//genlmsg_multicast(&genl_test_fam, msg, 0, 0, 0);
//nlmsg_multicast();
genlmsg_multicast_allns(&genl_test_fam, msg, 0, 0, GFP_ATOMIC);
return 6666;
}
static struct genl_ops genl_test_ops[] = {
{
.cmd = GENL_TEST_CMD,
.doit = genl_test_cmd_process,
},
};
static struct genl_multicast_group genl_test_group[] = {
{.name = "genl_test_group",}
};
int __init genl_test_init(void)
{
int err;
//err = genl_register_family_with_ops(&genl_test_fam, genl_test_ops, ARRAY_SIZE(genl_test_ops));
//in android M has change this api usage
err = genl_register_family_with_ops_groups(&genl_test_fam, genl_test_ops, genl_test_group);
if (err)
goto err_out;
printk("%s: genl_test_fam id:%u, mcgrp_offset:%u\n", "genl_test", genl_test_fam.id, genl_test_fam.mcgrp_offset);
//err = genl_register_mc_group(&genl_test_fam, &genl_test_group);
//if (err)
// goto err_out;
//printk("%s: genl_test_group id:%u\n", "genl_test", genl_test_group.id);
return 0;
err_out:
genl_unregister_family(&genl_test_fam);
return err;
}
module_init(genl_test_init);
void __exit genl_test_exit(void)
{
genl_unregister_family(&genl_test_fam);
}
module_exit(genl_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cuijiyue");
6.2 用戶空間
#include <.h>
#define MAX_PAYLOAD 1024 /* maximum payload size*/
#define GENL_TEST_CMD 6
#define GENL_TEST_ATRR_TYPE 66
char *attr_data = "66666";
struct nl_sock *nl_sk;
int main(int argc, char* argv[])
{
int ret;
int family_id, group_id;
struct nl_msg *msg;
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
struct msghdr msg_rev;
memset(&msg, 0, sizeof(msg));
//創建sock並連接到kernel
nl_sk = nl_socket_alloc();
ret = genl_connect(nl_sk);
printf("self pid:%u\n", getpid());
if (ret != 0) {
printf("genl_connect error%s\n", ret);
goto err_out;
}
//獲取自定義的family的id
family_id = genl_ctrl_resolve(nl_sk, "genl_test");
printf("genl_test family id:%d\n", ret);
// multi group, get it from kernel log group 9
nl_socket_add_membership(nl_sk, 9);
//生成msg
msg = nlmsg_alloc();
genlmsg_put(msg, 0, 0, family_id, 0, 0, GENL_TEST_CMD, 0);
//放置attr
nla_put(msg, GENL_TEST_ATRR_TYPE, strlen(attr_data), attr_data);
//發送消息
ret = nl_send_auto_complete(nl_sk, msg);
printf("nl_send ret:%d\n", ret);
//接收multi消息
memset(&dest_addr, 0, sizeof(dest_addr));
nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg_rev.msg_name = (void *)&dest_addr;
msg_rev.msg_namelen = sizeof(dest_addr);
msg_rev.msg_iov = &iov;
msg_rev.msg_iovlen = 1;
printf("waiting rev\n");
recvmsg(nl_sk, &msg_rev, 0);
printf(" Received message pid:%d, payload: %s\n", nlh->nlmsg_pid, NLMSG_DATA(nlh));
err_out:
nl_socket_free(nl_sk);
return ret;
}
7 wpa_supplicant與內核的通信
7.1 內核中nl80211模塊
nl80211直接使用的是Generic Netlink接口,添加了一個name爲“nl80211”的family和對應的 ops方法,同時註冊了多個多播組
代碼位置
kernel/net/wireless/nl80211.c
kernel/include/uapi/linux/nl80211.h
nl80211添加到genl中的family
static struct genl_family nl80211_fam = {
.id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */
.name = "nl80211", /* have users key off the name instead */
.hdrsize = 0, /* no private header */
.version = 1, /* no particular meaning now */
.maxattr = NL80211_ATTR_MAX,
.netnsok = true,
.pre_doit = nl80211_pre_doit,
.post_doit = nl80211_post_doit,
};
static struct genl_ops nl80211_ops[]
是nl80211可以接受到的命令,每種命令都有相應的nl80211處理函數
wifi的scan,associate,connect等等,都是由此處理的
太多了,去看源碼吧
7.2 wpa_supplicant中的genl socekt
wpa_s中與nl80211的通信時創建了兩條socket,一條用於發送消息到內核,一條接收內核中的多播消息,
scoekt的初始化源碼在wpa_driver_nl80211_init_nl_global()中
7.2.1 發送消息的socekt:
global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
global->nl = nl_create_handle(global->nl_cb, "nl");
在nl80211中,每接收到一條命令,都會通過socket發送回相應的處理結果
其中nl_cb可以自定義函數處理內核返回的消息。
在global->nl發送命令前,通過以下函數設置自定義的結果處理函數。
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, valid_data);
7.2.2接收內核多播消息的socekt:
global->nl_event = nl_create_handle(global->nl_cb, "event");
獲取nl80211中各個多播組的group_id,並添加socekt到該多播組
ret = nl_get_multicast_id(global, "nl80211", "scan");
ret = nl_socket_add_membership(global->nl_event, ret);
下一篇 wpa_supplicant源碼分析–掃描連接過程