Netlink socket에 대하여

개요


Netlink 는 Kernel과 User space간 원활한 정보교류를 위한 비교적 유연한 통신 방식입니다.

본 내용을 이해하기 위해서 다음의 Linux kernel header 를 먼저 열어보시고 함께 보시면 좋습니다.
  • "<linux/netlink.h>"
    #define NETLINK_<XXX> ...
    #define NLM_F_<XXX> ...
    
    #define NLMSG_ALIGNTO   4U
    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
    #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
    #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
    #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
    #define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
    #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
                               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                               (nlh)->nlmsg_len <= (len))
    #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
    
    #define NLMSG_NOOP              0x1     /* Nothing.             */
    #define NLMSG_ERROR             0x2     /* Error                */
    #define NLMSG_DONE              0x3     /* End of a dump        */
    #define NLMSG_OVERRUN           0x4     /* Data lost            */
    
    #define NLMSG_MIN_TYPE          0x10    /* < 0x10: reserved control messages */
    
    #define NLA_ALIGNTO             4
    #define NLA_ALIGN(len)          (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
    #define NLA_HDRLEN              ((int) NLA_ALIGN(sizeof(struct nlattr)))
    
    struct sockaddr_nl { ... };
    struct nlmsghdr { ... };
    struct nlattr { ... };
    
  • "<linux/genetlink.h>"
    #define GENL_HDRLEN     NLMSG_ALIGN(sizeof(struct genlmsghdr))
    #define GENL_ID_CTRL            NLMSG_MIN_TYPE
    
    enum {
    ...
            CTRL_ATTR_FAMILY_ID,
            CTRL_ATTR_FAMILY_NAME,
    ...
    };
    
    struct genlmsghdr { ... };
    
  • "<linux/rtnetlink.h>"
    #define RTA_ALIGNTO     4U
    #define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) )
    #define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \
                             (rta)->rta_len >= sizeof(struct rtattr) && \
                             (rta)->rta_len <= (len))
    #define RTA_NEXT(rta,attrlen)   ((attrlen) -= RTA_ALIGN((rta)->rta_len), \
                                     (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
    #define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len))
    #define RTA_SPACE(len)  RTA_ALIGN(RTA_LENGTH(len))
    #define RTA_DATA(rta)   ((void*)(((char*)(rta)) + RTA_LENGTH(0)))
    #define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
    
    struct rtattr { ... };
    struct rtmsg { ... };
    
  • "<linux/if_addr.h>"
  • "<linux/if_link.h>"
  • "<linux/netconf.h>"
  • "<linux/neighbour.h>"
  • "<linux/xfrm.h>" : (Transform) IPSec VPN에 관련한 Netlink 정의

  • 예제소스
  • netlink message format


    기본적으로 Netlink message는 하나 이상을 연속으로 붙여 하나의 요청(Request) 또는 응답(Response) 단위로 묶어 전송구현합니다.
    
     +----------------------+----------------------+----------------------+----------------------+ ~ ~ ~ ~ ~ +----------------------+
     |  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 ----------------------------------------------->
    
    


    하나의 Netlink message의 Header와 Payload는 각각 정렬(Align)된 형태를 맞추기 위해서 Padding 을 포함할 수 있습니다. (정렬의 크기는 "<linux/netlink.h>" kernel header에 정의된 NLMSG_ALIGNTO 정의를 기준으로 하며 NLMSG_ALIGN macro 함수를 사용하여 계산할 수 있습니다.)

    • Netlink message format
      Netlink message format
      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)

    • Netlink message overview
        <----- 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로 구성되는게 일반적입니다.
      


    • Netlink attribute overview (Netlink message payload 내에서 Specific data 뒷부분에 선택적으로 추가됨)
        <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
       +---------------------+- - -+- - - - - - - - - -+- - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
       |        Header       | Pad |     Payload       | Pad | ... (Next attribute) ...
       |   (struct nlattr)   | ing |                   | ing |
       +---------------------+- - -+- - - - - - - - - -+- - -+ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
       ↑ nlattr
        <-------------- nlattr->nla_len -------------->
      


    • netlink header struct
       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 */
       };
      


    • netlink attribute header struct
       struct nlattr {
           uint16_t nla_len; /* Header를 포함한 attribute 크기 */
           uint16_t nla_type; /* Attribute type */
       };
      


    Generic Netlink message Overview


      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            |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    


    "iotop" 명령어가 task 상태에 대한 모니터링을 위한 구현을 위해서 Generic Netlink를 사용하는 대표적 사용예입니다.

    구현사항에 대한 간략한 골격구조 설명

    1. Netlink socket을 XFRM쪽으로 open
      s_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM /* 6 - ipsec */);
      


    2. XFRM모듈쪽의 어떤 Group 과 통신할지를 binding
      __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));
      


    3. netlink socket으로부터 Netlink protocol RAW 수신
      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)
      );
      


    4. 수신된 Netlink protocol RAW data에서 Netlink header 를 통해서 각 요소별 분리
      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) { /* 각 메세지의 종류별로 다른 파싱구조를 가지고 있으므로 커널을 참조하여 해당 부분을 파싱해야 합니다. */
              .....
          }
      }
      


    실제 VPN 장비에서 VPN연결과정에서 본 예제프로그램으로 Netlink 통신을 수신하여 파싱된 내용을 모니터링한 콘솔내용

    내용을 보시려면 여기를 클릭해주세요.

    


    /*
    [ FrontPage | PrintView | RawView | RSS ]

    Copyright ⓒ MINZKN.COM
    All Rights Reserved.

    MINZKN
    */