1 copy_to_user
與copy_from_user
2 netlink
2.1 netlink簡介
- netlink socket是一種linux特有的socket,用與實現用戶進程與內核進程之間通訊的一種特殊的進程之間通訊方式(IPC),也是網絡應用程序與內核通訊的最經常使用的接口
- netlink是一種在內核和用戶應用空間之間進行雙向數據傳輸的好方式,用戶態只須要使用標準的socket API接口就可以使用Netlink所提供的功能,固然,內核態還須要使用專門的內核API來使用Netlink
2.2 netlink API
- netlink報文格式:netlink的報文一般由消息頭與消息體構成,其中消息頭使用
struct nlmsghdr
結構體保存,以下:
/* netlink消息報文消息頭 */
/*
@nlmsg_len:整個消息長度,包含netlink消息頭自己,單位:字節
@nlmsg_type:消息類型,便是數據消息仍是控制消息,其中有如下4種控制消息
NLMSG_NOOP:空消息,啥也不幹
NLMSG_ERROR:指明該消息中含有一個錯誤
NLMSG_DONE:若是內核經過Netlink隊列返回了多個消息,那麼隊列的最後一條消息的類型爲NLMSG_DONE,其他全部 消息的nlmsg_flags屬性都被設置NLM_F_MULTI位有效
NLMSG_OVERRUN:暫時用不到
@nlmsg_flags:附加在消息上的額外說明信息,如上面提到的NLM_F_MULTI,通常不多用
@nlmsg_seq:序列號,不多用
@nlmsg_pid:消息發送方的PID進程號,若是發送方是內核,那麼爲0
*/
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 PID */
};
/* 獲得不小於len且字節對齊的最小數值 */
#define NLMSG_ALIGNTO 4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/* 計算數據部分長度爲len時實際的消息長度,它通常用於分配消息緩存 */
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/* 返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值,它也用於分配消息緩存 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/* 取得消息的數據部分的首地址,設置和讀取消息數據部分時須要使用該宏 */
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/* 獲得下一個消息的首地址,同時len也減小爲剩餘消息的總長度,該宏通常在一個消息被分紅幾個部分發送或接收時使用 */
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
/* 用於判斷消息是否有len這麼長 */
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
/* 返回payload的長度 */
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
/* step1:創建socket套接字 */
#include <sys/types.h>
#include <sys/socket.h>
/*
功能:建立套接字
參數:
@domain:地址族(Address Family),即IP地址類型,經常使用有:
AF_INET(IPV4類型),AF_INET6(IPV6類型)
AF_UNIX, AF_LOCAL(本地進程通訊)
AF_NETLINK(內核與用戶空間的通訊,用於設備驅動)
AF_PACKET(原始套接字)
@type:套接字類型,經常使用有:
SOCK_STREAM(流式套接字,對應TCP)
SOCK_DGRAM(數據報套接字,對應UDP)
SOCK_RAW(原始套接字)
@protocol:TCP與UDP編程時候填0,原始套接字時候需填充,爲netlink時候需填入netlink協議類型
返回值:成功返回一個文件描述符,失敗返回-1
*/
int socket(int domain, int type, int protocol);
/* 一共支持32種消息類型,下述是linux系統所定義好的幾種消息類型,剩下的可本身定義 */
#define NETLINK_ROUTE 0 /* Routing/device hook(路由) */
#define NETLINK_W1 1 /* 1-wire subsystem */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook(防火牆) */
#define NETLINK_INET_DIAG 4 /* INET socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/***************************************************************************************************/
/* step2:綁定端口 */
#include <sys/types.h>
#include <sys/socket.h>
/*
功能:將一本地地址與一套接口捆綁
參數:
@sockfd:經過socket函數得到的文件描述符
@addr:結構體地址,基於Internet通訊時候填sockaddr_in類型結構體,須要強制轉換爲sockaddr類型
@addrlen:結構體長度
返回值:成功返回0,失敗返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/* 注意:在綁定的時候bind()函數的參數2是sockaddr類型參數,實際在編程中使用的卻不是該格式的數據類型,因此在填充的時候須要強制轉換爲struct sockaddr類型。在網絡編程中使用的是struct sockaddr_in相似的數據,而在與內核通訊時候須要填充爲struct sockaddr_nl類型,該類型詳細介紹以下:*/
struct sockaddr_nl
{
sa_family_t nl_family; /*該字段老是爲AF_NETLINK */
unsigned short nl_pad; /* 目前未用到,填充爲0*/
__u32 nl_pid; /* 綁定者進程的進程號,一般設置爲當前進程的進程號 */
__u32 nl_groups; /* 綁定者但願加入的多播組 */
};
/***************************************************************************************************/
/* setp3:發送消息 */
int sendmsg(struct socket *sock, struct msghdr *m, size_t total_len);
/* msghdr參數格式以下:
@msg_name:指向sockaddr的指針,在netlink中指向sockaddr_nl類型
@msg_namelen:msg_name的長度
@msg_iov:數據
*/
struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
/* iovc結構體組成以下:
@iov_base:數據的基地址
@iov_len:數據的長度
*/
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
/************************************內核API*********************************************************/
/*
功能:建立netlink socket(老版本內核)
參數:
@net:一個網絡名字空間namespace,在不一樣的名字空間裏面能夠有本身的轉發信息庫,有本身的一套net_device等等,默 認狀況下都是使用 init_net 這個全局變量
@unit:表示netlink協議類型,如NETLINK_TEST、NETLINK_SELINUX
@groups:多播地址
@input:爲內核模塊定義的netlink消息處理函數,當有消息到達這個netlink socket時,該input函數指針就會被引 用,且只有此函數返回時,調用者的sendmsg才能返回
@cb_mutex:爲訪問數據時的互斥信號量
@module:通常爲THIS_MODULE
*/
struct sock *netlink_kernel_create(struct net *net,
int unit,unsigned int groups,
void (*input)(struct sk_buff *skb),
struct mutex *cb_mutex,struct module *module);
/*
功能:建立netlink socket(新版本內核)
參數:
@net:一個網絡名字空間namespace,在不一樣的名字空間裏面能夠有本身的轉發信息庫,有本身的一套net_device等等,默 認狀況下都是使用 init_net這個全局變量
@unit:表示netlink協議類型,如NETLINK_TEST、NETLINK_SELINUX
@cfg:配置參數結構體
*/
struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
/* netlink_kernel_cfg結構體 */
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);
}
/*
功能:發送單播消息
參數:
@ssk:函數netlink_kernel_create()返回的socket
@skb:存放消息,它的data字段指向要發送的netlink消息結構,而skb的控制塊保存了消息的地址信息,宏
NETLINK_CB(skb)就用於方便設置該控制塊
@pid:即接收此消息進程的pid,即目標地址,若是目標爲組或內核,它設置爲0
@nonblock:表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回;而若是爲0,該函數在沒有 接收緩存可利用定時睡眠。
*/
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);
/*
功能:發送廣播消息
參數:
@ssk:函數netlink_kernel_create()返回的socket
@skb:存放消息,它的data字段指向要發送的netlink消息結構,而skb的控制塊保存了消息的地址信息,宏
NETLINK_CB(skb)就用於方便設置該控制塊
@pid:即接收此消息進程的pid,即目標地址,若是目標爲組或內核,它設置爲0
@group:接收消息的多播組,該參數的每個位表明一個多播組,所以若是發送給多個多播組,就把該參數設置爲多個多播 組組ID的位相或,全部目標多播組對應掩碼的OR操做的合值
@allocation:內核內存分配類型,通常地爲GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不能夠睡 眠),而GFP_KERNEL用於非原子上下文。
*/
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation);
/*
功能:釋放 netlink socket
*/
void netlink_kernel_release(struct sock *sk);
/**************************************工具函數******************************************************/
/* 從sk_buff->data獲取struct nlmsghdr(消息頭)數據 */
static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
{
return (struct nlmsghdr *)skb->data;
}
/* 建立len大小的struct sk_buff */
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
return alloc_skb(nlmsg_total_size(payload), flags);
}
/* 將一個新的netlink消息加入到skb中。若是skb沒法存放消息則返回NULL */
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
int type, int payload, int flags);
/* 釋放nlmsg_new()建立的skb */
static inline void nlmsg_free(struct sk_buff *skb)--------------------------------釋放nlmsg_new()建立的skb。
{
kfree_skb(skb);
}
/* 根據nlmsghdr指針獲取對應的payload */
static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{
return (unsigned char *) nlh + NLMSG_HDRLEN;
}
/* 獲取消息流中下一個netlink消息 */
static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh, int *remaining);
/* */
static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh);
2.3 netlink使用流程分析
2.3.1 用戶層分析
- 首先使用netlink與用戶交互數據須要遵照內核所規定的格式
- 跟UDP網絡編程同樣,咱們在發送消息的時候須要先創建socket鏈接,在創建socket鏈接的時候咱們須要指定地址族爲
AF_NETLINK
類型,專門用於用戶與內核空間之間進行數據交互;以後須要將套接字類型設置爲原始套接字SOCK_RAW
其專門用於進程間通訊;最後的消息類型直接填寫咱們本身定義的或者系統定義好的消息類型便可,消息種類可參考2.2
- 在創建好套接字事後咱們須要綁定端口,其中參數二的類型爲
sockaddr
類型,可是咱們實際的類型倒是sockaddr_nl
類型,在此結構體中咱們須要指明咱們的地址族,仍然是AF_NETLINK
類型;以後即是設置端口號,注意,雖然代碼裏面寫的是pid,但此pid並不是指的是進程號,相比於進程號,它更像是一個地址,在bind
函數中此處nl_pid
就是數據源地址,即消息的發送者
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
struct sockaddr_nl
{
sa_family_t nl_family; /*該字段老是爲AF_NETLINK */
unsigned short nl_pad; /* 目前未用到,填充爲0*/
__u32 nl_pid; /* 消息地址,一般設置爲當前進程的進程號,0則表示是內核 */
__u32 nl_groups; /* 綁定者但願加入的多播組 */
};
- 綁定完成以後就須要咱們去填寫消息接收方的信息,一樣使用
sockaddr_nl
結構體去保存消息接收者的信息,示例中的消息接收方是內核,因此nl_pid
應該寫爲0
- 收發雙方信息肯定以後就須要咱們去發送消息了,netlink整個數據包由消息頭與消息體所組成,消息頭使用
nlmsghdr
封裝起來,在此處咱們須要使用NLMSG_ALIGN
宏去計算咱們所申請的用來保存消息頭的空間大小
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 PID */
};
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
- 有了保存消息頭的空間以後就須要咱們去完善消息頭內容了,首先咱們須要在
nlmsg_len
處指明消息的大小,此處仍是使用NLMSG_ALIGN
宏來計算;以後在nlmsg_pid
處填上發送方的地址便可,這裏就有人會決定奇怪了,以前不是在sockaddr_nl
處指明瞭消息發送方的地址了嗎,爲啥還要指定?由於此處不像網絡編程,在網絡編程裏面系統會把發送方的信息給直接寫入報文裏面去,而此處須要咱們手動寫入發信方的地址,這樣在接收方接收到這條消息以後才能夠從消息中解析出發信人的地址。就比如咱們去郵局寄信,雖然郵局(操做系統)知道你郵寄了一封信件,可是你仍是得在信上寫下你的姓名,爲的就是告訴收信人這是你發的消息。以後再使用NLMSG_DATA
來找到咱們寫入消息的位置進而去向這個位置寫入消息
/* 取得消息的數據部分的首地址,設置和讀取消息數據部分時須要使用該宏 */
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
- 至此,消息準備完畢,以後就須要尋求載體將準備好的消息發送出去,這裏咱們先使用
sendmsg
函數去發送消息,此處須要將上面準備的接收方信息和消息頭數據填入msghdr
結構體中
int sendmsg(struct socket *sock, struct msghdr *m, size_t total_len);
/* msghdr參數格式以下:
@msg_name:指向sockaddr的指針,在netlink中指向sockaddr_nl類型
@msg_namelen:msg_name的長度
@msg_iov:數據
*/
struct msghdr {
/* 指向socket地址結構 */
void * msg_name; /* Socket name */
/* 地址結構長度 */
int msg_namelen; /* Length of name */
/* 數據 */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
/* iovc結構體組成以下:
@iov_base:數據的基地址
@iov_len:數據的長度
*/
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
2.3.2 內核層分析
- 內核端不用多說,首先就是進入模塊初始化函數裏面,在裏面建立一個內核版本的netlink socket,其中咱們須要在參數二處指定咱們所要收發的消息類型,此處應該與應用層的類型保持一致;並且咱們須要在參數三中配置相關參數,其中最重要的就是消息回調函數,當內核收到消息後會進入消息回調函數處理消息,只有當這個函數處理完消息以後,應用層的
sendmsg
函數纔會返回
struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
void (*input)(struct sk_buff *skb);
- 其中應用層的消息(消息頭)被保存在了
sk_buff->data
結構體中,咱們可經過函數nlmsg_hdr
將消息頭取出
- 取出消息頭以後可使用
NLMSG_DATA
宏將用戶實際發送的負載信息取出來
- 至此,用戶數據已經被內核拿到,此刻如何給用戶層一個反饋呢?
- 其實又須要反過來,首先準備咱們的負載信息(內核-->用戶空間),以後使用
nlmsg_new
函數去申請一片sk_buff
類型的數據區,
2.4 netlink使用示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#define MAX_PAYLOAD 1024
#define NETLINK_TEST 25
int main(int argc, char *argv[])
{
int sock_fd;
int ret;
struct nlmsghdr *nlh;
struct sockaddr_nl send_addr, recv_addr;
struct iovec iov;
struct msghdr msg;
/* step1:建立一個socket套接字 */
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
if (sock_fd == -1) {
printf("fail to create socket\n");
return -1;
}
/* 填充綁定步驟所須要的數據,地址族,消息源地址,多播組等 */
memset(&send_addr, 0x00, sizeof(send_addr));
send_addr.nl_family = AF_NETLINK;
send_addr.nl_pid = 100;
send_addr.nl_groups = 0;
/* step2:綁定,與建立的套接字綁定,發信人信息準備完畢 */
ret = bind(sock_fd, (struct sockaddr*)&send_addr, sizeof(send_addr));
if (ret < 0) {
printf("fail to bind\n");
close(sock_fd);
return -1;
}
/* 填寫消息接收者的信息,收信人信息準備完畢 */
memset(&recv_addr, 0x00, sizeof(recv_addr));
recv_addr.nl_family = AF_NETLINK;
recv_addr.nl_pid = 0;
recv_addr.nl_groups = 0;
/* 申請一個空間來存儲消息頭的信息,申請了一張信紙 */
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
if (!nlh) {
printf("failed to malloc space\n");
close(sock_fd);
return -1;
}
/* 填寫消息頭信息,在信紙上寫信 */
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = 100; //填寫發送方的地址
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "hello kernel\n"); //填寫發送的內容
/* 將消息頭數據再次封裝 */
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
/* 填寫接收方信息 */
memset(&msg, 0x00, sizeof(msg));
msg.msg_name = (void *)&recv_addr;
msg.msg_namelen = sizeof(recv_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* 發送消息 */
ret = sendmsg(sock_fd, &msg, 0);
if (ret == -1) {
printf("failed to sendmsg\n");
}
/* 清空消息頭 */
memset(nlh, 0x00, NLMSG_SPACE(MAX_PAYLOAD));
while (1) {
/* 接收消息 */
ret = recvmsg(sock_fd, &msg, 0);
if (ret < 0) {
printf("failed to recvmsg\n");
}
/* 取出收到的消息 */
printf("APP recv msg--->%s\n", (char *)NLMSG_DATA(nlh));
}
close(sock_fd);
free(nlh);
nlh = NULL;
return 0;
}
#include <linux/module.h>
#include <linux/init.h>
#include <net/netlink.h>
#include <net/sock.h>
#define NETLINK_TEST 25
#define USER_PORT 100
int netlink_count = 0;
/* 發送給用戶層的消息 */
char netlink_kmsg[30] = {0};
struct sock *nl_sock = NULL;
int send_to_user(char *buff, int len)
{
int ret;
/* 保存消息頭 */
struct nlmsghdr *nlh = NULL;
/* 保存向用戶發送的消息 */
struct sk_buff *nl_skb;
/* 申請一片sk_buff類型的數據區 */
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if (!nl_skb) {
printk("sk_buff malloc failed\n");
return -1;
}
/* 將消息頭數據做爲負載加入到sk_buff中 */
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
if (!nlh) {
printk("failed to nlmsg_put\n");
nlmsg_free(nl_skb);
return -1;
}
/* 在消息頭中找到負載區,將負載消息加入到負載區 */
memcpy(nlmsg_data(nlh), buff, len);
/* 發送單播消息給用戶層 */
ret = netlink_unicast(nl_sock, nl_skb, USER_PORT, MSG_DONTWAIT);
return ret;
}
static void netlink_rcv_msg(struct sk_buff *skb)
{
char *recv_msg = NULL;
/* 保存消息頭 */
struct nlmsghdr *nlh = NULL;
if (skb->len >= NLMSG_SPACE(0)) {
netlink_count++;
/* 封裝給用戶層發送的消息 */
snprintf(netlink_kmsg, sizeof(netlink_kmsg), "recv %d messages from user\n", netlink_count);
/* 取出消息頭 */
nlh = nlmsg_hdr(skb);
/* 取出消息頭中的負載信息(真實信息) */
recv_msg = NLMSG_DATA(nlh);
if (recv_msg) {
printk(KERN_INFO "recv from user:%s\n", recv_msg);
/* 發送反饋信息給用戶層 */
send_to_user(netlink_kmsg, strlen(netlink_kmsg));
}
}
}
/* 填寫netlink socket的配置參數,此處指定了對應的消息處理函數 */
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg,
};
static int __init netlink_init(void)
{
/* 建立內核netlink socket */
nl_sock = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if (nl_sock == NULL) {
printk(KERN_ERR "fail to create kernel netlink\n");
return -1;
}
printk("create kernel netlink success\n");
return 0;
}
static void __exit netlink_exit(void)
{
if (nl_sock != NULL) {
netlink_kernel_release(nl_sock);
nl_sock = NULL;
}
printk("kernel netlink release success\n");
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_AUTHOR("hq");
MODULE_LICENSE("GPL");