korechi’s diary

とあるVR/ARエンジニアのブログ

ドライバ内でのパケット作成

sk_buff

基本的には、sk_buffを使う。使い方は以下に例をのせる。

#include <linux/skbuff.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>

struct sk_buff *skb;
skb = alloc_skb( 
                sizeof( struct ethhdr ) 
                        + sizeof(struct iphdr) 
                        + sizeof( struct tcphdr )
                        + LL_MAX_HEADER,
                GFP_ATOMIC
                );

sk_buffにはhead, data, tail, endの4つのフィールドを持つ。
それぞれ
- head: データ格納用バッファの先頭を示す
- data: バッファに格納されているデータの先頭
- tail: データの終端
- end: データ格納用バッファの終端 / このアドレスは、skb_shared_infoの開始アドレスでもある
となっている。

ヘッダー構造体

パケットのヘッダーは上記のものをincludeすれば用意されているものが使える。

Ethernet ヘッダー

 struct ethhdr
 {
    unsigned char   h_dest[ETH_ALEN];   /* destination eth addr */
    unsigned char   h_source[ETH_ALEN]; /* source ether addr    */
    unsigned short  h_proto;        /* packet type ID field */
 };

IP ヘッダー

  • linux/ip.h or netinet/ip.h
 struct iphdr
   {
 #if __BYTE_ORDER == __LITTLE_ENDIAN
     unsigned int ihl:4;
     unsigned int version:4;
 #elif __BYTE_ORDER == __BIG_ENDIAN
     unsigned int version:4;
     unsigned int ihl:4;
 #else
 # error "Please fix <bits/endian.h>"
 #endif
     u_int8_t tos;
     u_int16_t tot_len;
     u_int16_t id;
     u_int16_t frag_off;
     u_int8_t ttl;
     u_int8_t protocol;
     u_int16_t check;
     u_int32_t saddr;
     u_int32_t daddr;
     /*The options start here. */
   };

※一番初めにくるはずのIPヘッダー長が無い点に注意

UDP ヘッダー

 struct udphdr {
    __u16   source;
    __u16   dest;
    __u16   len;
    __u16   check;
 };
  • netinet/udp.h
 struct udphdr {
         u_int16_t uh_sport;           /* source port */
         u_int16_t uh_dport;           /* destination port */
         u_int16_t uh_ulen;            /* udp length */
         u_int16_t uh_sum;             /* udp checksum */
 };

ただ自分はもう自分でUDPパケット用の構造体を用意した。

struct t_udppacket {
    unsigned char eth_dest[6];
    unsigned char eth_source[6];
    unsigned char eth_type[2];

    unsigned char ip_headlen;
    unsigned char ip_tos;
    unsigned char ip_len[2];
    unsigned char ip_id[2];
    unsigned char ip_flag;
    unsigned char ip_offset;
    unsigned char ip_ttl;
    unsigned char ip_protocol;
    unsigned char ip_checksum[2];
    unsigned char ip_source[4];
    unsigned char ip_dest[4];

    unsigned char udp_source[2];
    unsigned char udp_dest[2];
    unsigned char udp_length[2];
    unsigned char udp_checksum[2];
    unsigned char data[VALUE_SIZE];
    unsigned char fcs[5];
};

パケットの送り方

基本的には、~xmit()関数に*skbを渡せば良いはず。
* mlx4

mlx4_en_xmit(skb, dev);
  • nf10
nf10priv_xmit(card, skb, port);

この関数を入れれば良い。ここに入れるskbは、できあがっているものとする。
注意としては、この関数を入れる前に、

skb->len = sizeof(struct t_udppacket) / 2;
skb_put(skb, skb->len);

をいれないといけない。これはもはやおまじないと思って良いかもしれない。
ここで何で2で割ってるかはよく分からないけど、これをやらないと何故かパケットが2倍の長さになり、0が埋められるという状態になる。気をつけるべし。でも何でなんだ。。。笑

チェックサム

基本的には計算してチェックサムフィールドを埋める必要があるが、UDPチェックサムフィールドに0x0000を入れると、チェックサムのチェックを省くことができるオプションが実はある。
パケットとしては不完全だが、とりあえずこれを使うのも手だろう。

Wireshark

パケットを確認するために、Wiresharkを使うことがあるだろう。
そのお作法というか、ぱっと見分からないことをのせる。

パディング(padding)

Wiresharkでの最小フレーム長は60Byteなので、それ以下のパケットが来た場合は、パディングというダミーのデータを付加する。

なぜかパケットにのせていないはずの情報がのっている

Wiresharkで確認すると、パケットの末尾になぜかTrailerとして謎の情報がのせられ、パケット長が増えている。
しかし、通信としては成立しており謎の情報はアプリケーション層では認知されない。
そこで、ここがとても参考になった。
どうも、これはOSに起因する使用で、Windows Linux MacOSいずれの環境でも発生するらしい。

ETHERNET FRAME CHECK SEQUENCE INCORRECT

と出てしまう。が、FCS(Frame Check Sequence)はOS側で破棄されているらしい。