Linux內核中netlink協議族的實現(上)


Linux內核中netlink協議族的實現(上)

本文檔的Copyleft歸yfydz所有,使用GPL發佈,可以自由拷貝,轉載,轉載時請保持文檔的完整性,嚴禁用於任何商業用途。
msn: [email protected]
來源:http://yfydz.cublog.cn

1. 前言

netlink協議族是Linux內核網絡部分的一個固定部分, 一旦在內核配置中選了網絡支持就自動帶了而不能單獨去掉。
netlink的實現源碼在net/netlink目錄下,主要是net/netlink/af_netlink.c文件。

以下內核代碼版本爲2.6.19.2, 如無特別說明代碼取自net/netlink/af_netlink.c。

2. 數據結構

netlink套接口結構:
/* net/netlink/af_netlink.c */
struct netlink_sock {
/* struct sock has to be the first member of netlink_sock */
struct sock  sk;
u32  pid; // 自己的pid, 通常是0
u32  dst_pid; // 對方的pid
u32  dst_group; // 對方的組
u32  flags; 
u32  subscriptions;
u32  ngroups; // 多播組數量
unsigned long  *groups; // 多播組號
unsigned long  state;
wait_queue_head_t wait; // 等待隊列,用於處理接收發送包時的top half
struct netlink_callback *cb;  // 回調結構,包含回調函數
spinlock_t  cb_lock;
void  (*data_ready)(struct sock *sk, int bytes); // 數據到達時
                                //的操作, netlink可有不同類型, 如ROUTE, FIREWALL, ARPD等,                                  //每種類型都自己定義的data_ready處理
struct module  *module;
};
這個結構先是包含一個標準的struct sock結構,後面又跟和netlink相關的特有相關數據,內核中其他協議的sock也是類似定義的, 注意sock結構必須放在第一位,這是爲了可以直接將sock的指針轉爲netlink_sock的指針。

netlink sock的表:
struct netlink_table {
struct nl_pid_hash hash; // 根據pid進行HASH的netlink sock鏈表, 相當於客戶端鏈表
struct hlist_head mc_list; // 多播的sock鏈表
unsigned long *listeners;  // 監聽者標誌
unsigned int nl_nonroot;
unsigned int groups; // 每個netlink的協議類型可以定義多個組, 8的倍數,最小是32
struct module *module;
int registered;
};
最大可有MAX_LINKS(32)個表,處理不同協議類型的netlink套接口, 注意由於是自身的通信, 本機同時作爲服務器和客戶端, 服務端需要一個套接口對應, 每個客戶端也要有一個套接口對應, 多個客戶端的套接口形成一個鏈表.
struct nl_pid_hash {
struct hlist_head *table; // 鏈表節點
unsigned long rehash_time; // 重新計算HASH的時間間隔
unsigned int mask; 
unsigned int shift;
unsigned int entries;  // 鏈表節點數
unsigned int max_shift; // 最大冪值
u32 rnd; // 隨機數
};
其他和netlink數據相關的數據結構在include/linux/netlink.h中定義, 不過這些結構更多用在各具體的netlink對象的實現中, 在基本netlink套接口中到是用得不多。

3. af_netlink協議初始化

static int __init netlink_proto_init(void)
{
struct sk_buff *dummy_skb;
int i;
unsigned long max;
unsigned int order;
// 登記netlink_proto結構, 該結構定義如下:
// static struct proto netlink_proto = {
//  .name  = "NETLINK",
//  .owner  = THIS_MODULE,
//  .obj_size = sizeof(struct netlink_sock),
// };
// 最後一個參數爲0, 表示不進行slab的分配, 只是簡單的將netlink_proto結構
// 掛接到系統的網絡協議鏈表中,這個結構最主要是告知了netlink sock結構的大小
int err = proto_register(&netlink_proto, 0);
if (err != 0)
  goto out;
BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > sizeof(dummy_skb->cb));
// 分配MAX_LINKS個netlink表結構
nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
if (!nl_table)
  goto panic;
// 以下根據系統內存大小計算最大鏈表元素個數
// PAGE_SHIFT是每頁大小的2的冪,對i386是12,即每頁是4K,2^12
// 對於128M內存的機器,max計算值是(128*1024) >> (21-12) = 256
// 對於64M內存的機器,max計算值是(64*1024) >> (23-12) = 32
if (num_physpages >= (128 * 1024))
  max = num_physpages >> (21 - PAGE_SHIFT);
else
  max = num_physpages >> (23 - PAGE_SHIFT);
// 根據max再和PAGE_SHIFT計算總內存空間相應的冪值order
order = get_bitmask_order(max) - 1 + PAGE_SHIFT;
// max是最大節點數
max = (1UL << order) / sizeof(struct hlist_head);
// order是max對於2的冪數
order = get_bitmask_order(max > UINT_MAX ? UINT_MAX : max) - 1;
for (i = 0; i < MAX_LINKS; i++) {
  struct nl_pid_hash *hash = &nl_table.hash;
// 爲netlink的每個協議類型分配HASH錶鏈表頭
  hash->table = nl_pid_hash_alloc(1 * sizeof(*hash->table));
  if (!hash->table) {
  while (i-- > 0)
    nl_pid_hash_free(nl_table.hash.table,
      1 * sizeof(*hash->table));
  kfree(nl_table);
  goto panic;
  }
// 初始化HASH表參數
  memset(hash->table, 0, 1 * sizeof(*hash->table));
// 最大冪數
  hash->max_shift = order;
  hash->shift = 0;
  hash->mask = 0;
  hash->rehash_time = jiffies;
}
// 登記netlink協議族的的操作結構
sock_register(&netlink_family_ops);
#ifdef CONFIG_PROC_FS
proc_net_fops_create("netlink", 0, &netlink_seq_fops);
#endif
/* The netlink device handler may be needed early. */ 
// 初始化路由netlink
rtnetlink_init();
out:
return err;
panic:
panic("netlink_init: Cannot allocate nl_table\n");
}
core_initcall(netlink_proto_init);


4. 建立netlink套接口

4.1  建立對應客戶端的套接口
// netlink協議族操作, 在用戶程序使用socket打開netlink類型的socket時調用,
// 相應的create函數在__sock_create(net/socket.c)函數中調用:
static struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
// 在用戶空間每次打開netlink socket時都會調用此函數: 
static int netlink_create(struct socket *sock, int protocol)
{
struct module *module = NULL;
struct netlink_sock *nlk;
unsigned int groups;
int err = 0;
// sock狀態初始化
sock->state = SS_UNCONNECTED;
// 對netlink sock的類型和協議(實際是netlink_family類型)限制檢查
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
  return -ESOCKTNOSUPPORT;
if (protocol<0 || protocol >= MAX_LINKS)
  return -EPROTONOSUPPORT;
netlink_lock_table();
#ifdef CONFIG_KMOD
// 如果相應的netlink協議是模塊又沒有加載的話先加載該模塊
if (!nl_table.registered) {
  netlink_unlock_table();
  request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
  netlink_lock_table();
}
#endif
if (nl_table.registered &&
    try_module_get(nl_table.module))
  module = nl_table.module;
// groups這個值在函數後面也沒見用上, 這句沒意義
groups = nl_table.groups;
netlink_unlock_table();
// 真正的建立netlink sock的函數
if ((err = __netlink_create(sock, protocol)) < 0)
  goto out_module;
nlk = nlk_sk(sock->sk);
nlk->module = module;
out:
return err;
out_module:
module_put(module);
goto out;
}

// 基本函數
static int __netlink_create(struct socket *sock, int protocol)
{
struct sock *sk;
struct netlink_sock *nlk;
// netlink sock的基本操作
sock->ops = &netlink_ops;
// 分配sock結構, 通過netlink_proto中的obj_size指出了netlink sock的大小
sk = sk_alloc(PF_NETLINK, GFP_KERNEL, &netlink_proto, 1);
if (!sk)
  return -ENOMEM;
// 初始化sock基本數據, 將sock和socket關聯起來
sock_init_data(sock, sk);
// 將普通sock轉爲netlink sock,實際只是重新定義的一下指針類型,指針本身值不變
nlk = nlk_sk(sk);
// 初始化sock的鎖
spin_lock_init(&nlk->cb_lock);
// 初始化等待隊列
init_waitqueue_head(&nlk->wait);
// sock的析構函數,釋放接收隊列中的skb數據包
sk->sk_destruct = netlink_sock_destruct;
sk->sk_protocol = protocol;
// 注意這裏沒有重新定義sk的sk_data_ready函數
// 在sock_init_data()函數中將sk_data_ready定義爲sock_def_readable()函數
return 0;
}

用戶空間使用socket(2)系統調用打開netlink類型的套接口時, 在內核中會調用sys_sock()函數, 然後是調用__sock_create()函數, 在其中調用netlink協議族的create()函數, 即netlink_create()函數.

4.2 建立服務器端的套接口

以前也介紹過另一個建立netlink sock的函數netlink_kernel_create, 一般是在netlink的各種協議類型模塊初始化時調用的, 而不是socket系統調用時調用的, 每個netlink協議初始化是隻調用一次, 建立一個內核中的netlink接口, 相當於服務器, 其中也調用了__netlink_create()函數:
/*
* We export these functions to other modules. They provide a 
* complete set of kernel non-blocking support for message
* queueing.
*/
struct sock *
netlink_kernel_create(int unit, unsigned int groups,
                      void (*input)(struct sock *sk, int len),
                      struct module *module)
{
struct socket *sock;
struct sock *sk;
struct netlink_sock *nlk;
unsigned long *listeners = NULL;
BUG_ON(!nl_table);
if (unit<0 || unit>=MAX_LINKS)
  return NULL;
// 這裏的lite表示只是簡單分配一個socket,沒有真正初始化
if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
  return NULL;
// 用這個lite sock再建立netlink sock
if (__netlink_create(sock, unit) < 0)
  goto out_sock_release;
if (groups < 32)
  groups = 32;
// listerns是個位圖對應groups中每個元素
listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
if (!listeners)
  goto out_sock_release;
sk = sock->sk;
// 重新定義了sk_data_ready函數
sk->sk_data_ready = netlink_data_ready;
// 這個是相應的各netlink協議數據處理函數
if (input)
  nlk_sk(sk)->data_ready = input;
if (netlink_insert(sk, 0))
  goto out_sock_release;
nlk = nlk_sk(sk);
nlk->flags |= NETLINK_KERNEL_SOCKET;
netlink_table_grab();
// 註冊到相應unit的netlink協議表中
nl_table.groups = groups;
nl_table.listeners = listeners;
nl_table.module = module;
// 該標誌表示該項被登記
nl_table.registered = 1;
netlink_table_ungrab();
return sk;
out_sock_release:
kfree(listeners);
sock_release(sock);
return NULL;
}

5. netlink套接口的操作

在__netlink_create函數中定義了netlink套接口的操作結構爲netlink_ops:
sock->ops = &netlink_ops;
該結構定義如下:
static const struct proto_ops netlink_ops = {
.family = PF_NETLINK,
.owner = THIS_MODULE,
.release = netlink_release,
.bind =  netlink_bind,
.connect = netlink_connect,
.socketpair = sock_no_socketpair, // 無定義
.accept = sock_no_accept, // 無定義
.getname = netlink_getname,
.poll =  datagram_poll,
.ioctl = sock_no_ioctl, // 無定義
.listen = sock_no_listen, // 無定義
.shutdown = sock_no_shutdown, // 無定義
.setsockopt = netlink_setsockopt,
.getsockopt = netlink_getsockopt,
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
.mmap =  sock_no_mmap, // 無定義
.sendpage = sock_no_sendpage, // 無定義
};

5.1 釋放
在close(2)時調用
static int netlink_release(struct socket *sock)
{
struct sock *sk = sock->sk;
struct netlink_sock *nlk;
if (!sk)
  return 0;
// 將套接口sk從系統sk鏈表和綁定鏈表中斷開
netlink_remove(sk);
nlk = nlk_sk(sk);
spin_lock(&nlk->cb_lock);
if (nlk->cb) {
// 釋放netlink控制塊處理
  if (nlk->cb->done)
  nlk->cb->done(nlk->cb);
  netlink_destroy_callback(nlk->cb);
  nlk->cb = NULL;
}
spin_unlock(&nlk->cb_lock);
/* OK. Socket is unlinked, and, therefore,
    no new packets will arrive */
// 設置sk狀態爲SOCK_DEAD, 斷開sock和sk的互指
sock_orphan(sk);
sock->sk = NULL;
// 喚醒所有等待隊列
wake_up_interruptible_all(&nlk->wait);
// 清空寫隊列
skb_queue_purge(&sk->sk_write_queue);
if (nlk->pid && !nlk->subscriptions) {
// 發送釋放通知
  struct netlink_notify n = {
      .protocol = sk->sk_protocol,
      .pid = nlk->pid,
      };
  atomic_notifier_call_chain(&netlink_chain,
    NETLINK_URELEASE, &n);

// 減少模塊計數
if (nlk->module)
  module_put(nlk->module);
// 相當於加鎖
netlink_table_grab();
if (nlk->flags & NETLINK_KERNEL_SOCKET) {
// 釋放內核中的netlink服務器端
  kfree(nl_table[sk->sk_protocol].listeners);
  nl_table[sk->sk_protocol].module = NULL;
  nl_table[sk->sk_protocol].registered = 0;
} else if (nlk->subscriptions)
  netlink_update_listeners(sk);
// 相當於解鎖
netlink_table_ungrab();
// 釋放該netlink sock的多播組
kfree(nlk->groups);
nlk->groups = NULL;
// 釋放sock
sock_put(sk);
return 0;
}

5.2 綁定bind
綁定通常是針對服務端
static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
{
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
int err;
// 檢查一下地址的協議族是否爲AF_NETLINK 
if (nladdr->nl_family != AF_NETLINK)
  return -EINVAL;
/* Only superuser is allowed to listen multicasts */
if (nladdr->nl_groups) {
// 指定了多播組, 這是需要root權限
  if (!netlink_capable(sock, NL_NONROOT_RECV))
  return -EPERM;
  if (nlk->groups == NULL) {
// 分配多播組空間
  err = netlink_alloc_groups(sk);
  if (err)
    return err;
  }
}
if (nlk->pid) {
// 如果sock的pid非0, 檢查是否匹配在nladdr地址結構中指定的pid
  if (nladdr->nl_pid != nlk->pid)
  return -EINVAL;
} else {
// sock的pid爲0, 根據nladdr是否指定pid來執行插入或
  err = nladdr->nl_pid ?
  netlink_insert(sk, nladdr->nl_pid) :
  netlink_autobind(sock);
  if (err)
  return err;
}
// 非多播情況時就可以返回成功了
if (!nladdr->nl_groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
  return 0;
netlink_table_grab();
// 多播情況下更新sock參數
netlink_update_subscriptions(sk, nlk->subscriptions +
                                  hweight32(nladdr->nl_groups) -
                                  hweight32(nlk->groups[0]));
nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | nladdr->nl_groups; 
netlink_update_listeners(sk);
netlink_table_ungrab();
return 0;
}

// 根據pid插入
static int netlink_insert(struct sock *sk, u32 pid)
{
// netlink相應協議的HASH結構
struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
struct hlist_head *head;
// 缺省錯誤爲地址已經被使用
int err = -EADDRINUSE;
struct sock *osk;
struct hlist_node *node;
int len;
netlink_table_grab();
// 根據pid查找相應HASH鏈表頭
head = nl_pid_hashfn(hash, pid);
len = 0;
// 檢查pid是否已經在鏈表中, 有則失敗
sk_for_each(osk, node, head) {
  if (nlk_sk(osk)->pid == pid)
  break;
  len++;
}
if (node)
  goto err;
// 缺省錯誤改爲系統忙
err = -EBUSY;
// 如果sock的pid不爲0, 錯誤, 只有pid爲0的sock才能執行該函數
// sock的pid不爲0時不會再進行insert操作了
if (nlk_sk(sk)->pid)
  goto err;

// 缺省錯誤改爲無內存空間
err = -ENOMEM;
if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))
  goto err;
// 如果鏈表不爲空而且鏈表長度數量過長,會調整HASH表,重新獲取HASH鏈表頭
// 不過這種情況很少發生
if (len && nl_pid_hash_dilute(hash, len))
  head = nl_pid_hashfn(hash, pid);
hash->entries++;
// 將pid賦值給sock的pid參數
nlk_sk(sk)->pid = pid;
// 將sock節點添加進HASH鏈表
sk_add_node(sk, head);
err = 0;
err:
netlink_table_ungrab();
return err;
}

// 未指定pid時的自動綁定
// 實際是選一個沒用過的pid後再進行插入操作
static int netlink_autobind(struct socket *sock)
{
// 從socket找到sock
struct sock *sk = sock->sk;
// netlink相應協議的HASH結構
struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
struct hlist_head *head;
struct sock *osk;
struct hlist_node *node;
// pid取爲當前進程的組ID
s32 pid = current->tgid;
int err;
// 有符號32位數
static s32 rover = -4097;
retry:
cond_resched();
netlink_table_grab();
// 找合適的HASH鏈表頭
head = nl_pid_hashfn(hash, pid);
sk_for_each(osk, node, head) {
// 查找鏈表中是否已經有該pid
  if (nlk_sk(osk)->pid == pid) {
// 存在, 則更新pid, 重新檢查, 注意這時的pid是個負數
  /* Bind collision, search negative pid values. */
  pid = rover--;
  if (rover > -4097)
    rover = -4097;
  netlink_table_ungrab();
  goto retry;
  }
}
netlink_table_ungrab();
// 此時的pid是一個負數轉換爲無符號32位數, 將是一個非常大的數
// 執行正常的pid插入
err = netlink_insert(sk, pid);
if (err == -EADDRINUSE)
  goto retry;
/* If 2 threads race to autobind, that is fine.  */
if (err == -EBUSY)
  err = 0;
return err;
}
// 更新subscriotions
static void
netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions)
{
struct netlink_sock *nlk = nlk_sk(sk);
if (nlk->subscriptions && !subscriptions)
  __sk_del_bind_node(sk);
else if (!nlk->subscriptions && subscriptions)
  sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list);
nlk->subscriptions = subscriptions;
}
// 更新listeners
static void
netlink_update_listeners(struct sock *sk)
{
struct netlink_table *tbl = &nl_table[sk->sk_protocol];
struct hlist_node *node;
unsigned long mask;
unsigned int i;
for (i = 0; i < NLGRPSZ(tbl->groups)/sizeof(unsigned long); i++) {
  mask = 0;
// 遍歷多播鏈表生成多播組的掩碼
  sk_for_each_bound(sk, node, &tbl->mc_list)
  mask |= nlk_sk(sk)->groups;
  tbl->listeners = mask;
}
/* this function is only called with the netlink table "grabbed", which
  * makes sure updates are visible before bind or setsockopt return. */
}

發佈了7 篇原創文章 · 獲贊 11 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章