小規模時の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
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との関係は
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)
ユーザ定義チェインについて

ユーザ定義チェイン
ユーザ定義チェインは先頭と末尾に固定の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

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つある場合には

のように格納される
hook_entriesにchain内の最初の要素、under_flowにchain内の最後の要素が格納される。
ipt_entry
ipt_entryにはルール一つが入る。 ルールには汎用マッチと(iptablesの-iや-o, -sなどの全てのルールに固有の情報)と、 -m等で明示的に指定するマッチがある(暗黙的なマッチも明示的なマッチになる)

マッチは複数入り、-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!