totofugaのブログ

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

小規模時のtcp_probeについて

輳制御を確認してグラフ化するのに最近tcp_probeを使用しています。

iperfとかで試す時には良いのですが、少ないパケットを送って

cat /proc/net/tcpprobe

等試すと全く何も表示されません。 (full=1オプションをつけてもダメ。。)

このままだと小規模で使えないので調べてみると

readで指定したバッファサイズ以上パケットがこないと処理をかえさなくなってました。 ということで通常のreadと同じようにパケットがあれば即返すように変更。

diff --git a/net/ipv4/tcp_probe.c b/net/ipv4/tcp_probe.c
index 3d063eb..309ff0a 100644
--- a/net/ipv4/tcp_probe.c
+++ b/net/ipv4/tcp_probe.c
@@ -207,17 +210,18 @@ static ssize_t tcpprobe_read(struct file *file, char __user *buf,
        if (!buf)
                return -EINVAL;
 
-       while (cnt < len) {
+       while (cnt == 0 || tcp_probe.head != tcp_probe.tail) {
                char tbuf[256];
                int width;
 
                /* Wait for data in buffer */
                error = wait_event_interruptible(tcp_probe.wait,
                                                 tcp_probe_used() > 0);
-               if (error)
-                       break;
-
+        if (error)
+            return error;
+        
                spin_lock_bh(&tcp_probe.lock);
+        /* double-checked locking */
                if (tcp_probe.head == tcp_probe.tail) {
                        /* multiple readers race? */
                        spin_unlock_bh(&tcp_probe.lock);
@@ -225,20 +229,20 @@ static ssize_t tcpprobe_read(struct file *file, char __user *buf,
                }
 
                width = tcpprobe_sprint(tbuf, sizeof(tbuf));
-
-               if (cnt + width < len)
-                       tcp_probe.tail = (tcp_probe.tail + 1) & (bufsize - 1);
-
                spin_unlock_bh(&tcp_probe.lock);
 
                /* if record greater than space available
                   return partial buffer (so far) */
-               if (cnt + width >= len)
+               if (cnt + width > len)
                        break;
 
+        tcp_probe.tail = (tcp_probe.tail + 1) & (bufsize - 1);
                if (copy_to_user(buf + cnt, tbuf, width))
                        return -EFAULT;
                cnt += width;
+
+               if (cnt ==  len) 
+                       break;
        }
 
        return cnt == 0 ? error : cnt;

tcp_probeについての使い方はこの辺りを参考にしてください https://wiki.linuxfoundation.org/networking/tcpprobe:tcpprobe

tcp_probeが入っていない場合はkernelのビルド時 CONFIG_NET_TCPPROBE=mに設定してください。

ip link経由でのドライバ追加

家にあるLinuxデバイスドライバの本のネットワークドライバのサンプルが古すぎて動かないので 4.9.57で動くようにしてついでに今風にipコマンド経由で追加できるように書き換えてみた。

最低限動くようにするところだけ移行。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <net/rtnetlink.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/etherdevice.h>

MODULE_LICENSE("GPL");

#define MYQUEUE_RX_INTR 1

struct mynet_packet {
    struct mynet_packet *next;
    struct net_device *dev;
    int datalen;
    u8 data[ETH_DATA_LEN];
};

int mynet_newlink(struct net *net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]);

static int pool_size = 8;

struct mynet_private {
    struct net_device *peer;
    int is_peer;
    struct mynet_packet *ppool;
    struct mynet_packet *rx_queue;
    spinlock_t lock;
    int status;
};

static void mynet_setup_pool(struct net_device *dev) {
    struct mynet_private *priv = netdev_priv(dev);
    struct mynet_packet *pkt;
    int i;
    priv->ppool = NULL;
    for ( i = 0; i < pool_size; ++i ) {
        pkt = kmalloc(sizeof(struct mynet_packet), GFP_KERNEL);
        if ( !pkt ) {
            printk(KERN_ERR "mynet setup pool error");
            return;
        }
        pkt->next = priv->ppool;
        pkt->dev = dev;
        pkt->datalen = 0;
        priv->ppool = pkt;
    }
}

static struct mynet_packet* mynet_get_tx_buffer(struct net_device *dev) {
    struct mynet_private *priv = netdev_priv(dev);
    struct mynet_packet *pkt;
    unsigned long flags;
    spin_lock_irqsave(&priv->lock, flags);
    pkt = priv->ppool;
    priv->ppool = pkt->next;
    pkt->next = NULL;

    if ( priv->ppool == NULL ) {
        netif_stop_queue(dev);
    }
    spin_unlock_irqrestore(&priv->lock, flags);
    return pkt;
}

void mynet_enqueue_buf(struct net_device *dev, struct mynet_packet *pkt) {
    unsigned long flag;
    struct mynet_private *priv = netdev_priv(dev);
    spin_lock_irqsave(&priv->lock, flag);
    pkt->next = priv->rx_queue;
    priv->rx_queue = pkt;
    spin_unlock_irqrestore(&priv->lock, flag);
}

void mynet_release_buffer(struct mynet_packet *pkt) {
    struct mynet_private *priv = netdev_priv(pkt->dev);
    unsigned long flags;
    spin_lock_irqsave(&priv->lock, flags);
    pkt->next = priv->ppool;
    priv->ppool = pkt;
    spin_unlock_irqrestore(&priv->lock, flags);
    if ( netif_queue_stopped(pkt->dev) && pkt->next == NULL ) {
        netif_wake_queue(pkt->dev);
    }

}

void print_mac(char *mac) {
    unsigned char buff[1024];
    int i, k = 0;

    for ( i = 0; i < ETH_ALEN; ++i ) {
        buff[++k] = hex_asc_hi(mac[i]);
        buff[++k] = hex_asc_hi(mac[i]);
        buff[++k] = ':';
    }

    buff[k-1] = '\0';
    printk("%s", buff);
}

static void mynet_rx(struct net_device *dev, struct mynet_packet *pkt) {
    struct sk_buff *skb;

    struct iphdr *iph;

    printk(KERN_DEBUG "mynet_rx");
    skb = dev_alloc_skb(pkt->datalen + 2);

    skb_reserve(skb, 2);
    iph = (struct iphdr*)(skb->data + sizeof(struct ethhdr));
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
    skb->dev = dev;
    skb->protocol = eth_type_trans(skb, dev);
    skb->ip_summed = CHECKSUM_UNNECESSARY;

    printk(KERN_DEBUG "||||protocol %d\n", skb->protocol);
    printk(KERN_DEBUG "||||len %d", pkt->datalen);
    printk(KERN_DEBUG "||||saddr : %pI4", &iph->saddr);
    printk(KERN_DEBUG "||||daddr : %pI4", &iph->daddr);

    netif_rx(skb);
}

static void mynet_interrupt(struct net_device *dev) {
    struct mynet_private *priv = netdev_priv(dev);
    struct mynet_packet *pkt = NULL;
    int status = priv->status;
    priv->status = 0;

    spin_lock(&priv->lock);
    if ( status & MYQUEUE_RX_INTR ) {
        pkt = priv->rx_queue;
        if ( pkt ) {
            priv->rx_queue = pkt->next;
            mynet_rx(dev, pkt);
        }
    }

    spin_unlock(&priv->lock);
}

netdev_tx_t mynet_start_xmit(struct sk_buff *skb, struct net_device *dev) {
    struct ethhdr *eh;
    struct iphdr *iph;
    struct mynet_private *priv, *peer_priv;
    u32 *saddr, *daddr;
    struct mynet_packet *tx_buffer;

    priv = netdev_priv(dev);
    peer_priv = netdev_priv(priv->peer);

    printk(KERN_WARNING "mynet xmit %s", dev->name);

    if ( !pskb_may_pull(skb, sizeof(struct iphdr) + sizeof(struct ethhdr)) ) {
        printk(KERN_DEBUG "can no read ether header\n");
        return NETDEV_TX_OK;
    }

    eh = (struct ethhdr*)skb->data;
    memcpy(eh->h_dest, priv->peer->dev_addr, ETH_ALEN);
    memcpy(eh->h_source, dev->dev_addr, ETH_ALEN);

    printk(KERN_DEBUG "addr len %d", dev->addr_len);
    printk(KERN_DEBUG "hard header len %d", dev->hard_header_len);
    iph = (struct iphdr*)((skb->data) + sizeof(struct ethhdr));
    printk(KERN_DEBUG "version: %d", iph->version);
    printk(KERN_DEBUG "saddr : %pI4", &iph->saddr);
    printk(KERN_DEBUG "daddr : %pI4", &iph->daddr);

    saddr = &iph->saddr;
    daddr = &iph->daddr;
    ((u8 *)saddr)[2] ^= 1;
    ((u8 *)daddr)[2] ^= 1;

    printk(KERN_DEBUG "=> saddr : %pI4", &iph->saddr);
    printk(KERN_DEBUG "=> daddr : %pI4", &iph->daddr);

    iph->check = 0;
    iph->check = ip_fast_csum((unsigned char*)iph, iph->ihl);

    tx_buffer = mynet_get_tx_buffer(dev);
    tx_buffer->datalen = skb->len;
    printk(KERN_DEBUG "||||len %d", skb->len);

    memcpy(tx_buffer->data, skb->data, skb->len);
    mynet_enqueue_buf(priv->peer, tx_buffer);

    peer_priv->status |= MYQUEUE_RX_INTR;
    mynet_interrupt(priv->peer);

    dev_kfree_skb(skb);

    return NETDEV_TX_OK;
}

int mynet_open(struct net_device *dev) {
    memcpy(dev->dev_addr, "\0TEST0", ETH_ALEN);
    if ( ((struct mynet_private*)netdev_priv(dev))->is_peer ) {
        dev->dev_addr[ETH_ALEN -1]++;
    }
    netif_start_queue(dev);
    return 0;
}

static struct net_device_ops mynet_ops = {
    .ndo_start_xmit = mynet_start_xmit,
    .ndo_open =  mynet_open,
};

void mynet_setup(struct net_device *dev) {
    struct mynet_private *priv;
    ether_setup(dev);
    dev->netdev_ops = &mynet_ops;
    dev->destructor = free_netdev;
    dev->flags = IFF_NOARP;
    mynet_setup_pool(dev);

    priv = netdev_priv(dev);
    spin_lock_init(&priv->lock);

}

void mynet_dellink(struct net_device *dev, struct list_head *head) {
    unregister_netdevice_queue(((struct mynet_private*)netdev_priv(dev))->peer, head);
    unregister_netdevice_queue(dev, head);
}

static struct rtnl_link_ops mynet_link_ops = {
    .kind = "mynet",
    .setup = mynet_setup,
    .newlink = mynet_newlink,
    .dellink = mynet_dellink,
    .priv_size  = sizeof(struct mynet_private),
};

int mynet_newlink(struct net *net, struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]){
    struct net_device *peer = rtnl_create_link(net, "mynetpeer_%d", NET_NAME_ENUM, &mynet_link_ops, tb);
    ((struct mynet_private*)netdev_priv(peer))->is_peer = 1;
    printk("mynet newlink %p", peer);

    ((struct mynet_private*)netdev_priv(dev))->peer = peer;
    ((struct mynet_private*)netdev_priv(peer))->peer = dev;

    register_netdevice(dev);
    register_netdevice(peer);

    return 0;
}

static int init_mynet(void) {
    printk(KERN_DEBUG "===========-mynet init==========");
    return rtnl_link_register(&mynet_link_ops);
}

static void exit_mynet(void) {
    rtnl_link_unregister(&mynet_link_ops);
}

module_init(init_mynet);
module_exit(exit_mynet);

追加するスクリプトはこんな感じ

#!/bin/sh
FLAG_D=0

while getopts d OPT
do
    case $OPT in
        d) FLAG_D=1
        ;;
    esac
done

if [ $FLAG_D -eq 0 ];
then
    echo "install"
    insmod mynet.ko
    ip link add mynet0 type mynet
    ip link set mynet0 up
    ip link set mynetpeer_0 up

    ip addr add dev mynet0 1.1.0.1/24
    ip addr add dev mynetpeer_0 1.1.1.2/24
else
    echo "uninstall"
    ip link del mynet0
    rmmod mynet
fi    

ping 1.1.0.2でpingが通ることが確認できる

conntrackの勉強(1)

conntrackのフック

IPTableと同じようにNetFilter経由で実装されている。

使用しているフックは

  • PREROUTING => ipv4_conntrack_in
  • LOCAL_OUT => ipv4_conntrack_local
  • POSTROUTING => ipv4_confirm
  • NF_INET_LOCAL_IN => ipv4_confirm

となっている。

フックの優先順位

優先順位は

enum nf_ip_hook_priorities {
...
    NF_IP_PRI_CONNTRACK = -200,           
    NF_IP_PRI_MANGLE = -150,              
    NF_IP_PRI_NAT_DST = -100,             
    NF_IP_PRI_FILTER = 0,                 
    NF_IP_PRI_SECURITY = 50,              
    NF_IP_PRI_NAT_SRC = 100,              
    NF_IP_PRI_SELINUX_LAST = 225,         
    NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
...
}

のようになっており、 iptablesとの関係は f:id:totofuga:20171125202918p:plain PREROUTING, OUTPUTでIPTABLESの処理に入る前に 必ずconntrackを作るをようになっており、 POSTROUTING,INPUTで登録を行うが、こちらはIPTABLESでフィルタをかけられたものは登録されないようになっている。

nf_conntrackとsk_buffの関連付け

PREROUTINGとINPUTのskbufにconntrackの関連付けを行う skbufに結び付けられるconntrackのパラメータは以下の二つ

struct sk_buff {
...
    struct nf_conntrack *nfct;
    __u8 nfctinfo:3;
...
}

nf_conntrackは実際にはnf_connが入っている。 nfctinfoはステータスが入り、ステータスは以下の5パターンがある

  • IP_CT_ESTABLISHED,
  • IP_CT_RELATED,
  • IP_CT_NEW,
  • IP_CT_IS_REPLY + IP_CT_ESTABLISHED
  • IP_CT_IS_REPLY + IP_CT_RELATED

nf_conntrack_tuple

nf_connに結びついていてるハッシュのに使われるタプル情報 conntrackでは行きと戻り、両方をペアとして保存する必要があるため、 nc_conn構造体には2つのタプル情報がある

struct nf_conntrack_tuple_hash {        
    struct hlist_nulls_node hnnode;     
    struct nf_conntrack_tuple tuple;    
};           

struct nf_conn {
...
    struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
....
}

enum ip_conntrack_dir {
    IP_CT_DIR_ORIGINAL,
    IP_CT_DIR_REPLY,
    IP_CT_DIR_MAX
};    

IP_CT_DIR_ORIGINALが行きパケットで、IP_CT_REPLYが戻りパケットになる。

nf_conntrack_tupleには以下の情報が入っている

実際にハッシュ値を生成するときはdirectionは含まれておらず、 これはtupleからnf_conntrackを取得する際に使われる。 directionがわかることで以下のようにnf_connの位置がわかりtupleからnf_connを取得できる

static inline struct nf_conn *                                        
nf_ct_tuplehash_to_ctrack(const struct nf_conntrack_tuple_hash *hash) 
{                                                                     
    return container_of(hash, struct nf_conn,                         
                tuplehash[hash->tuple.dst.dir]);                      
}                                                                     

tupleの管理

tupleはネームスペースごとに管理される net->netns_ct->hashがハッシュ情報となる。 ハッシュに入るのはPOSTROUTINGとINPUT時なのでそれまでの間、 ハッシュに入っていないtuple情報はnet->netns_ct->unconfirmedで管理される。

insertは__nf_conntrack_hash_insertで行われる。 行きと戻りセットで入れるため、ORIGINALとREPLY両方のinsertが行われる。

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!