分享

netlink 套接字

 just_person 2019-09-10

参考文档:

https://www./article/7356  Linux Journey: Why and how to use Netlink socket

https://www./article/8498?page=0,1

 

用户空间与内核通信的几种方法:

* system call

* procfs(/proc文件系统)

* sysctl(/proc/sys目录)

* sysfs(/sys文件系统)

* ioctl 系统调用

* netlink套接字(RFC 3549) net/netlink目录

    - af_netlink.c

    - af_netlink.h

   - genetlink.c

   - diag.c

 

af_netlink 模块提供了内核态API

genetlink 模块提供了更新的 内核API, 更容易发送数据信息

diag 模块提供了netlink接口监控信息

 

 

一、用户态netlink API

用户态的netlink库有两个:

http://www./~tgr/libnl/    功能较全

http:///projects/libmnl/     mini的libnetlink 库

 

以下流程用来对网络环境进行修改:

step 1. 打开套接字

step 2. 在本地 bind 套接字

step 3. 发送消息到目的地

step 4. 在目的地接收消息

step 5. 关闭套接字

 

/* 添加头文件 */
#include <bits/sockaddr.h> #include <asm/types.h> #include <linux/rtnetlink.h> #include <sys/socket.h>

 

1. 创建socket

netlink套接字调用原型 include/linux/netlink.h 

#include <sys/socket.h>
#include <linux/netlink.h>

int socket(int domain, int type, int protocol);

第一个参数 doamin 表示什么样的套接字类型,使用RTNETLINK, 使用AF_NETLINK

第二个参数 type 表示什么方法 RAW 或 DGRAM ,对于RTNETLINK 都可以使用

第三个参数 protocol , 为了修改路由表,我们使用NETLINK_ROUTE

如果成功,返回一个正数整形

如果失败,返回一个负数

 

从用户态创建socket

/* 示例代码 */
int
sock_fd; sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

 


2. bind

int bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));

第一个参数是 socket创建的点

第二个参数 struct sockaddr* 是本地地址,结构如下:

数据结构 sockaddr_nl 表示一个netlink套接字地址 include/uapi/linux/netlink.h 

复制代码
struct sockaddr_nl {
    __kernel_sa_family_t    nl_family;     /* AF_NETLINK    */
    unsigned short    nl_pad;              /* zero        */
    __u32        nl_pid;                   /* port ID    */
    __u32        nl_groups;                /* multicast groups mask */
};
复制代码

nl_family 总是 AF_NETLINK

nl_pad 总是为0

nl_pid 

   当地址是内核套接字时,值为0

   用户空间程序会将nl_pid的值设为他们的进程ID

nl_groups: 组播值为0表示单播,值从0-31

 

bind 函数成功,返回0,失败返回负数

复制代码
/* 示例代码 */
int rtn; struct sockaddr_nl la; ... bzero(&la, sizeof(la)); la.nl_family = AF_NETLINK; la.nl_pad = 0; la.nl_pid = getpid(); la.nl_groups = 0; rtn = bind(fd, (struct sockaddr*) &la, sizeof(la));
复制代码

 

libnetlink中代码: libnetlink.c 中rtnl_open函数解析

复制代码
#include <asm/types.h>
#include <libnetlink.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

// 打开rtnetlink接口,将handle信息保存在rth结构中 int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
成功返回0,失败返回-1
复制代码

 

 

复制代码
// 定义在头文件 libnetlink.h 
struct rtnl_handle {
    int            fd;    // 套接字描述符
    struct sockaddr_nl    local;  // 本地地址
    struct sockaddr_nl    peer;   // 目的地址
    __u32            seq;         // 序列号
    __u32            dump;        
    int                proto;     // 协议
    FILE            *dump_fp;
    int            flags;         // 标志字段
};
复制代码

 

 

复制代码
// libnetlink.c
// 成功返回0, 失败返回-1 int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) { return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); }
复制代码

 

 

复制代码
// libnetlink.c 头文件中rtnl_open_byproto函数
// 成功返回0, 失败返回-1
int
rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, int protocol) { socklen_t addr_len; int sndbuf = 32768; int one = 1;
   // 第一步:初值 memset(rth, 0, sizeof(*rth));
// 第二上:填充rth结构 rth->proto = protocol; // 协议为NETLINK_ROUTE

// 第三步:打开套接字接口 rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); // 调用socket函数 if (rth->fd < 0) { perror("Cannot open netlink socket"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { perror("SO_SNDBUF"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { perror("SO_RCVBUF"); return -1; } /* Older kernels may no support extended ACK reporting */ setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one));
// 将本地地址初始化为0 && 初值 memset(&rth->local, 0, sizeof(rth->local)); rth->local.nl_family = AF_NETLINK; rth->local.nl_groups = subscriptions;
  // 调用bind if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < 0) { perror("Cannot bind netlink socket"); return -1; } addr_len = sizeof(rth->local); if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { perror("Cannot getsockname"); return -1; } if (addr_len != sizeof(rth->local)) { fprintf(stderr, "Wrong address length %d\n", addr_len); return -1; } if (rth->local.nl_family != AF_NETLINK) { fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); return -1; }
// 序列号为当前时间 rth->seq = time(NULL); return 0; }
复制代码

 

 

 

3. 发送netlink消息

如果消息是发送给内核的,nl_pid 与 nl_groups 的值都为0

如果消息是单播消息发送给另一个进程的, nl_pid是另一个进程的pid值,nl_groups的值为0

如果消息是组播发送的,需要定义nl_groups的值。

ssize_t sendmsg(int fd, const struct msghdr *msg,
                                      int flags);

 

第一个参数 是socket创建点

第二个参数是需要发送消息的首地址,需要如下定义

复制代码
struct msghdr msg;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);

iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
 
msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1;
复制代码

 

复制代码
struct msghdr
{
  void *msg_name;        //Address to send to
  socklen_t msg_namelen; //Length of address data
  struct iovec *msg_iov; //Vector of data to send
  size_t msg_iovlen;     //Number of iovec entries
  void *msg_control;     //Ancillary data
  size_t msg_controllen; //Ancillary data buf len
  int msg_flags;         //Flags on received msg
};
复制代码

 

netlink消息头部 include/uapi/linux/netlink.h

复制代码
struct nlmsghdr {
    __u32        nlmsg_len;    /* Length of message including header */
    __u16        nlmsg_type;    /* Message content */
    __u16        nlmsg_flags;    /* Additional flags */
    __u32        nlmsg_seq;    /* Sequence number */
    __u32        nlmsg_pid;    /* Sending process port ID */
};
复制代码

 

复制代码
   0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          Length                             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |            Type              |           Flags              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                      Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                      Process ID (PID)                       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
复制代码

32位的nlmsg_len 表示整个netlink消息的数据包长度

16位的nlmsg_type是从应用定义

  NLMSG_NOOP  无任何操作,消息必须丢弃

  NLMSG_ERROR  错误发生了

  NLMSG_DONE   其他消息结束了

  NLMSG_OVERRUN  错误,数据丢失了

 

 

4. 接收消息

接收的应用必须分配足够的内存空间来获取消息头部与payloads.

ssize_t recv(int fd, void *buf, size_t len,
                                      int flags);

 

 

复制代码
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;

iov.iov_base = (void *)nlh;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);

msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);
复制代码

 

 

5. 关闭

int close(int fd);

 

libnetlink库中rtnl_close函数

// libnetlink.h 头文件
void
rtnl_close(struct rtnl_handle *rth);

 

 

复制代码
void rtnl_close(struct rtnl_handle *rth)
{
    if (rth->fd >= 0) {
        close(rth->fd);
        rth->fd = -1;
    }
}
复制代码

 

 

二、内核态netlink 接口

net/core/af_netlink.c

 

1. 创建socket接口

struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

第一个参数 net 表示网络命令空间

第二个参数 是netlink协议如, uapi/linux/netlink.h

  NETLINK_ROUTE

  NETLINK_XFRM

  NETLINK_AUDIT

  ...

第三个参数 是可选项 include/linux/netlink.h

复制代码
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void            (*input)(struct sk_buff *skb);
    struct mutex    *cb_mutex;
    int             (*bind)(struct net *net, int group);
    void            (*unbind)(struct net *net, int group);
    bool            (*compare)(struct net *net, struct sock *sk);
};
复制代码

groups 表示组播号

flags字段可以是

  NL_CFG_F_NONROOT_RECV 

  NL_CFG_F_NONROOT_SEND

input是一个回调函数

   rtnetlink_rcv() 

cb_mutex 是可选项,

 

netlink_kernel_create 在内核 nl_table 创建一个点。 

 

2. 内核注册

extern void rtnl_register(int protocol, int msgtype,
                     rtnl_doit_func,
                     rtnl_dumpit_func,
                     rtnl_calcit_func ); 

 

3. 发送消息

rtmsg_ifinfo()

 

2. 在内核通过netlink发送消息

int
netlink_unicast(struct sock *ssk, struct sk_buff
                *skb, u32 pid, int nonblock);

 

3. 在内核关闭netlink

sock_release(nl_sk->socket);

 

三、在路由表中添加或删除一条路由

ip route add 192.168.1.11 via 192.168.2.20
ip route del 192.168.2.11
ip monitor route

 

这条命令从用户态程序中发送netlink 消息(RTM_NEWROUTE)调用rtlinknet套接字来创建路由条目。

消息被内核态rtnetlink套接字的rtnetlink_rcv()方法接收。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多