How to use Linux Netlink

Posted by 051119 on Sun, 02 Jan 2022 21:54:47 +0100

How to use Linux Netlink

1, Netlink user mode construction process

1. Create a netlink based on a custom port_ Test socket.

#define NETLINK_TEST 99

skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
if (skfd == -1) {
	perror("create socket error\n");
	return -1;
}

2. Initialize source address structure struct sockaddr_nl is used for data transmission

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 */
};

struct sockaddr_nl saddr;
//Source address.
memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK; //AF_NETLINK
saddr.nl_pid = USER_PORT;  //netlink portid, same as kernel.
saddr.nl_groups = 0;

This structure is used to construct the communication address of netlink, which is similar to SOCKADDR in socket programming_ In similar, nl_pid is used to represent the communication port, nl_groups is used to represent communication groups. Note that here is the mask of the number of multicast groups you want to join, that is, up to 32 groups are supported.

3 * *. After the source address is initialized and the socket file descriptor is established successfully, the socket needs to be bound with the address and port to receive and send data**

//bind to skfd with saddr.
if (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {
	perror("bind() error\n");
	close(skfd);
	return -1;
}

4. Construct destination addr, that is, the initialization structure is struct sockaddr_nl type destination address.

//Destination address.
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0;    // to kernel 
daddr.nl_groups = 0;

5. The message header of Netlink is constructed and described by struct nlmsghdr. The data area of Netlink message is composed of message header and message body, and the message body is connected after the message header.

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 */
};

The following netlink macros are commonly used

#define NLMSG_ALIGNTO    4U
//Used to get the minimum value not less than len and byte aligned
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
//netlink header length
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
//Calculate the real message length of the message data len, message body + message header
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
//Return no less than NLMSG_LENGTH(len) and the smallest byte aligned value
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
//It is used to obtain the first address of the data part of the message. This macro is required when setting and reading the data part of the message
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
//It is used to get the first address of the next message, and len becomes the length of the remaining messages
#define NLMSG_NEXT(nlh,len)     ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
//Judge whether the message is > len
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len <= (len))
//Used to return the length of the payload
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
//Allocate memory for message headers and message bodies and initialize Netlink message headers
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
memset(nlh, 0, sizeof(struct nlmsghdr));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = saddr.nl_pid; //self port

6. netlink message body initialization, that is, the data we need to transmit.

memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));

7. Sending and receiving data

Send data to the kernel through skfd.

//Include header file
#include <sys/types.h>
#include <sys/socket.h>

//Function definition
int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

**Function Description: * * sendto() is used to transfer data from the specified socket to the other host Parameter s is the socket for which the connection has been established. If UDP protocol is used, the connection operation is not required The parameter msg points to the data content to be connected. The parameter flags is generally set to 0. For detailed description, please refer to send() The parameter to is used to specify the network address to be transmitted. For the sockaddr structure, please refer to bind() The parameter tolen is the result length of sockaddr

Return value: if successful, the actual number of characters transmitted will be returned. If failed, it will return - 1. The error reason is stored in errno

Error code:
1,EBADF parameter s Illegal socket Processing code.
2,EFAULT Parameter has a pointer to inaccessible memory space.
3,WNOTSOCK canshu s Is a document descriptor, wrong socket.
4,EINTR Interrupted by signal.
5,EAGAIN This action will block the process, But parameters s of soket Blocked for make-up classes.
6,ENOBUFS The system is out of buffer memory.
7,EINVAL The parameters passed to the system call are incorrect.
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
if (!ret) {
	perror("sendto error\n");
	close(skfd);
	exit(-1);
}

Receive data from kernel:

//Include header file
#include <sys/types.h>
#include <sys/socket.h>

//Function definition
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen);

**Function Description: * * recv() is used to receive the data from the remote host through the specified socket and store the data in the memory space pointed by the parameter buf. The parameter len is the maximum length of the data that can be received The parameter flags is generally set to 0. For other numerical definitions, please refer to recv() The parameter from is used to specify the network address to be transmitted. For the structure sockaddr, please refer to bind() The parameter fromlen is the structure length of sockaddr

Return value: returns the number of characters received if successful, or - 1 if failed. The error reason is stored in errno

Error code:
EBADF parameter s Illegal socket Processing code
EFAULT Parameter has a pointer to inaccessible memory space.
ENOTSOCK parameter s Is a document descriptor, wrong socket.
EINTR Interrupted by signal.
EAGAIN This action will block the process, But parameters s of socket It is non blocking.
ENOBUFS The system is out of buffer memory
ENOMEM Insufficient core memory
EINVAL The parameters passed to the system call are incorrect.
//Message header + message body
typedef struct _user_msg_info {
	struct nlmsghdr hdr;
	char  msg[MSG_LEN];
} user_msg_info;

//Receive netlink message from kernel.
memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
if (!ret) {
	perror("recv form kernel error\n");
	close(skfd);
	exit(-1);
}

2, Netlink kernel section

1. In the driver initialization phase, initialize a socket to interact with user state data.

struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb);-----------------------------------input Callback function
    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);
};

//Callback function of kernel receiving data
struct netlink_kernel_cfg cfg = { 
	.input  = netlink_rcv_msg, /* set recv callback */
};

struct socket *nlsk = NULL;
/* Create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if (nlsk == NULL) {   
	printk("netlink_kernel_create error !\n");
	return -1; 
}

2. The callback function processes the data sent from the user status.

skb->len; 
@len: Length of actual data
//The parameter of the receiving function is struct sk_buffer *skb;
nlh = nlmsg_hdr(skb);  //Get nlmsghdr from sk_buff.
umsg = NLMSG_DATA(nlh); //Get payload from nlmsghdr.

3. Send data to user status and create a new sk_ buffer -> nlmsg_ Put() set sk_ Buffer nltmsghdr - > fill paylaod - > send data to port.

int send_usrmsg(char *pbuf, uint16_t len)
{
	struct sk_buff *nl_skb;
	struct nlmsghdr *nlh;

	int ret;

	//Create sk_buff using nlmsg_new().
	nl_skb = nlmsg_new(len, GFP_ATOMIC);
	if(!nl_skb)
	{
		printk("netlink alloc failure\n");
		return -1;
	}

	//Set up nlmsghdr.
	nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
	if(nlh == NULL)
	{
		printk("nlmsg_put failaure \n");
		nlmsg_free(nl_skb);  //If nlmsg_put() failed, nlmsg_free() will free sk_buff.
		return -1;
	}

	//Copy pbuf to nlmsghdr payload.
	memcpy(nlmsg_data(nlh), pbuf, len);
	ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

	return ret;
}

The sender of the kernel needs to construct sk_buffer.

Reference documents:

netlink:https://www.cnblogs.com/arnoldlu/p/9532254.html

netlink details: https://blog.csdn.net/stone8761/article/details/72780863

Topics: Linux network server