ovs源碼閱讀--netlink使用

netlink

netlink socket是一種用於用戶態進程和內核態進程之間的通訊機制。它經過爲內核模塊提供一組特殊的API,併爲用戶程序提供了一組標準的socket接口的方式,實現了全雙工的通信鏈接。segmentfault

特色:架構

  • 雙向傳輸,異步通訊
  • 用戶空間中使用標準socket API
  • 內核空間中使用專門的API
  • 支持多播
  • 可由內核端發起通訊
  • 支持32種協議類型

netlink僅支持32種協議類型,這在實際應用中可能並不足夠,所以產生了generic netlink(如下簡稱爲genl),
generic netlink支持1023個子協議號,彌補了netlink協議類型較少的缺陷。異步

netlink通訊架構

圖片描述

Netlink子系統:全部genl通訊的基礎,Netlink子系統中收到的全部Generic類型的netlink數據都被送到genl總線上;從內核發出的數據也經由genl總線送至netlink子系統,再打包送至用戶空間 socket

Generic Netlink控制器:做爲內核的一部分,負責動態地分配genl通道(即genl family id),並管理genl任務,genl控制器是一個特殊的genl內核用戶,它負責監聽genl bus上的通訊通道函數

genl通訊創建在一系列的通訊通道的基礎上,每一個genl family對應多個通道,這些通道由genl控制器動態分配ui

相關結構體

圖片描述

genl family
Generic Netlink是基於客戶端-服務端模型的通訊機制,服務端註冊family(family是對genl服務的各項定義的集合),控制器和客戶端都經過已註冊的信息與服務端通訊。spa

//genl_family主要字段
struct genl_family
{
      unsigned int        id;    //family id
      unsigned int      hdrsize;  //用戶自定議頭部長度
      char              name[GENL_NAMSIZ]; //family名,要求不一樣的family使用不一樣的名字
      unsigned int      version;    //版本
      unsigned int      maxattr;    //最大attr類型數,使用netlink標準的attr來傳輸數據
      genl_ops             *ops;        // 操做集合
};

genl_ops
定義了netlink family相關的操做.net

// genl_ops主要字段
struct genl_ops
{
      u8                 cmd;    //命令名,用於識別genl_ops
      unsigned int       flags;    //設置屬性
      struct nla_policy  *policy; //定義了attr規則,genl在觸發事件處理程序以前,會用其進行attr校驗
      int                (*doit)(struct sk_buff *skb, struct genl_info *info);
      int                (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
};
  • doit:回調函數,在generic netlink收到數據時觸發,運行在進程上下文
  • dumpit:回調函數,當genl_ops的flag標誌被添加了NLM_F_DUMP之後,每次收到genl消息即會回觸發這個函數

dumpit與doit的區別是:dumpit的第一個參數skb不會攜帶從客戶端發來的數據。相反地,開發者應該在skb中填入須要傳給客戶端的數據,skb中攜帶的數據會被自動送到客戶端。只要dumpit的返回值大於0,dumpit函數就會再次被調用,並被要求在skb中填入數據。當服務端沒有數據要傳給客戶端時,dumpit要返回0。若是函數中出錯,要求返回一個負值。指針

nal_policy
定義了attr規則code

struct nla_policy
{
    u16     type;    //attr中的數據類型
    u16     len;    //若是在type字段配置的是字符串有關的值,要把len設置爲字符串的最大長度
};

genl_info
內核在接收到用戶的genetlink消息後,會對消息解析並封裝成genl_info結構

struct genl_info
{
    u32                     snd_seq;  //發送序號  
    u32                     snd_pid;  //發送客戶端的PID
    struct nlmsghdr *       nlhdr;      //netlink header的指針
    struct genlmsghdr *     genlhdr;  //genl頭部的指針(即family頭部)
    void *                  userhdr;  //用戶自定義頭部指針  
    struct nlattr **        attrs;    //若是定義了genl_ops->policy,保存被policy過濾之後的結果
};

Generic Netlink服務端(內核)初始化

這裏以OVS中packet的處理爲例:

1. 定義family

//定義packet family
static struct genl_family dp_packet_genl_family __ro_after_init = {
    .hdrsize = sizeof(struct ovs_header), 
    .name = OVS_PACKET_FAMILY,  
    .version = OVS_PACKET_VERSION, 
    .maxattr = OVS_PACKET_ATTR_MAX, 
    .netnsok = true,
    .parallel_ops = true,
    .ops = dp_packet_genl_ops,      //操做集合
    .n_ops = ARRAY_SIZE(dp_packet_genl_ops),
    .module = THIS_MODULE,
};

2. 定義operation

// 定義packet family 的操做 --- packet類型的操做只支持OVS_PACKET_CMD_EXECUTE
static struct genl_ops dp_packet_genl_ops[] = {
    { .cmd = OVS_PACKET_CMD_EXECUTE, 
      .flags = GENL_UNS_ADMIN_PERM, 
      .policy = packet_policy,  
      .doit = ovs_packet_cmd_execute    //接受數據包時,調用ovs_packet_cmd_execute進行處理
    }
};

// 定義packet family 的過濾規則
static const struct nla_policy packet_policy[OVS_PACKET_ATTR_MAX + 1] = {
    [OVS_PACKET_ATTR_PACKET] = { .len = ETH_HLEN },
    [OVS_PACKET_ATTR_KEY] = { .type = NLA_NESTED },
    [OVS_PACKET_ATTR_ACTIONS] = { .type = NLA_NESTED },
    [OVS_PACKET_ATTR_PROBE] = { .type = NLA_FLAG },
    [OVS_PACKET_ATTR_MRU] = { .type = NLA_U16 },
};

3. 註冊family

genl_register_family(&dp_packet_genl_family);

Generic Netlink客戶端(用戶空間)初始化

struct sockaddr_nl saddr;    
int                sock;
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); //建立一個netlink類型的socket

if (sock < 0) {
    return -1;
}

memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pid = getpid();    //獲取family id
if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {    //綁定
    printf("bind fail!\n");
    close(*p_sock);
    return -1;
}

內核空間接受發送數據

接受數據:內核端一旦收到generic netlink數據,會觸發doit函數運行,經過回調函數進行處理
發送數據:將數據打包好以後,可經過單播(genlmsg_unicast)或多播()的形式進行發送

用戶空間接受發送數據

接受數據:調用recv函數便可完成從內核來的數據的接收
發送數據:調用sendto來發送數據

netlink收發數據—以ovs中packet爲例

圖片描述

參考內容

GenerRic Netlink 詳解

做者: yearsj
轉載請註明出處: https://segmentfault.com/a/11...
相關文章
相關標籤/搜索