totofugaのブログ

ネットワークとかc言語、perlの話。

iptablesの勉強(4)

ユーザ定義チェインについて

f:id:totofuga:20171119191701p:plain

ユーザ定義チェイン

ユーザ定義チェインは先頭と末尾に固定のipt_entryを持ったエントリとして定義される。

先頭のipt_entryはチェインの名前を示す。 チェインの名前はxt_get_revision.nameに格納される。

末尾のipt_entryはチェインから戻るための情報を持っていて xt_standard_targetとなる。 ビルドインチェインの場合末尾にはポリシーによってACCEPTやDROPが入っていたが、 ユーザ定義チェインの場合はチェインから戻るためのRETURNが入る。

ユーザ定義チェインへのジャンプ

チェインへのJUMPターゲット(-j <ユーザ定義チェイン>)を指定した場合、 ユーザ空間のiptablesコマンド内で、ユーザ定義名ではなく、xt_standard_targetのverdictとして次のエントリへのオフセットが格納される

struct xt_standard_target {        
    struct xt_entry_target target; 
    int verdict;                   
};

verdictの値はカーネル空間でチェックされ、同時に無限ループしていないかもチェックされる。

スタックについて

スタックのデータ構造

xt_table_infoにはJUMPから戻れるようにスタックを用意している。

    unsigned int stacksize;
    unsigned int __percpu *stackptr;
    void ***jumpstack;
  • stacksize => チェインはループすることがないのでスタックサイズはユーザチェインの数となる。
  • stackptr => 現在のスタック位置(CPUごとに保持する)
  • jumpstack[cpu個数][stacksize] => jumpした時のipt_entryのポインタをCPUごとに保存する

ipt_do_tableにて以下のように設定と取り出しを行なっている

jumpstack  = (struct ipt_entry **)xt_table_info->jumpstack[cpu];

// JUMP時
jumpstack[(*stackptr)++] = e;

// RETURN時
e = jumpstack[--*stackptr]; 
e = ipt_next_entry(e);

スタックサイズの決定

スタックサイズはnet/ipv4/netfilter/ip_tables.c:translate_table内で XT_ERROR_TARGETの数をカウントしている。 XT_ERROR_TARGETはtargetとして設定されることがないので XT_ERROR_TARGETの数 == チェインの数となる模様

このターゲットに処理が来ることはないが、もしも処理が来た場合には ログを出してドロップする処理になっている。

ユーザ定義チェインのループチェック

ユーザ定義チェインは同じチェインにループしないように

do_ipt_set_ctl
    do_replace
        translate_table
            mark_source_chains

にてチェインのループチェックをおこなっている。

iptablesの勉強(3)

引き続きiptablesの勉強中

今回はカーネルのデータ構造とiptablesで表示されるデータについて

カーネル内でのデータの保持

カーネル内ではxt_tableというデータで保持されている

タスクからのアクセスはnetns_ipv4経由で

task_struct->ns_proxy->net_ns->ipv4

struct netns_ipv4 {
...
    struct xt_table     *iptable_filter; 
    struct xt_table     *iptable_mangle; 
    struct xt_table     *iptable_raw;    
...
}

にてそれぞれテーブルごとのxt_tableにアクセスできる。

xt_table

f:id:totofuga:20171118195832p:plain

xt_tableではカウンタの更新時にロックを取らないようにするため CPUごとにテーブル情報をコピーして保存している

valid_hookにはテーブルで有効なチェインのビットフラグ

enum nf_inet_hooks {      
    NF_INET_PRE_ROUTING,  
    NF_INET_LOCAL_IN,     
    NF_INET_FORWARD,      
    NF_INET_LOCAL_OUT,    
    NF_INET_POST_ROUTING, 
    NF_INET_NUMHOOKS      
};

nameには"filter"等のテーブル名が入っている

xt_table_info

xt_table_infoにはテーブルごとの情報が入っている 例えば

[root@localhost linux-2.6.39]# iptables -L 
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
           all  --  anywhere             anywhere            
           all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
           all  --  anywhere             anywhere            
           all  --  anywhere             anywhere            
           all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
           all  --  anywhere             anywhere   

のようにINPUT2つ,FORWARD3つ,OUTPUT1つある場合には

f:id:totofuga:20171118200405p:plain

のように格納される

hook_entriesにchain内の最初の要素、under_flowにchain内の最後の要素が格納される。

ipt_entry

ipt_entryにはルール一つが入る。 ルールには汎用マッチと(iptablesの-iや-o, -sなどの全てのルールに固有の情報)と、 -m等で明示的に指定するマッチがある(暗黙的なマッチも明示的なマッチになる)

f:id:totofuga:20171118201956p:plain

マッチは複数入り、-jで指定するターゲットは1つだけとなる。 マッチへのアクセスはelemsにて、 次のマッチへのアクセスはu.match_size分シフト。 target_offsetまで行くとターゲットとなる。

ソースルーティング

IPのオプションを指定してLinuxで経路を指定してパケットを出す方法を調べてみた

設定方法

IPのオプションを指定する方法は以下の2通りがある

  • ソケット単位に指定する setsockoptでIP_OPTIONSを指定する

  • パケット単位で設定を変える sendmsgのstruct msghdr->msg_controlにIP_RETOPTのCMSGを設定する

今回はソケット単位に指定する方法を使用

ソース

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/udp.h>

#include <linux/ip.h>

void main() {
    unsigned char options[256] = {0};
    options[0] = IPOPT_LSRR; // type
    int len = 11;
    options[1] = len; // len;
    options[2] = 4; // pointer

    struct in_addr addr;
    inet_aton("1.1.1.1", &addr);
    *((uint32_t*)(options + 3))  = addr.s_addr;

    inet_aton("2.2.2.2", &addr);
    *((uint32_t*)(options + 7))  = addr.s_addr;
    options[7] = IPOPT_NOOP;

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if ( sock == -1 ) {
        perror("creat socket error\n");
        exit(1);
    }

    if ( setsockopt(sock, SOL_IP, IP_OPTIONS, options, len) == -1 ) {
        perror("set ip options error\n");
        exit(1);
    }

    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(8080);
    inet_aton("3.3.3.3", &sin.sin_addr);

    char buf[] = "test";
    int ret = sendto(sock, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin));
    printf("%d\n", ret);
}

動作

ソースルーティングはoptionで指定されているものが送信先となる。 この場合1.1.1.1 => 2.2.2.2 => 3.3.3.3という経路になるので 送信先が1.1.1.1となる。 sendで指定されたものは送信先ではなく、経路の最後に追加される。

処理しているのはLinuxカーネルのnet/ipv4/ip_options.c:ip_options_compile周り

ルータの設定

デフォルトではLinuxをルータとして使用している場合ip_forwardを有効にしてもソースルーティングは有効にならない。

echo "1" > /proc/sys/net/ipv4/conf/all/accept_source_route 

を実行して使用したいデバイス

echo "1" > /proc/sys/net/ipv4/conf/<dev>/accept_source_route

sourceルーティングを有効にしておく必要がある

カーネルからユーザ空間にnetlinkブロードキャスト

iptablesのULOGを調べててカーネル空間にnetlinkにブロードキャストを送る方法が気になったので書いてみた

カーネル空間のプログラム

#include <linux/socket.h>                                                     
#include <net/sock.h>                                                         
                                                                              
static struct timer_list my_timer;                                            
static struct sock *sock;                                                     
static int seq;                                                               
                                                                              
MODULE_LICENSE("GPL");                                                        
                                                                              
void my_timer_callback( unsigned long data ) {                                
    char word[] = "hello world!";                                             
    struct sk_buff *skb;                                                      
    int type = 0;                                                             
    struct nlmsghdr *nlh;                                                     
    char *d;                                                                  
    int ret;                                                                  
                                                                              
    printk("timeout\n");                                                      
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));                    
                                                                              
                                                                              
    skb = alloc_skb(1024, GFP_ATOMIC);                                        
    if ( !skb ) {                                                             
        printk("error: skb alloc\n");                                         
        return;                                                               
    }                                                                         
                                                                              
    nlh = NLMSG_PUT(skb, 0, ++seq, type, strlen(word));                       
    d = NLMSG_DATA(nlh);                                                      
    memcpy(d, word, strlen(word));                                            
                                                                              
    ret = netlink_broadcast(sock, skb, 0, 5, GFP_ATOMIC);                                 
                                                                              
    return;                                                                   
                                                                              
nlmsg_failure:                                                                
    printk("error:NLMSG_PUT\n");                                              
    return;                                                                   
}                                                                             
                                                                              
int myrtnl_init(void) {                                                       
                                                                              
    sock = netlink_kernel_create(&init_net, 20, 32, NULL, NULL, THIS_MODULE); 
    if ( !sock ) {                                                            
        printk("error: netlink kernel create\n");                             
        return 1;                                                             
    }                                                                         
                                                                              
    setup_timer(&my_timer, my_timer_callback, 0);                             
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(200));                    
    return 0;                                                                 
}                                                                             
                                                                              
void myrtnl_exit(void) {                                                      
    del_timer(&my_timer);                                                     
    netlink_kernel_release(sock);                                             
}                                                                             
                                                                              
module_init(myrtnl_init);                                                     
module_exit(myrtnl_exit);                                                     

200ミリ秒ごとにNETLINKにブロードキャストを送るプログラム

cat /proc/net/netlink で空いてるをとりあえず使ってみた。(ルールは今度ちゃんと調べる)

netlink_broadcastにsk_buffを渡せば作れる。

NLMSG_PUTというショートカットメソッドも用意されている。

ユーザ空間のプログラム

#include <stdio.h>                                                               
#include <libnetlink.h>                                                          
#include <stdlib.h>                                                              
#include <string.h>                                                              
                                                                                 
int listen_handler(const struct sockaddr_nl *nl, struct nlmsghdr *n, void *arg) {
    char buf[256] = {0};                                                         
    memcpy(buf, NLMSG_DATA(n), 12);                                              
    printf("data: %s\n", buf);                                                   
    return 0;                                                                    
}                                                                                
void main() {                                                                    
    struct rtnl_handle rh;                                                       
    int n = 5;                                                                   
    if ( rtnl_open_byproto(&rh, 1 << (n - 1), 20) != 0 ) {                       
        perror("open error\n");                                                  
        exit(1);                                                                 
    }                                                                            
                                                                                 
    printf("listen start\n");                                                    
    rtnl_listen(&rh, listen_handler, NULL);                                      
}                                                                                

libnetlinkを使った受信

ユーザ空間のグループ指定はビットシフトになるので注意

出力結果

[root@localhost rtnetlink]# ./a.out 
listen start                        
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  
data: hello world!                  

iptables勉強(2)

iptablesのtargetrの拡張

iptablesだいぶわかってきたので最小構成のtargetを書いてみた

構成ファイル

user空間用とkernel空間用の2種類のファイルを用意する必要がある

user空間用ファイル

iptablesコマンドを拡張するために /lib64/xtables/libに共有ライブラリを作成して配置する

名前はlibipt_<target名>.so

エントリポイントは_initという関数を作りxtables_targetを登録する

xtables.hはiptables-develパッケージに含まれている

#include <xtables.h>                                                                        
#include <stdio.h>                                                                          
                                                                                            
static void MYLOG_help() {                                                                  
    // printf("commandオプションがあればここに記述\n");  // iptables -j MYLOG --helpの時に表示される                                   
}                                                                                           
                                                                                            
static void MYLOG_print(const void *ip, const struct xt_entry_target *target, int numeric) {
    printf(" MYLOG");    // iptables -Lの時に表示される名前                                                                
}                                                                                           
                                                                                            
static struct xtables_target my_log_target = {                                              
    .name  = "MYLOG",                                                                       
    .version = XTABLES_VERSION,                                                             
    .family = NFPROTO_IPV4,                                                                 
    .help = MYLOG_help,                                                                     
    .print = MYLOG_print                                                                    
};                                                                                          
                                                                                            
void _init() {                                                                              
    xtables_register_target(&my_log_target);                                                
}                                                                                           

作成したら共有ライブラリとしてコンパイルして/lib64/xtables/libに配置

gcc -fpic -shared ./libipt_MYLOG.c -o libipt_MYLOG.so

kernel空間用ファイル

user空間用ファイルに対応するカーネルモジュールを作成する

今回はヒットしたらログを出すだけ。

ユーザ空間のファイルとの対応はxt_targetのnameで行なっている

#include <linux/module.h>                                                              
#include <linux/netfilter.h>                                                           
#include <linux/netfilter/x_tables.h>                                                  

MODULE_LICENSE("GPL");

static unsigned int log_mylog(struct sk_buff *skb, const struct xt_action_param *par) {
    printk(KERN_ALERT "run mylog\n");
    return XT_CONTINUE;                                                                
}                                                                                      
                                                                                       
static struct xt_target mylog_target __read_mostly = {                                 
    .name       = "MYLOG",                                                             
    .family     = NFPROTO_IPV4,                                                        
    .target     = log_mylog,                                                           
    .me     = THIS_MODULE,                                                             
};  

static int __init mylog_init(void) {                                                   
    return xt_register_target(&mylog_target);                                          
}   

static void __exit mylog_exit(void) {
    xt_unregister_target(&mylog_target);
}

module_init(mylog_init);
module_exit(mylog_exit);

作成したらカーネルモジュールをロード

insmod mylog.ko

iptables

user空間とkernel空間のファイルを作成すると、iptablesコマンドの-jオプションにて自分の作成したターゲットを指定できる

# iptables -A INPUT -j MYLOG
# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
MYLOG      all  --  anywhere             anywhere             MYLOG

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

この状態でアクセスがあるとカーネルログにrun mylogが出力される

iptables勉強

ユーザ空間から IPTablesのチェインの取り出し。

とりあえずなんとなくとれるようになったので今日はここまで。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <net/if.h>
#include <getopt.h>
#include <stdlib.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter/x_tables.h>

int show_ipt_entry(struct ipt_entry * ent, struct ipt_getinfo *info, struct ipt_get_entries *entries) {
    struct xt_entry_target *xt_target = ipt_get_target(ent);

    // user定義チェイン
    if ( strcmp(xt_target->u.user.name, "ERROR") == 0 ) {
        printf("user定義チェイン(%s)\n", (char*)xt_target->data);
        return 0;
    }

    // builtinチェイン
    int i;
    for ( i = 0; i < NF_IP_NUMHOOKS; ++i ) {
        if ( info->valid_hooks & (1 << i) ) { // 有効か確認
            if ( (struct ipt_entry*)((char*)entries->entrytable + info->hook_entry[i]) == ent ) {
                switch( i ) {
                    case NF_IP_PRE_ROUTING:
                        printf("PREROUTING\n");
                        break;
                    case NF_IP_POST_ROUTING:
                        printf("POSTROUTING\n");
                        break;
                    case NF_IP_LOCAL_IN:
                        printf("LOACLIN\n");
                        break;
                    case NF_IP_LOCAL_OUT:
                        printf("LOCALOUT\n");
                        break;
                    case NF_IP_FORWARD:
                        printf("FORWARD\n");
                        break;
                    default:
                        printf("unknown chain\n");
                        break;
                }
            }
        }
    }

    struct xt_standard_target *st = (struct xt_standard_target*)xt_target;
    printf("#### ipt_verdict %d ####\n", st->verdict);

    return 0;
}

void main(int argc, char *argv[]) {

    char table[256] = {};
    strcpy(table, "filter");
    int arg;
    while( (arg = getopt(argc, argv, "t:")) != -1 ) {
        switch(arg) {
            case 't':
                strcpy(table, optarg);
                break;
        }
    }

    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

    struct ipt_getinfo info;
    memset(&info, 0, sizeof(info));
    strcpy(info.name, table);


    int len = sizeof(info);
    if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &len) == -1 ) {
        perror("IPT_SO_GET_INFO error\n");
        return;
    }

    printf("#### ipt_getinfo ####\n");
    printf("name : %s\n", info.name);
    printf("valid_hooks : %d\n", info.valid_hooks);
    printf("underflow: \n");
    printf("\tNF_INET_PRE_ROUTING : %d\n", info.underflow[NF_INET_PRE_ROUTING]);
    printf("\tNF_INET_LOCAL_IN: %d\n", info.underflow[NF_INET_LOCAL_IN]);
    printf("\tNF_INET_FORWARD: %d\n", info.underflow[NF_INET_FORWARD]);
    printf("\tNF_INET_LOCAL_OUT: %d\n", info.underflow[NF_INET_LOCAL_OUT]);
    printf("\tNF_INET_POST_ROUTING: %d\n", info.underflow[NF_INET_POST_ROUTING]);
    printf("num_entries: %d\n", info.num_entries);
    printf("size: %d\n", info.size);

    struct ipt_get_entries *entries;
    len = sizeof(*entries) + info.size;
    entries = calloc(1, len);
    strcpy(entries->name, info.name);
    entries->size = info.size;
    if ( getsockopt(sock, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &len) == -1 ) {
        perror("IPT_SO_GET_ENTRIES error\n");
        return;
    }

    int i;
    for(i = 0; i < NF_INET_NUMHOOKS; ++i) {
        if ( info.underflow[i] == -1 ) {
            continue;
        }
        struct ipt_entry* ent = (struct ipt_entry*)((char*)entries->entrytable + info.underflow[i]);
        struct xt_entry_target *t = ipt_get_target(ent);
        if ( strcmp(t->u.user.name, "") != 0 ) {
            printf("error\n");
        }

        struct xt_standard_target *st = (struct xt_standard_target*)t;
    }

    IPT_ENTRY_ITERATE(entries->entrytable, entries->size, show_ipt_entry, &info, entries);

}

linux arpの内部データ

arpの統計情報が欲しかったのでnetlink経由で取得するものを作った NDTA_PARMSはrtattrが入れ子になっている。

#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/neighbour.h>
#include <linux/rtnetlink.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>

static int seq;

void msg_receive(int sock) {
    char buf[4096]; 

    int n = read(sock, buf, sizeof(buf));
    if ( n <= 0 ) {
        perror("read error\n");
        exit(1);
    }
    struct nlmsghdr *nh = (struct nlmsghdr *)buf;
    // printf("read size: %d\n", n);
    if ( nh->nlmsg_seq != seq ) {
        printf("seq error\n");
        exit(1);
    }
    if ( nh->nlmsg_type == NLMSG_ERROR ) {
        printf("error\n");
        exit(1);
    } else if ( nh->nlmsg_type == RTM_NEWNEIGHTBL ) {
        NLMSG_ALIGN(nh->nlmsg_len);
        struct ndtmsg *ndtm = NLMSG_DATA(nh);
        if ( ndtm->ndtm_family != AF_INET ) {
            printf("ndgm_family is not AF_INET\n");
            exit(1);
        }

        struct rtattr *rta;
        int len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndtmsg));
        for (rta = (struct rtattr*)((char *)ndtm + sizeof(struct ndtmsg)); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
            switch(rta->rta_type) {
                case NDTA_NAME:
                    printf("NAME: %s\n", (char*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH1:
                    printf("THRESH1 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH2:
                    printf("THRESH2 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_THRESH3:
                    printf("THRESH3 %d\n", *(uint32_t*)RTA_DATA(rta));
                    break;
                case NDTA_CONFIG:
                     {
                        struct ndt_config *config = (struct ndt_config*)RTA_DATA(rta);
                        printf("CONFIG:\n");
                        printf("\tkey_len: %d\n", config->ndtc_key_len);
                        printf("\tentry_size: %d\n", config->ndtc_entry_size);
                        printf("\tentries: %d\n", config->ndtc_entries);
                        printf("\tlast_flush: %d\n", config->ndtc_last_flush);
                        printf("\tlast_rand: %d\n", config->ndtc_last_rand);
                        printf("\thash_rnd: %d\n", config->ndtc_hash_rnd);
                        printf("\thash_mask: %d\n", config->ndtc_hash_mask);
                        printf("\thash_chain_gc: %d\n", config->ndtc_hash_chain_gc);
                        printf("\tproxy_qlen: %d\n", config->ndtc_proxy_qlen);
                    }
                    break;
                case NDTA_PARMS:
                    {
                        printf("PARAMS:\n");
                        struct rtattr *parm_rta;
                        int parm_len = rta->rta_len;
                        for (parm_rta = rta + 1; RTA_OK(parm_rta, parm_len); parm_rta = RTA_NEXT(parm_rta, parm_len)) {
                            switch(parm_rta->rta_type) {
                                case NDTPA_IFINDEX:
                                    printf("\tifindex: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_REFCNT:
                                    printf("\trefcnt: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_REACHABLE_TIME:
                                    printf("\treachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_BASE_REACHABLE_TIME:
                                    printf("\tbase_reachable_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_RETRANS_TIME:
                                    printf("\tretrans_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_GC_STALETIME:
                                    printf("\tgc_staletime: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_DELAY_PROBE_TIME:
                                    printf("\tdelay_probe_time: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_QUEUE_LEN:
                                    printf("\tqueue_len: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_APP_PROBES:
                                    printf("\tapp_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_UCAST_PROBES:
                                    printf("\tucast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_MCAST_PROBES:
                                    printf("\tmcast_probes: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_ANYCAST_DELAY:
                                    printf("\tanycast_delay: %lld\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_PROXY_DELAY:
                                    printf("\tproxy_delay: %d\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_PROXY_QLEN:
                                    printf("\tproxy_qlen: %d\n", *(uint32_t*)RTA_DATA(parm_rta));
                                    break;
                                case NDTPA_LOCKTIME:
                                    printf("\tlocktime: %d\n", *(uint64_t*)RTA_DATA(parm_rta));
                                    break;
                            }
                        }
                    }
                    break;
                case NDTA_STATS:
                    {
                        struct ndt_stats *stats = (struct ndt_stats*)RTA_DATA(rta);
                        printf("STATS:\n");
                        printf("\tallocs: %d\n", stats->ndts_allocs);
                        printf("\tdestorys: %d\n", stats->ndts_destroys);
                        printf("\tgrow: %d\n", stats->ndts_hash_grows);
                        printf("\tres_failed: %d\n", stats->ndts_res_failed);
                        printf("\tlookups %d\n", stats->ndts_lookups);
                        printf("\thits: %d\n", stats->ndts_hits);
                        printf("\trcv_probes_mcast: %d\n", stats->ndts_rcv_probes_mcast);
                        printf("\trcv_probes_ucast: %d\n", stats->ndts_rcv_probes_ucast);
                        printf("\tperiodic_gc_runs: %d\n", stats->ndts_periodic_gc_runs);
                        printf("\tforced_gc_runs: %d\n", stats->ndts_forced_gc_runs);
                    }
                    break;
                case NDTA_GC_INTERVAL:
                    printf("GC_INTERVAL %lld\n", *(uint64_t*)RTA_DATA(rta));
                    break;
            }
        }
    }
}

void main() {
    srand(time(NULL));
    seq = rand();

    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
    if ( sock == -1 ) {
        perror("sock error\n");
        exit(1);
    }

    struct {
        struct nlmsghdr nh;
        struct rtgenmsg rt;
    } h;

    struct sockaddr_nl nl;
    memset(&nl, 0, sizeof(nl));
    nl.nl_family = AF_NETLINK;

    if ( bind(sock, (struct sockaddr*)&nl, sizeof(nl)) == -1 ) {
        perror("bind error\n");
        exit(1);
    }

    memset(&h, 0, sizeof(h));
    h.nh.nlmsg_type  = RTM_GETNEIGHTBL;
    h.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
    h.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(h.rt));
    h.nh.nlmsg_seq   = seq;
    h.rt.rtgen_family = AF_INET;

    int n = write(sock, &h, sizeof(h));
    if ( n <= 0 ) {
        perror("write error\n");
        exit(1);
    }
    msg_receive(sock);
}

出力はこんな感じ

NAME: arp_cache
GC_INTERVAL 10000
THRESH1 128
THRESH2 512
THRESH3 1024
CONFIG:
        key_len: 4
        entry_size: 260
        entries: 3
        last_flush: 27997298
        last_rand: 63783
        hash_rnd: 398501669
        hash_mask: 7
        hash_chain_gc: 0
        proxy_qlen: 0
STATS:
        allocs: 49
        destorys: 46
        grow: 0
        res_failed: 25
        lookups 61661
        hits: 48360
        rcv_probes_mcast: 0
        rcv_probes_ucast: 0
        periodic_gc_runs: 2274
        forced_gc_runs: 0
PARAMS:
        refcnt: 1
        queue_len: 3
        proxy_qlen: 64
        app_probes: 0
        ucast_probes: 3
        mcast_probes: 3
        reachable_time: 18336
        base_reachable_time: 20000
        gc_staletime: 60000
        delay_probe_time: 5000
        retrans_time: 1000
        anycast_delay: 1000
        proxy_delay: 800
        locktime: 1000