netfilterを利用しカーネルでパケットを操作する

確認環境

コンパイル環境及び動作確認環境ともに以下の通り

OS:CentOS 7.2.1511 (64bit)
カーネルバージョン:3.10.0-327.el7.x86_64
gcc:4.8.3 20140911

kernel内で受信したパケットを解析する

netfilter(ファイアウォールのAPI)を利用することで解析を行う。

基本的にカーネルでのドライバ起動時に、 パケット受信時/送信時にフックしてもらう関数を登録しておけばパケットを解析することができる。

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>

static struct nf_hook_ops nf_ops_recv;              /* フック関数の登録構造体 */
static struct nf_hook_ops nf_ops_post; /* IPV4パケット送信時にフックする関数用構造体 */
static struct nf_hook_ops nf_ops_arp_in;   /* ARP受信時にフックする関数用構造体 */


int init_module()
{

     // IPv4 パケット受信時に動作する関数の登録
     nf_ops_recv.hook     = ipv4_hook_recv;                         /* フック関数の登録 */
     nf_ops_recv.pf       = NFPROTO_IPV4;                   /* フック対象のインターネットプロトコルファミリー */
     nf_ops_recv.hooknum  = NF_IP_PRE_ROUTING;              /* IPパケット受信時に呼び出す設定 */
     nf_ops_recv.priority = NF_IP_PRI_FIRST;                        /* 最優先で実行される */


     // IPv4 パケット送信時に動作する関数の登録
     nf_ops_post.hook       = ipv4_hook_post;
     nf_ops_post.pf         = NFPROTO_IPV4;
     nf_ops_post.hooknum    = NF_IP_POST_ROUTING;
     nf_ops_post.priority   = NF_IP_PRI_FIRST;

     // ARP受信時
     nf_ops_arp_recv.hook     = arp_hook_recv;
     nf_ops_arp_recv.pf             = NFPROTO_ARP;  /* ARPのProtocol Family*/
     nf_ops_arp_recv.hooknum        = NF_ARP_IN;    /* ARP受信時 */
     nf_ops_arp_recv.priority       = NF_IP_PRI_FIRST; /* 最優先 */

     int reg_r = 0;

     /* フック関数の登録 */
     reg_r = nf_register_hook(&nf_ops_recv);
     if ( reg_r < 0 )
     {
         printk(KERN_ALERT "Failed to register nf_register_hook.(ipv4_recv)");
         return reg_r;
     }

     reg_r = nf_register_hook(&nf_ops_post);
     if ( reg_r < 0 )
     {
         printk(KERN_ALERT "Failed to register nf_register_hook.(ipv4_post)");
         return reg_r;
     }

     reg_r = nf_register_hook(&nf_ops_arp_recv);
     if ( reg_r < 0 )
     {
         printk(KERN_ALERT "Failed to register nf_register_hook.(arp_recv)");
         return reg_r;
     }
 }

 // cleanup時にnetfilterに登録した関数を全て外す必要がある
 void cleanup_module() {

    nf_unregister_hook(&nf_ops_recv);
    nf_unregister_hook(&nf_ops_post);
    nf_unregister_hook(&nf_ops_arp_recv);

 }

登録する関数のプロトタイプ宣言は以下の通り。

1
2
3
4
5
6
7
unsigned int ipv4_hook_recv(
    const struct nf_hook_ops *ops,  // たぶん登録したnf_hook_opsの情報が入っている
    struct sk_buff *skb,            // パケットデータなどが格納される領域、基本的にこのデータを参照する
    const struct net_device *in,    // パケットを受信したデバイス名(ネットワークデバイスに関する情報が入っている)
    const struct net_device *out,   // パケットを送信しようとするデバイス名
    struct nf_hook_state *state     // 未調査
)

Note

パケット受信時に呼ばれる関数内で、outには触らないこと(NULLポインタアクセスで死ぬ)。 同じくパケット送信時に、inにも触らないこと。

パケットの情報は以下のsk_buff構造体に含まれる。 以下、sk_buff構造体のメンバのうち、よく利用するものを抜粋して記載する。 詳細はカーネルのskbuff.hに記載されている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <linux/skbuff.h>

struct sk_buff {
     unsigned char   data;   // パケットデータが含まれるポインタ

     struct net_device      *dev; // パケットを受信した(もしくは送ろうとしている)デバイスの情報

        __be16                      vlan_proto; // 802.1QVLANのプロトコル
        __u16                       vlan_tci;   // VLANの情報(VLANIDなどが入っている)

        __be16                      protocol; // Etherフレームでのプロトコル(例えばARPなら ETH_P_ARPが入る)
        __u16                       mac_len,    // Etherフレームのヘッダの長さ

     // 以下の位置を示すメンバは全てskb->dataからの相対値
        __u16                       transport_header; // トランスポート層のデータが入っている場所を示す
        __u16                       network_header; // ネットワーク層のデータが入っている場所を示す
        __u16                       mac_header; // Etherフレームが入っているデータの場所を示す
}

以上を踏まえて、送受信パケットを解析する方法は以下の通り

  1. nf_hook_ops構造体を準備
  2. nf_hook_registerでフック関数として登録
  3. 登録した関数内でskbのデータを書き換えたり、パケット自体を破棄したりする。

なお、nf_hook_ops構造体のpfに指定するProtocol Familyは、kernel2.6.28以降、以下のように変更されているので注意すること。

PF_INET  -> NFPROTO_IPV4
PF_INET6 -> NFPROTO_IPV6
NF_ARP   -> NFPROTO_ARP
(none)   -> NFPROTO_BRIDGE

ユーザ空間から送信したパケットを変更、解析する

送信用のフック関数を登録し、skbの各種メンバやdataを解析したり書き換えたりすることができる。

送信しようとするパケットの中身を書き換える時の注意事項

  1. 送信しようとするパケットにはEtherヘッダはまだついていないので、MACアドレスやVLANIDを参照したり、書き換えることは不可能。

    MACアドレスはnetfilterよりももっと後の処理で付与されるらしいので、もし送信元MACアドレスを変更したりしたいのであれば、 skbのdataをコピーし、別のskbで自分の付けたいMACアドレスを付与して送信することで対応する。

  2. 送信しようとするパケットのVLANに関する情報をskbから直接取得することはできない。

    VLANに関する情報を取得したい場合、以下の方法が考えられる。

    net_device構造体の名前から、送信しようとするデバイス名を取得し、そのデバイス名が紐づくインターフェースの情報からVLANの情報を取り出す

ドライバから直接パケットを送信する

ドライバから直接パケットを送信する場合、 自分でskbの領域を用意し、以下dev_queue_xmit関数で送信することができる。

Note

自分で組み立てたパケットを送信する場合は、 自分でVLANIDを設定する必要はない。送信先のnet_deviceがVLANだった場合、 自動でVLANIDなどの情報が付与され送信される。 また、IPチェックサムについても自分で計算する必要はない。 送信時に自動的に計算され、格納される。 ネットワーク層より上のTCP/UDPのチェックサムは自分で計算する必要がある。

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
static void SendArpPacket(ETH_HDR* ether_header, ETH_HDR_VLAN* ether_header_vlan, ARP_HDR* arp_header, const struct net_device* out)
{
    struct sk_buff *skb = NULL;
    uint16_t        size_ethhdr = sizeof(ETH_HDR);
    uint16_t        size_arphdr = sizeof(ARP_HDR);
    uint32_t        skb_size = 0;

    int16_t         xmit_val = 0;

    // calc skb size
    if( ether_header_vlan == NULL ) // set non-vlan packet
    {
            skb_size = size_ethhdr + size_arphdr;
    }
    else
    {
            skb_size = size_ethhdr_vlan + size_arphdr;
    }

    // get skb
    skb = alloc_skb( skb_size, GFP_ATOMIC );
    skb_put(skb, skb_size );

    // set skb data
    if( ether_header_vlan == NULL){
            // copy param
            memcpy( skb->data, ether_header, size_ethhdr);
            memcpy( &( ((uint8_t*)(skb->data))[size_ethhdr]), arp_header, size_arphdr);
    }
    else
    {
            // copy param
            memcpy( skb->data, ether_header_vlan, size_ethhdr_vlan);
            memcpy( &( ((uint8_t*)(skb->data))[size_ethhdr_vlan]), arp_header, size_arphdr);
    }

    // set skb param
    {
            skb->mac_header = skb->data;
            skb->dev = out;
            skb->network_header = &( ((uint8_t*)(skb->data))[size_ethhdr]);
            skb->protocol = ether_header->proto;
            skb->mac_len = size_ethhdr;
    }

    // 送信
    xmit_val = dev_queue_xmit(skb);
    if(xmit_val != NET_XMIT_SUCCESS)
    {
            printk(KERN_WARNING "failed to send packet : xmit return value=%d", xmit_val);
    }
    return;
}

ポイントとしては、

  1. alloc_skbのオプションをきちんと選択すること(GFP_ATOMIC、GFP_KERNELなど)
  2. alloc_skbで取得するサイズは、netfilterでフックする関数と異なり、Etherヘッダの構造体サイズも含んだ大きさにすること(Etherヘッダ+ネットワーク層以上のデータサイズ)
  3. skb->mac_header、skb->dev、skb->mac_len, skb->network_header, skb->protocolを設定すること

受信したパケット/送信しようとするパケットのポート変更、転送

Warning

以下のソースコードは変数宣言などを省略しているので、そのままだと動かない

以下は受信時の動作のコードだが、ポート変更処理は送信時もほぼ同様である。

送信時にはポートを書き換えたskbをそのままNF_ACCEPTすれば良い。

  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// [in] :
//          skb                             : sk buffer
// [out] :
//          target_sport    : source port to replace packet's source port
//          target_dport    : destination port to replace packet's destination port
// [return value] :
//          none.
void ExchangeUdpTcpPorts(struct sk_buff* skb, uint16_t target_sport, uint16_t target_dport, uint16_t flag)
{
    struct iphdr* ip_header = NULL;

    ip_header = (struct iphdr*)skb->data;
    uint32_t ip_data_pos = ( ip_header->ihl )* 4;

    if(flag == UDP)
    {
            /* calculate udp header position by ip header length */
            struct udphdr* udp_header = (struct udphdr *)(skb->data + ip_data_pos);
            if(target_sport != 0) { udp_header->source      = ntohs(target_sport); }
            if(target_dport != 0) { udp_header->dest        = ntohs(target_dport); }

            /*calc checksum */
            unsigned short tmp_checksum = 0; // UDPのチェックサム計算処理を入れる
            udp_header->check       = tmp_checksum;
    }
    else
    {
            /* calculate tcp header position by ip header length */
            struct tcphdr* tcp_header = (struct tcphdr *)(skb->data + ip_data_pos);
            if( target_sport != 0) { tcp_header->source     = ntohs(target_sport); }
            if( target_dport != 0) { tcp_header->dest       = ntohs(target_dport); }
            tcp_header->check = 0; // TCPのチェックサム計算処理が必要
        //...
    }
    return;
}

// [in] :
//          skb             : sk buffer
// [out] :
//          sport   : source port
//          dport   : destination port
// [return value] :
//                          RETVAL_BADPACKET                        :error packet
//                          RETVAL_NOTTARGET                        :not target packet
//                          MY_UDP  :UDP
//                          MY_TCP  :TCP
static int32_t GetUdpTcpPorts(const struct sk_buff* skb, uint16_t* sport, uint16_t* dport)
{
    struct iphdr* ip_header = NULL;
    uint16_t packet_proto = 0;      // TCP/UDP flag (return value)
    int32_t retval = RETVAL_NOTTARGET;

    /* initialize */
    *dport = 0;
    *sport = 0;

    /* set ip_header */
    ip_header = (struct iphdr*)(skb->data);

    /* set packet flag */
    packet_proto = ip_header->protocol;

    /* Get packet's protocol */
    {
            if( !(packet_proto == MY_UDP || packet_proto == MY_TCP))
            {
                            retval = RETVAL_NOTTARGET; // ignore not-tcp/udp packet
                            goto END_GETUDPTCPPORTS;
            }
    }

    /* check skb_len */
    uint32_t skb_len = skb_headlen(skb);
    uint32_t ip_data_pos = ( ip_header->ihl )* 4;
    // 長さチェック
    if( ip_data_pos >= skb_len )
    {
            printk(KERN_ALERT "this packet's ip_data_pos is too big. ip_data_pos:%d, skb_len:%d", ip_data_pos, skb_len);
            retval = RETVAL_BADPACKET;
            goto END_GETUDPTCPPORTS;
    }

    /* set dport and sport */
    if(packet_proto == MY_UDP)
    {
            struct udphdr* udp_header = NULL;
            /* calculate udp header position by ip header length */
            udp_header = (struct udphdr *)(( (uint8_t*)(ip_header))+ ip_data_pos);
            *dport = ntohs(udp_header->dest);
            *sport = ntohs(udp_header->source);
            retval = MY_UDP;
    }
    else
    {
            struct tcphdr* tcp_header = NULL;

            /* calculate udp header position by ip header length */
            tcp_header = (struct tcphdr *)( ((uint8_t*)(ip_header)) + ip_data_pos);
            *dport = ntohs(tcp_header->dest);
            *sport = ntohs(tcp_header->source);
            retval = MY_TCP;
    }

END_GETUDPTCPPORTS:
    return retval;
}

unsigned int ipv4_hook_recv(const struct nf_hook_ops *ops,
                              struct sk_buff *skb,
                              const struct net_device *in,
                              const struct net_device *out,
                              struct nf_hook_state *state)
{

    /* ループバックアドレスは無視する */
    if(strcmp(in->name, "lo") == 0){
            return NF_ACCEPT;
    }

    // check length and get ports.
    {
            packet_flag = GetUdpTcpPorts(skb, &sport, &dport); // パケットの送信元、送信先のポートを取得
            if( packet_flag < 0 )
            {
                    printk(KERN_ALERT "not post GetUdpTcpPorts error. packet_flag=%d", packet_flag);
                    return NF_ACCEPT; // ignore this packet.
            }
            else
            {
                    // tcp and to http(80)
                    if(packet_flag == MY_TCP && dport == REWRITE_PORT_FROM_HTTP)
                    {
                            // rewrite dest port to 81
                            //                                      skb     , src, dest, packet_flag
                            ExchangeUdpTcpPorts(sock_buff, 0, REWRITE_PORT_TO_HTTP, (uint16_t)packet_flag); // TCP(80)なら81に変更する
                            // copy send to user space application
                            cpflag = TRUE;
                    }
            }
    }

    /* パケットをユーザ空間に送る */
    if(cpflag) {
        // okfnに変更したskbを渡す。
        // これにより、変更されたskb(のコピー)がユーザ空間に渡される
            state->okfn(state->sk,skb);
    }

    if(cpflag) {
        // ユーザ空間に転送したパケットは自分で処理したので、
        // 通常のパケット処理のルートから外す
            return NF_STOLEN;
    }
    else {
            return NF_ACCEPT;
    }
}

ポイントとしては、

  1. state->okfnにskbを渡した場合は、NF_STOLENを返すこと。
  2. TCP/UDPのチェックサム計算は自動では行われないため、自分でデータを入れること
  3. TCP/UDPデータを参照する場合は、きちんと長さチェックをすること

nf_hook_register内での領域確保について

nf_hook_registerに登録した関数内でkmallocなどの領域確保を行う場合、 GFP_ATOMICで領域を確保する必要がある。

もしGFP_KERNELなどで確保した場合、dmsegから

scheduling while atomic......

というようなログが出力される.

これは、netfilterでのパケット処理がATOMICな処理(割り込みできない処理)であるため。 ATOMICな処理ではsleepできないため、このようなログが出力される。

GFP_ATOMICであれば、メモリ確保に失敗した際 すぐにリターンする。

付録

sk_buff

  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/**
 *  struct sk_buff - socket buffer
 *  @next: Next buffer in list
 *  @prev: Previous buffer in list
 *  @tstamp: Time we arrived/left
 *  @sk: Socket we are owned by
 *  @dev: Device we arrived on/are leaving by
 *  @cb: Control buffer. Free for use by every layer. Put private vars here
 *  @_skb_refdst: destination entry (with norefcount bit)
 *  @sp: the security path, used for xfrm
 *  @len: Length of actual data
 *  @data_len: Data length
 *  @mac_len: Length of link layer header
 *  @hdr_len: writable header length of cloned skb
 *  @csum: Checksum (must include start/offset pair)
 *  @csum_start: Offset from skb->head where checksumming should start
 *  @csum_offset: Offset from csum_start where checksum should be stored
 *  @priority: Packet queueing priority
 *  @ignore_df: allow local fragmentation
 *  @cloned: Head may be cloned (check refcnt to be sure)
 *  @ip_summed: Driver fed us an IP checksum
 *  @nohdr: Payload reference only, must not modify header
 *  @nfctinfo: Relationship of this skb to the connection
 *  @pkt_type: Packet class
 *  @fclone: skbuff clone status
 *  @ipvs_property: skbuff is owned by ipvs
 *  @peeked: this packet has been seen already, so stats have been
 *          done for it, don't do them again
 *  @nf_trace: netfilter packet trace flag
 *  @protocol: Packet protocol from driver
 *  @destructor: Destruct function
 *  @nfct: Associated connection, if any
 *  @nf_bridge: Saved data about a bridged frame - see br_netfilter.c
 *  @skb_iif: ifindex of device we arrived on
 *  @tc_index: Traffic control index
 *  @tc_verd: traffic control verdict
 *  @hash: the packet hash
 *  @queue_mapping: Queue mapping for multiqueue devices
 *  @ndisc_nodetype: router type (from link layer)
 *  @ooo_okay: allow the mapping of a socket to a queue to be changed
 *  @l4_hash: indicate hash is a canonical 4-tuple hash over transport
 *          ports.
 *  @wifi_acked_valid: wifi_acked was set
 *  @wifi_acked: whether frame was acked on wifi or not
 *  @no_fcs:  Request NIC to treat last 4 bytes as Ethernet FCS
 *  @xmit_more: More SKBs are pending for this queue
 *  @napi_id: id of the NAPI struct this skb came from
 *  @secmark: security marking
 *  @mark: Generic packet mark
 *  @dropcount: total number of sk_receive_queue overflows
 *  @vlan_proto: vlan encapsulation protocol
 *  @vlan_tci: vlan tag control information
 *  @inner_protocol: Protocol (encapsulation)
 *  @inner_transport_header: Inner transport layer header (encapsulation)
 *  @inner_network_header: Network layer header (encapsulation)
 *  @inner_mac_header: Link layer header (encapsulation)
 *  @transport_header: Transport layer header
 *  @network_header: Network layer header
 *  @mac_header: Link layer header
 *  @tail: Tail pointer
 *  @end: End pointer
 *  @head: Head of buffer
 *  @data: Data head pointer
 *  @truesize: Buffer size
 *  @users: User count - see {datagram,tcp}.c
 */

struct sk_buff {
    /* These two members must be first. */
    struct sk_buff          *next;
    struct sk_buff          *prev;
#ifdef __GENKSYMS__
    ktime_t         tstamp;
#else
    union {
            ktime_t         tstamp;
            struct skb_mstamp skb_mstamp;
    };
#endif
    struct sock             *sk;
    struct net_device       *dev;

    /*
     * This is the control buffer. It is free to use for every
     * layer. Please put your private variables there. If you
     * want to keep them across layers you have to do a skb_clone()
     * first. This is owned by whoever has the skb queued ATM.
     */
    char                    cb[48] __aligned(8);

    unsigned long           _skb_refdst;
#ifdef CONFIG_XFRM
    struct  sec_path        *sp;
#endif
    unsigned int            len,
                            data_len;
    __u16                   mac_len,
                            hdr_len;
    union {
            __wsum          csum;
            struct {
                    __u16   csum_start;
                    __u16   csum_offset;
            };
    };
    __u32                   priority;
    kmemcheck_bitfield_begin(flags1);
    __u8                    RH_KABI_RENAME(local_df, ignore_df):1,
                            cloned:1,
                            ip_summed:2,
                            nohdr:1,
                            nfctinfo:3;
    __u8                    pkt_type:3,
                            fclone:2,
                            ipvs_property:1,
                            peeked:1,
                            nf_trace:1;
    kmemcheck_bitfield_end(flags1);
    __be16                  protocol;

    void                    (*destructor)(struct sk_buff *skb);
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
    struct nf_conntrack     *nfct;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
    struct nf_bridge_info   *nf_bridge;
#endif

    int                     skb_iif;

    RH_KABI_REPLACE(__u32   rxhash,
                    __u32   hash)

    __be16                  vlan_proto;
    __u16                   vlan_tci;

#ifdef CONFIG_NET_SCHED
    __u16                   tc_index;       /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
    __u16                   tc_verd;        /* traffic control verdict */
#endif
#endif

    __u16                   queue_mapping;
    kmemcheck_bitfield_begin(flags2);
#ifdef CONFIG_IPV6_NDISC_NODETYPE
    __u8                    ndisc_nodetype:2;
#endif
    __u8                    pfmemalloc:1;
    __u8                    ooo_okay:1;
    __u8                    RH_KABI_RENAME(l4_rxhash, l4_hash):1;
    __u8                    wifi_acked_valid:1;
    __u8                    wifi_acked:1;
    __u8                    no_fcs:1;
    __u8                    head_frag:1;
    /* Indicates the inner headers are valid in the skbuff. */
    __u8                    encapsulation:1;
    RH_KABI_EXTEND(__u8                     encap_hdr_csum:1)
    RH_KABI_EXTEND(__u8                     csum_valid:1)
    RH_KABI_EXTEND(__u8                     csum_complete_sw:1)
    RH_KABI_EXTEND(__u8                     xmit_more:1)
    RH_KABI_EXTEND(__u8                     inner_protocol_type:1)
    RH_KABI_EXTEND(__u8                     remcsum_offload:1)
    /* 0/2 bit hole (depending on ndisc_nodetype presence) */
    kmemcheck_bitfield_end(flags2);

#if defined CONFIG_NET_DMA_RH_KABI || defined CONFIG_NET_RX_BUSY_POLL
    union {
            unsigned int    napi_id;
            RH_KABI_DEPRECATE(dma_cookie_t, dma_cookie)
    };
#endif
#ifdef CONFIG_NETWORK_SECMARK
    __u32                   secmark;
#endif
    union {
            __u32           mark;
            __u32           dropcount;
            __u32           reserved_tailroom;
    };

#ifdef __GENKSYMS__
    __be16                  inner_protocol;
#else
    union {
            __be16          inner_protocol;
            __u8            inner_ipproto;
    };
#endif

    __u16                   inner_transport_header;
    __u16                   inner_network_header;
    __u16                   inner_mac_header;
    __u16                   transport_header;
    __u16                   network_header;
    __u16                   mac_header;

    RH_KABI_EXTEND(kmemcheck_bitfield_begin(flags3))
    RH_KABI_EXTEND(__u8     csum_level:2)
    RH_KABI_EXTEND(__u8     rh_csum_pad:1)
    RH_KABI_EXTEND(__u8     csum_bad:1)
    /* 12 bit hole */
    RH_KABI_EXTEND(kmemcheck_bitfield_end(flags3))

    /* RHEL SPECIFIC
     *
     * The following padding has been inserted before ABI freeze to
     * allow extending the structure while preserve ABI. Feel free
     * to replace reserved slots with required structure field
     * additions of your backport.
     */
    u32                     rh_reserved1;
    u32                     rh_reserved2;
    u32                     rh_reserved3;
    u32                     rh_reserved4;

    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t          tail;
    sk_buff_data_t          end;
    unsigned char           *head,
                            *data;
    unsigned int            truesize;
    atomic_t                users;
};

ethhdr

#include <linux/if_ether.h>
1
2
3
4
5
6
7
8
/*
 *  This is an Ethernet frame header.
 */
struct ethhdr {
    unsigned char   h_dest[ETH_ALEN];       /* destination eth addr */
    unsigned char   h_source[ETH_ALEN];     /* source ether addr    */
    __be16          h_proto;                /* packet type ID field */
} __attribute__((packed));

vlan_ethhdr

#include <linux/if_vlan.h>
struct vlan_ethhdr {
    unsigned char h_dest[ETH_ALEN];
    unsigned char h_source[ETH_ALEN];
    __be16  h_vlan_proto;
    __be16  h_vlan_TCI;
    __be16  h_vlan_encapsulated_proto;
};

iphdr

#include<linux/ip.h>

tcphdr

#include<linux/tcp.h>
 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
32
33
34
 struct tcphdr {
     __be16  source;
     __be16  dest;
     __be32  seq;
     __be32  ack_seq;
 #if defined(__LITTLE_ENDIAN_BITFIELD)
     __u16   res1:4,
             doff:4,
             fin:1,
             syn:1,
             rst:1,
             psh:1,
             ack:1,
             urg:1,
             ece:1,
             cwr:1;
 #elif defined(__BIG_ENDIAN_BITFIELD)
     __u16   doff:4,
             res1:4,
             cwr:1,
             ece:1,
             urg:1,
             ack:1,
             psh:1,
             rst:1,
             syn:1,
             fin:1;
 #else
 #error      "Adjust your <asm/byteorder.h> defines"
 #endif
     __be16  window;
     __sum16 check;
     __be16  urg_ptr;
 };

udphdr

#include <linux/udp.h>

kernelにおけるスレッド作成方法

Warning

旧kernelで使っていた、kernel_threadは使えなくなるので以下の方法でスレッドを起こすこと

#include <linux/kthread.h>
/* Thread to send udp packet*/
static int KickSendUdpPacket(void *data)
{
    while(!kthread_should_stop())
    {
            SLEEP_MILLI_SEC(1000);
            printk(KERN_ALERT "%s\n", THREAD_TEST_DAT );
    }
    return 0;
}

    SendUDPThread = kthread_run(KickSendUdpPacket,"hello thread","sendudpthread");
// 以下の手順でも良い
// create thread
SendUDPThread = kthread_create(KickSendUdpPacket, "hello thread", "sendudpthread");
// start thread
wake_up_process(SendUDPThread);

skbやnet_device 関連のマクロや関数

skbの関連

1
2
3
4
5
6
7
8
9
// skbのdataの長さ
// ヘッダの長さではない
uint32_t skb_len = skb_headlen(skb);

// dataのmacヘッダを取り出す
    struct ethhdr* log_eth_header = ((struct ethhdr*)( skb_mac_header(skb) ));

// vlan_idを取り出す
    vlan_id = skb_vlan_tag_get_id(skb);

net_device関連

1
2
3
// デバイス名からnet_device構造体を検索して取り出す. init_netをシステムで定義済みの、デバイス一覧
#define DEV_TARGET "eth0"
out = dev_get_by_name(&init_net, DEV_TARGET);

kernelでのタイマー利用方法

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static struct       timer_list my_timer;   // タイマーリスト
static int          timeout         = 10000;        // 10 sec
static int          timerstop       = 0;            // timer stop flag
static void MyTimer_Handler(unsigned long data);
static void MyTimer_unregister(void);


// timerの登録
static void MyTimer_register(void)
{
    init_timer( &my_timer );
    my_timer.expires        = jiffies + timeout; // タイムアウト時間を設定
    my_timer.data           = (unsigned long )jiffies;
    my_timer.function       = MyTimer_Handler;
    add_timer( &my_timer );
}

// expire時に実行される関数
static void MyTimer_Handler(unsigned long data)
{
    if( timerstop ) {
            printk(KERN_INFO "Timer is not to be restarted.");
            return;
    }
    // タイマー再登録
    MyTimer_register();
}

// タイマー削除
static void MyTimer_unregister(void)
{
    timerstop = 1;
    del_timer_sync( &my_timer );
}


int init_module() // Timerの登録
{
    MyTimer_register();
    return 0;
}
void cleanup_module() // Timerの解除
{
            MyTimer_unregister();
}

kernelでのmutex利用方法

#include <linux/mutex.h>

// mutexの定義
static DEFINE_MUTEX(rwlock_sendudp);

// mutexのロック
mutex_lock(&rwlock_sendudp);

// mutexのアンロック
    mutex_unlock(&rwlock_sendudp);

kernelでの便利なprintフォーマット

// MACアドレスの表示 %pMで 00:01:02:03:04:05(6バイト分表示してくれる)
    printk(KERN_ALERT "dest_mac:%pM, source_mac:%pM, vlan_tag:%d, mac_len=%d", log_eth_header->h_dest, log_eth_header->h_source, vlan_id, sock_buff->mac_len );

// IPアドレスの表示 %pI4 で 192.168.0.1
printk(KERN_ALERT "send ip:%pI4, tgt_ip:%pI4", (struct in_addr*)(&(ip_dat.saddr)),(struct in_addr*)(&(ip_dat.daddr)));

// IPv6アドレスの表示
struct in6_addr in6;
printk("IPv6: %pI6\n", &in6);

Netfilterのフックタイミング

  [INPUT]--->[1]--->[ROUTE]--->[3]--->[4]--->[OUTPUT]
                       |            ^
                       |            |
                       |         [ROUTE]
                       v            |
                      [2]          [5]
                       |            ^
                       |            |
                       v            |
                    [INPUT*]    [OUTPUT*]
[1]  NF_IP_PRE_ROUTING
[2]  NF_IP_LOCAL_IN
[3]  NF_IP_FORWARD
[4]  NF_IP_POST_ROUTING
[5]  NF_IP_LOCAL_OUT
[*]  Network Stack

CentOS7でのVLAN設定(nmcliコマンド)

VLAN設定

e1という物理アダプタにvlanid=201を追加する。その際のIPアドレスは192.168.201.20/24、ゲートウェイは192.168.201.1で設定する。

#設定
nmcli connection add type vlan autoconnect yes con-name e1.201 ifname e1.201 dev e1 id 201 ip4 192.168.201.20/24 gw4 192.168.201.1

#反映、インターフェースのアップ
nmcli connection up e1.201