+----------------------+----------------------+----------------------+----------------------+ ~ ~ ~ ~ ~ +----------------------+ | Netlink messaeg #1 | Netlink messaeg #2 | Netlink messaeg #3 | Netlink messaeg #4 | ... | Netlink messaeg #n | | (Header+Payload+Pad) | (Header+Payload+Pad) | (Header+Payload+Pad) | (Header+Payload+Pad) | | (Header+Payload+Pad) | +----------------------+----------------------+----------------------+----------------------+ ~ ~ ~ ~ ~ +----------------------+ <--------------------------------------------------- Request OR Response packet ----------------------------------------------->
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Description | |
32bit Length (Header를 포함한 message 크기) | ↑↓16 bytes NLMSG_HDRLEN (struct nlmsghdr) | ↑↓Netlink message (nlmsghdr.nlmsg_len) | |||||||||||||||||||||||||||||||
16-bit Type (Message content) | 16-bit Flags (Additional flags) | ||||||||||||||||||||||||||||||||
32bit Sequence Number | |||||||||||||||||||||||||||||||||
32bit Process ID (PID, Sending process port ID) | |||||||||||||||||||||||||||||||||
Payload (Variable data) data_ptr = NLMSG_DATA(nlmsghdr) | ↑↓(nlmsghdr.nlmsg_len - NLMSG_HDRLEN) |
<----- NLMSG_HDRLEN ------> <-------- Payload-Len --------> +---------------------+- - -+- - - - - - - - - - - - - - - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ | Header | Pad | Payload | ... (Next netlink message) ... | (struct nlmsghdr) | ing | Specific data + [attribute..] | +---------------------+- - -+- - - - - - - - - - - - - - - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ↑ nlmsghdr ↑ NLMSG_DATA(&nlmsghdr) ↑ NLMSG_NEXT(&nlmsghdr) <------------------ nlmsghdr->nlmsg_len ------------------> <------------------ NLMSG_LENGTH(Payload-Len) ------------> Payload의 선두부분은 nlmsg_type에 따른 고유 구조체(Specific data) 형식이 올 수 있으며 NLMSG_ALIGN(sizeof(Specific data)) 정렬 후 그 다음에 attribute로 구성되는게 일반적입니다.
<------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> +---------------------+- - -+- - - - - - - - - -+- - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ | Header | Pad | Payload | Pad | ... (Next attribute) ... | (struct nlattr) | ing | | ing | +---------------------+- - -+- - - - - - - - - -+- - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ↑ nlattr <-------------- nlattr->nla_len --------------> nla_type (16 bits) +---+---+-------------------------------+ | N | O | Attribute Type | +---+---+-------------------------------+ N := Carries nested attributes O := Payload stored in network byte order Note: The N and O flag are mutually exclusive.
struct nlmsghdr { uint32_t nlmsg_len; /* Header를 포함한 Netlink message 크기 */ uint16_t nlmsg_type; /* Message content */ uint16_t nlmsg_flags; /* Additional flags */ uint32_t nlmsg_seq; /* Sequence number */ uint32_t nlmsg_pid; /* Sending process port ID */ };
struct nlattr { uint16_t nla_len; /* Header를 포함한 attribute 크기 */ uint16_t nla_type; /* Attribute type */ };
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Netlink message header (nlmsghdr) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Generic Netlink message header (genlmsghdr) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional user specific message header | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Optional Generic Netlink message payload | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
s_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM /* 6 - ipsec */);
__u32 s_nl_groups; struct sockaddr_nl s_sockaddr_nl; s_nl_groups |= XFRMNLGRP_ACQUIRE; s_nl_groups |= XFRMNLGRP_EXPIRE; s_nl_groups |= XFRMNLGRP_SA; s_nl_groups |= XFRMNLGRP_POLICY; s_nl_groups |= XFRMNLGRP_AEVENTS; s_nl_groups |= XFRMNLGRP_REPORT; s_nl_groups |= XFRMNLGRP_MIGRATE; s_nl_groups |= XFRMNLGRP_MAPPING; s_sockaddr_nl.nl_family = AF_NETLINK; s_sockaddr_nl.nl_pad = (unsigned short)0u; s_sockaddr_nl.nl_pid = (pid_t)0; s_sockaddr_nl.nl_groups = s_nl_groups; /* Multicast groups mask */ bind(s_socket, (const struct sockaddr *)(&s_sockaddr_nl), (socklen_t)sizeof(s_sockaddr_nl));
socklen_t s_socklen; s_socklen = (socklen_t)sizeof(s_sockaddr_nl); s_recv_bytes = recvfrom( s_socket, s_buffer, s_buffer_size, MSG_NOSIGNAL, (struct sockaddr *)(&s_sockaddr_nl), (socklen_t *)(&s_socklen) );
size_t s_msg_size; struct nlmsghdr *s_nlmsghdr; size_t s_payload_size; void *s_payload; s_msg_size = (size_t)s_recv_bytes; for(s_nlmsghdr = (struct nlmsghdr *)s_buffer;(s_is_break == 0) && NLMSG_OK(s_nlmsghdr, s_msg_size);s_nlmsghdr = NLMSG_NEXT(s_nlmsghdr, s_msg_size)) { /* Netlink 수신패킷 하나에 여러개의 Netlink header가 탑재될 수 있는데 이를 각 Header 단위로 분리하는 Loop */ s_payload_size = (size_t)NLMSG_PAYLOAD(s_nlmsghdr, 0); /* Header 내의 실제 Data 크기 */ s_payload = NLMSG_DATA(s_nlmsghdr); /* Header 내의 실제 Data 위치 포인터 */ switch(s_nlmsghdr->nlmsg_type) { /* 각 메세지의 종류별로 다른 파싱구조를 가지고 있으므로 커널을 참조하여 해당 부분을 파싱해야 합니다. */ ..... } }
#include <linux/netlink.h> #include <linux/genetlink.h>
size_t hwport_generate_netlink_message(void *s_buffer, size_t s_buffer_size, unsigned int s_nlmsg_type, unsigned int s_nlmsg_flags, unsigned int s_nlmsg_seq, unsigned int s_nlmsg_pid, const void *s_payload, size_t s_payload_size) { int s_aligned_payload_size; struct nlmsghdr *s_nlmsghdr; void *s_payload_ptr; s_aligned_payload_size = (int)NLMSG_ALIGN((uint32_t)s_payload_size); if (s_buffer_size < ((size_t)NLMSG_LENGTH(s_aligned_payload_size))) { return((size_t)0u); } s_nlmsghdr = (struct nlmsghdr *)memset(s_buffer, 0, sizeof(struct nlmsghdr)); s_nlmsghdr->nlmsg_len = (uint32_t)NLMSG_LENGTH(s_aligned_payload_size); s_nlmsghdr->nlmsg_type = (uint16_t)s_nlmsg_type; s_nlmsghdr->nlmsg_flags = (uint16_t)s_nlmsg_flags; s_nlmsghdr->nlmsg_seq = (uint32_t)s_nlmsg_seq; s_nlmsghdr->nlmsg_pid = (uint32_t)s_nlmsg_pid; s_payload_ptr = (void *)NLMSG_DATA(s_nlmsghdr); if (s_payload_size > ((size_t)0u)) { if (s_payload == ((const void *)(NULL))) { (void)memset(s_payload_ptr, 0, s_payload_size); } else { (void)memcpy(s_payload_ptr, s_payload, s_payload_size); } } return((size_t)s_nlmsghdr->nlmsg_len); }
size_t hwport_append_netlink_attr(void *s_buffer, size_t s_buffer_size, unsigned int s_nla_type, const void *s_attr1, size_t s_attr1_size, const void *s_attr2, size_t s_attr2_size) { struct nlmsghdr *s_nlmsghdr; struct nlattr *s_nlattr; size_t s_attr_size; void *s_attr_ptr; s_attr_size = s_attr1_size + s_attr2_size; s_nlmsghdr = (struct nlmsghdr *)s_buffer; if (s_buffer_size < ((size_t)(s_nlmsghdr->nlmsg_len + NLA_HDRLEN + NLA_ALIGN(s_attr_size)))) { return((size_t)0u); } s_nlattr = (struct nlattr *)(((uint8_t *)s_buffer) + s_nlmsghdr->nlmsg_len); s_nlattr->nla_len = (uint16_t)NLA_HDRLEN + s_attr_size; s_nlattr->nla_type = (uint16_t)s_nla_type; s_attr_ptr = (void *)(((uint8_t *)s_nlattr) + NLA_HDRLEN); if(s_attr1_size > ((size_t)0u)) { if(s_attr1 == ((const void *)0)) { (void)memset((void *)s_attr_ptr, 0, s_attr1_size); } else if(((const void *)s_attr_ptr) != s_attr1) { (void)memcpy((void *)s_attr_ptr, s_attr1, s_attr1_size); } s_attr_ptr = (void *)(((uint8_t *)s_attr_ptr) + s_attr1_size); } if(s_attr2_size > ((size_t)0u)) { if(s_attr2 == ((const void *)0)) { (void)memset((void *)s_attr_ptr, 0, s_attr2_size); } else if(((const void *)s_attr_ptr) != s_attr2) { (void)memcpy((void *)s_attr_ptr, s_attr2, s_attr2_size); } } s_nlmsghdr->nlmsg_len += (uint32_t)NLA_ALIGN(s_nlattr->nla_len); return((size_t)s_nlmsghdr->nlmsg_len); }
ssize_t hwport_request_generic_netlink(int s_socket, const void *s_data, size_t s_size, const struct sockaddr *s_sockaddr, socklen_t s_socklen) { size_t s_sent_size; size_t s_want_size; ssize_t s_send_bytes; const uint8_t *s_uint8_ptr; struct sockaddr_nl s_sockaddr_nl; if (s_socket == (-1)) { errno = EINVAL; return((ssize_t)(-1)); } if (s_sockaddr == ((struct sockaddr *)(NULL))) { s_socklen = (socklen_t)sizeof(s_sockaddr_nl); s_sockaddr = (const struct sockaddr *)memset((void *)(&s_sockaddr_nl), 0, sizeof(s_sockaddr_nl)); s_sockaddr_nl.nl_family = AF_NETLINK; s_sockaddr_nl.nl_pid = 0; /* port ID */ s_sockaddr_nl.nl_groups = 0; /* multicast groups mask */ } s_sent_size = (size_t)0u; s_uint8_ptr = (const uint8_t *)s_data; while (s_sent_size < s_size) { s_want_size = s_size - s_sent_size; s_send_bytes = sendto( s_socket, (const void *)(&s_uint8_ptr[s_sent_size]), s_want_size, MSG_NOSIGNAL, s_sockaddr, s_socklen ); if (s_send_bytes > ((ssize_t)0)) { s_sent_size += (size_t)s_send_bytes; } else if((s_send_bytes == ((ssize_t)(-1))) && ((errno == EINTR) || (errno == EAGAIN))) { continue; } else { /* error */ return(s_send_bytes); } } return((ssize_t)s_sent_size); }
ssize_t hwport_response_generic_netlink(int s_socket, void *s_data, size_t s_size, struct sockaddr *s_sockaddr, socklen_t *s_socklen_ptr) { struct sockaddr_storage s_sockaddr_storage; socklen_t s_socklen; if (s_socket == (-1)) { errno = EINVAL; return((ssize_t)(-1)); } if (s_sockaddr == ((struct sockaddr *)(NULL))) { s_sockaddr = (struct sockaddr *)memset((void *)(&s_sockaddr_storage), 0, sizeof(s_sockaddr_storage)); } if (s_socklen_ptr == ((socklen_t *)(NULL))) { s_socklen = (socklen_t)sizeof(struct sockaddr_nl); s_socklen_ptr = (socklen_t *)(&s_socklen); } return(recvfrom(s_socket, s_data, s_size, MSG_NOSIGNAL, s_sockaddr, s_socklen_ptr)); }
int hwport_get_family_id_by_name(int s_socket, const char *s_family_name) { int s_family_id; size_t s_family_name_size; uint8_t s_buffer[ 4 << 10 ]; size_t s_message_size; size_t s_offset; int s_is_break; struct nlmsghdr *s_nlmsghdr; struct genlmsghdr *s_genlmsghdr; struct nlattr *s_nlattr; ssize_t s_send_bytes; ssize_t s_recv_bytes; if (s_socket == (-1)) { errno = EINVAL; return(-1); } if (s_family_name == ((const char *)(NULL))) { errno = EINVAL; return(-1); } s_family_name_size = strlen(s_family_name); s_nlmsghdr = (struct nlmsghdr *)(&s_buffer[0]); s_message_size = hwport_generate_netlink_message( (void *)s_nlmsghdr, sizeof(s_buffer), (unsigned int)GENL_ID_CTRL, /* nlmsg_type */ (unsigned int)NLM_F_REQUEST /* | NLM_F_ACK */, /* nlmsg_flags */ 0u, /* nlmsg_seq */ (unsigned int)getpid(), /* nlmsg_pid */ (const void *)(NULL), /* payload */ sizeof(struct genlmsghdr) ); if (s_message_size <= ((size_t)0u)) { errno = ENOMEM; return(-1); } s_genlmsghdr = (struct genlmsghdr *)memset((void *)NLMSG_DATA(s_nlmsghdr), 0, sizeof(struct genlmsghdr)); s_genlmsghdr->cmd = CTRL_CMD_GETFAMILY; /* SeeAlso : CTRL_CMD_XXXX in "include/uapi/linux/genetlink.h" */ s_genlmsghdr->version = 1 /* 해당 message 종류에 따른 VERSION을 넣어야 함 */; s_message_size = hwport_append_netlink_attr( (void *)s_nlmsghdr, sizeof(s_buffer), (unsigned int)CTRL_ATTR_FAMILY_NAME, /* nla_type */ (const void *)s_family_name, /* attr1 */ s_family_name_size + ((size_t)1u), /* attr1_size (family name 은 문자열 뒤의 nul terminate까지 포함한 길이여야 함) */ (const void *)(NULL), /* attr2 */ (size_t)0u /* attr2_size */ ); if (s_message_size <= ((size_t)0u)) { errno = ENOMEM; return(-1); } s_send_bytes = hwport_request_generic_netlink( s_socket, (const void *)s_nlmsghdr, s_message_size, (const struct sockaddr *)(NULL), (socklen_t)0 ); if (s_send_bytes <= ((ssize_t)0)) { return(-1); } s_recv_bytes = hwport_response_generic_netlink( s_socket, (void *)(&s_buffer[0]), sizeof(s_buffer), (struct sockaddr *)(NULL), (socklen_t *)(NULL) ); if (s_recv_bytes <= ((ssize_t)0)) { return(-1); } s_family_id = (-1); s_is_break = 0; s_message_size = (size_t)s_recv_bytes; for (s_nlmsghdr = (struct nlmsghdr *)(&s_buffer[0]);(s_is_break == 0) && NLMSG_OK(s_nlmsghdr, s_message_size);s_nlmsghdr = NLMSG_NEXT(s_nlmsghdr, s_message_size)) { /* payload_size = (size_t)NLMSG_PAYLOAD(s_nlmsghdr, 0); */ /* payload = NLMSG_DATA(s_nlmsghdr); */ switch(s_nlmsghdr->nlmsg_type) { case NLMSG_NOOP: break; case NLMSG_ERROR: s_is_break = 1; break; case NLMSG_DONE: s_is_break = 1; break; case NLMSG_OVERRUN: s_is_break = 1; break; case GENL_ID_CTRL: /* NLMSG_MIN_TYPE */ s_genlmsghdr = (struct genlmsghdr *)NLMSG_DATA(s_nlmsghdr); for (s_offset = (size_t)(NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(struct genlmsghdr)));s_offset < s_nlmsghdr->nlmsg_len;) { if ((s_offset + ((size_t)NLA_HDRLEN)) > ((size_t)s_nlmsghdr->nlmsg_len)) { /* attibute header 만큼이 남지 않았음. */ break; } s_nlattr = (struct nlattr *)(((uint8_t *)s_nlmsghdr) + s_offset); if (s_nlattr->nla_len < ((size_t)NLA_HDRLEN)) { /* attribute length 가 최소 크기를 만족하지 못함 */ break; } if ((s_offset + ((size_t)NLA_ALIGN(s_nlattr->nla_len))) > ((size_t)s_nlmsghdr->nlmsg_len)) { /* attribute 공간이 nlmsg를 넘어섬 */ break; } switch(s_nlattr->nla_type) { /* SeeAlso : CTRL_ATTR_XXXX in "include/uapi/linux/genetlink.h" */ case CTRL_ATTR_UNSPEC: /* 0 */ break; case CTRL_ATTR_FAMILY_ID: /* 1 */ s_family_id = (int)(*((uint16_t *)(((uint8_t *)s_nlattr) + NLA_HDRLEN))); #if 1L if (s_family_id != (-1)) { /* family id 를 인지했으므로 더이상의 attr은 볼 필요가 없음 */ s_is_break = 1; break; } #endif break; case CTRL_ATTR_FAMILY_NAME: /* 2 */ break; case CTRL_ATTR_VERSION: /* 3 */ break; case CTRL_ATTR_HDRSIZE: /* 4 */ break; case CTRL_ATTR_MAXATTR: /* 5 */ break; case CTRL_ATTR_OPS: /* 6 */ break; case CTRL_ATTR_MCAST_GROUPS: /* 7 */ break; default: /* 8 >= */ break; } s_offset += (size_t)NLA_ALIGN(s_nlattr->nla_len); } break; default: break; } } return(s_family_id); }
int hwport_open_generic_netlink(uint32_t s_port_id, uint32_t s_groups_mask) { int s_socket; struct sockaddr_nl s_sockaddr_nl; s_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (s_socket == (-1)) { return(-1); } (void)memset((void *)(&s_sockaddr_nl), 0, sizeof(s_sockaddr_nl)); s_sockaddr_nl.nl_family = AF_NETLINK; #if 0L s_sockaddr_nl.nl_pad = 0; #endif s_sockaddr_nl.nl_pid = s_port_id; /* port ID */ s_sockaddr_nl.nl_groups = s_groups_mask; /* multicast groups mask */ if (bind(s_socket, (struct sockaddr *)(&s_sockaddr_nl), (socklen_t)sizeof(s_sockaddr_nl)) == (-1)) { int s_check; int s_save_errno; s_save_errno = errno; do { s_check = close(s_socket); }while((s_check == (-1)) && (errno == EINTR)); errno = s_save_errno; return(-1); } return(s_socket); }
int hwport_close_generic_netlink(int s_socket) { int s_check; if (s_socket == (-1)) { errno = EINVAL; return(-1); } do { s_check = close(s_socket); }while((s_check == (-1)) && (errno == EINTR)); return(s_check); }