WiresharkはMPEG-TSヘッダを解する

WiresharkがMPEG-TSヘッダをパースしているので驚いた。ちゃんとHeaderの先頭が0x47。

wireshark.PNG

このRTPを認識させるのに、「Edit」-「Preferences」-「Protocols」-「RTP」の「Try to decode RTP outside of conversations:」のチェックが必要だった。

DCCPの場合はペイロードにRTPなしでMPEG-TSを直置きしているのでパースしない。

今、MPEG-TSのさらに下のPESヘッダに下って、MPEGソースレベルで損失を表せないかプログラムを書いている。DTS(デコードするタイムスタンプ)とPTS(プレゼンテーションするタイムスタンプ)を使って上手くできないかやっているが、なかなか予備知識なしでは厳しいものがある。

例えば0x47という値があるが、これは188バイトごとに出てくる。つまり、TSパケット境界が分からなくなった場合は0x47を探せばMPEG-TSの先頭が分かる、という仕組みなどがある。

MPEG-TSにこだわらずに出力されたファイルの比較をする方法も考慮したが、出力されたMPEGファイルのMPEG-TSを解して比較を行ったほうが分かりやすいだろうと思っているが、どうするべきだろうか。

カテゴリー: 無線LAN | 2件のコメント

TCPでバッファオーバーフローは発生しているのか

UDPでバッファオーバーフローが発生し、DCCPでは抑制されていることが分かった。それではTCPではどうなっているのだろうか。

LinuxのデフォルトのTCPにてバッファオーバーフローが発生しているのかどうかを確認する実験を行った。

送信側
tshark-tcp-070819-2111-sndtcp.png

受信側
tshark-tcp-070819-2111-rcvtcp.png

赤のドットはTCPシーケンス図である。

diffの緑のインパルスは「受信したTCPシーケンス番号ーそれまで受信した最大のTCPシーケンス番号」を表している。このdiffは通常はMSS一杯である1448 Bになるが、TCP送信後にMAC層にてバッファオーバーフローした場合には1448以上の値が来るはずである。ただし受信したTCPシーケンス番号よりそれまで受信した最大のTCPシーケンス番号が大きい場合、つまり、再送が行われた場合にはdiffは0に固定されるようにしている。

送信側グラフ、受信側グラフを比較すると、値の大きいdiffでは類似した傾向が見られる。受信グラフでは7.5秒付近にdiffのピーク値が見られる。キャプチャミスは送信側グラフの1448 * 2付近にて多く見られ、RTP、DCCPの場合と似たような傾向が見られている。

(Firefoxを使っている場合は各グラフを「Ctrl+左クリック」で新しいタブとして開き、タブ番号2,3である場合は「Ctrl+2」「Ctrl+3」を交互に押すと比較しやすい。)

さらにMAC層の損失があるかどうか調べた。

tshark-tcp-070819-2111-sndmac.png

このMAC層シーケンス送信回数の傾向からは、特に伝播状況の悪さは見られない。

結果として、LinuxデフォルトのTCPでは、送信されたTCPセグメントは無線LANで送信された時点で既に損失が発生している、ことが確認できた。この原因としては、やはりMACのバッファオーバーフローが考えられる。

この現象はデフォルトTCPの大きすぎる広告ウィンドウによって送信ウィンドウが開きすぎ、キャパシティを越えるトラヒックがMAC層に到着し、その時点でMAC層バッファが溢れ、損失し、TCPは高速再転送もしくはタイムアウト再送を行ったもの、と判断できる。そのためタイムアウト再送待ちの時間が10秒付近で発生していたり、無駄な高速再転送が起きたりすることで実効帯域を狭めてしまっている。

この挙動に対してTCPでは、広告ウィンドウサイズを設定する等の対処が必要になる。

カテゴリー: 無線LAN | コメントする

送信側と受信側でキャプチャした結果が違う?

送信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-snd070817-2257 -R “tcp.port == 51401” | tail
94107 33.154248 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94108 33.154573 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94109 33.154897 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94110 33.155221 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94111 33.155548 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94112 33.155802 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
94113 33.156058 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=81738979 Win=563392 Len=0 TSV=179444729 TSER=113330957
94119 33.204014 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [FIN, ACK] Seq=81748203 Ack=191 Win=6912 Len=0 TSV=113330974 TSER=179444730
94120 33.204199 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [FIN, ACK] Seq=191 Ack=81748204 Win=563392 Len=0 TSV=179444742 TSER=113330974
94122 33.204530 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=81748204 Ack=192 Win=6912 Len=0 TSV=113330974 TSER=179444742
[/code]
受信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-rcv070817-2257 -R “tcp.port == 51401” | tail
84694 27.916116 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84695 27.916136 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81744770 Win=8803 Len=0 TSV=179444730 TSER=113330959
84696 27.916439 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84697 27.916764 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84698 27.916782 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81747666 Win=8803 Len=0 TSV=179444730 TSER=113330959
84699 27.917022 192.168.2.12 -> 192.168.2.10 TCP [TCP segment of a reassembled PDU]
84700 27.917054 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=81748202 Win=8803 Len=0 TSV=179444730 TSER=113330959
84701 27.965231 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [FIN, ACK] Seq=81748202 Ack=0 Win=108 Len=0 TSV=113330974 TSER=179444730
84702 27.965275 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [FIN, ACK] Seq=0 Ack=81748203 Win=8803 Len=0 TSV=179444742 TSER=113330974
84703 27.965748 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=81748203 Ack=1 Win=108 Len=0 TSV=113330974 TSER=179444742
[/code]

送信側のFIN/ACKがSeq=81748203
受信側のFIN/ACKがSeq=81748202

なぜ違う?

理由は、wireshark(tshark)のTCPシーケンス番号の計算方法が、そのストリームで一番初めに受信したTCPセグメントのシーケンス番号(ランダムに初期化された番号)を基準0として扱うため、「一番初めに受信したTCPセグメント」が送信側、受信側で異なると番号がずれる。

送信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-snd070817-2257 -R “tcp.port == 51401” | head
100 4.791747 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [SYN] Seq=0 Len=0 MSS=1460 TSV=179437639 TSER=0 WS=6
102 4.791882 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=113323870 TSER=179437639 WS=6
103 4.792085 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=1 Ack=1 Win=5888 Len=0 TSV=179437639 TSER=113323870
106 4.792353 192.168.2.12 -> 192.168.2.10 TCP [TCP ACKed lost segment] 1234 > 51401 [ACK] Seq=1 Ack=42 Win=5824 Len=0 TSV=113323871 TSER=179437639
107 4.792843 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [PSH, ACK] Seq=42 Ack=1 Win=5888 Len=149 TSV=179437639 TSER=113323871
109 4.792954 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=1 Ack=191 Win=6912 Len=0 TSV=113323871 TSER=179437639
112 4.892060 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=1 Ack=191 Win=6912 Len=84 TSV=113323896 TSER=179437639
113 4.892243 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=85 Win=5888 Len=0 TSV=179437664 TSER=113323896
115 4.892549 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=85 Ack=191 Win=6912 Len=188 TSV=113323896 TSER=179437664
116 4.892726 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=191 Ack=273 Win=6912 Len=0 TSV=179437664 TSER=113323896
[/code]

受信側
[code]
noch@debian-noch:~/wa2/wa2$ tshark -r /tmp/tshark-tcp-rcv070817-2257 -R “tcp.port == 51401” | head
1 0.000000 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=0 Ack=0 Win=108 Len=84 TSV=113323896 TSER=179437639
2 0.000026 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=84 Win=92 Len=0 TSV=179437664 TSER=113323896
3 0.000031 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=84 Ack=0 Win=108 Len=188 TSV=113323896 TSER=179437664
4 0.000035 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=272 Win=108 Len=0 TSV=179437664 TSER=113323896
5 0.000040 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=272 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
6 0.000045 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=1720 Win=154 Len=0 TSV=179437714 TSER=113323946
7 0.000049 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [ACK] Seq=1720 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
8 0.000054 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=3168 Win=199 Len=0 TSV=179437714 TSER=113323946
9 0.000059 192.168.2.12 -> 192.168.2.10 TCP 1234 > 51401 [PSH, ACK] Seq=3168 Ack=0 Win=108 Len=1448 TSV=113323946 TSER=179437664
10 0.000122 192.168.2.10 -> 192.168.2.12 TCP 51401 > 1234 [ACK] Seq=0 Ack=4616 Win=244 Len=0 TSV=179437714 TSER=113323946
[/code]

192.168.2.10から192.168.2.12に向かうSYNがキャプチャできていないことが分かる。

カテゴリー: 無線LAN | 5件のコメント

なるべくデーモンを止めてからキャプチャ

以前、LinuxでHDDのパフォーマンスを測定するときにはシングルユーザモードで行うと余計なプロセスが起動していないので良いという話をどこかのWebサイトから得た。

シングルユーザモードは以下のコマンドか、もしくはブートローダのカーネルオプションで指定することで、入ることが可能である。
[code]
# init 1
[/code]

これはキャプチャのときにもいえることかどうか分からないが、精神衛生上、他のプロセスが少ない状態でキャプチャを行ったほうがいいだろうと考えた。そこで動作モードを変更してキャプチャを行っている。

シングルユーザモードでは外部からsshで入ることができない。init 1ではsshdまで停止してしまう。そこでrc2.dにて最低限ネットワークを生かして、他の関係のないプロセス、例えばlpd(印刷デーモン)やcronを停止することにした。

Debianの場合、init 2〜5には違いがないらしいので、rc2.dを選んだ。止めたいプロセスを
[code]
# ls /etc/rc2.d
[/code]
で探し、
[code]
# update-rc.d -f [basename] remove
[/code]
で全てのrc.d登録を削除し、
[code]
# update-rc.d -f [basename] start 20 3 4 5 . \
stop 20 0 1 2 6 .
[/code]
にて、init 2のときに[basename]が停止するように設定した。

停止するようにしたのは、xdm, xfs, nfs, lpd, xprint, cupsys, samba, netperf, cannaとネットワークと実験に関係なさそうなプロセス。

これで、
[code]
# init 2
[/code]
とするだけで、起動していたプロセスは止まり、ps -efでもカーネルもの以外が少なくなる。

実験後、GUI等を復帰させたい場合は、
[code]
# init 5
[/code]
で元に戻る。

上記の対策を施してキャプチャを行ってみたが、違いがよく分からない。必要ないかもしれない。

カテゴリー: 無線LAN | コメントする

解析プログラムでTCPを解析してみた

TCPもやってみた。

tshark-tcp-snd070817-2257rtp.png

TCPシーケンス図。シーケンスナンバーの値が変。後ろのLostは無視。

tshark-tcp-snd070817-2257mac.png

MACの送信回数。DCCPと傾向は似ているが、多少、強引に送っている感がある。

途中でMAC層で送信せずに待ちに入っている時間がある。この原因としてMAC層損失がありそうだが、それが発生するくらい再送している状態は見られない。よってバッファオーバーフローではないかという仮説が出てくるが、TCPの内部状態とMACドライバのオーバーフロー数を今回は観察していないので分からない。

受信してファイル出力させたデータのサイズは80.6MB。再生時間までにデータ送信が間に合っていない可能性がある。

その原因としては、誤り訂正が行われるまでデータを上位に渡さないというTCPの性質が挙げられる。TCPは再送して途中が埋まるまでACKにて再送要求を行い、既に受信したデータといえども完全性を保証するまではアプリケーションには渡さない。つまり、先のデータが届いているのにも関わらず、再送を待つことで再生までに間に合わずプレイヤーで劣化として現れてしまったという仮説が考えられる。

これを証明するためには、やはり遅延を考慮した出力を行う必要がありそうだ。

カテゴリー: 無線LAN | コメントする

解析プログラムでDCCPを解析してみた

RTP/UDPの解析と同じ手法でDCCPの解析を行った。

tshark-dccp-snd070817-2131rtp.png

送信時にキャプチャミスは見られるものの、バッファオーバーフローはほとんど見られない。DCCPによる輻輳制御が上手く働いているものと考えられる。

tshark-dccp-snd070817-2131mac.png

10回再送が発生していないあたり、伝播状況はいいみたいだ。

tshark-dccp-rcv070817-2131rtp.png

受信時にはいくつかパケットロスしている様が見られる。これはキャプチャミスか本当にロスしているのか分からない。

データサイズの比較は、以下の通り。

  1. 送信ソースが94.8MB
  2. 送信時の推定サイズが93.7MB
  3. 受信時の推定サイズが94.0MB
  4. 受信出力ファイルサイズが93.3MB

一回ずつの実験なので傾向として言うことはできないが、結果としてRTPよりDCCPの方が再生に間に合ったファイルサイズが大きいことになる。

この理由としては伝播状況が悪いときにUDPは無理して送信することによりMAC再送が多発しバッファがオーバーしてしまうのに対して、DCCPは伝播状況が悪いときには抑制し伝播状況が良いときに送信するためにMAC層の再送が少なく済み、結果として受信できたデータサイズが大きくなったと考えられる。

この仮説の正しさを証明するためには、何回か実験を繰り返すこと、TCPでも同様の傾向が確認できることが必要そうな気がする。

カテゴリー: 無線LAN | コメントする

解析プログラムでRTP/UDPを解析してみた(2)

送信側のMACキャプチャした結果もRTP解析する。このMACキャプチャはPCに2枚無線LANカードを挿しRTP送信していない方でモニターモードにして別のアンテナでキャプチャしたものである。

tshark-snd070817-1612rtp.png

このグラフでは、かなり多くの損失が見られる。この原因はキャプチャミスが発生しているためであり、至近距離の別のアンテナにてMACキャプチャを行っても完全にキャプチャできていないことを意味する。

また20秒から25秒にかけてキャプチャミスを原因としないような大きな損失が見られる。これはMAC層レベルでRTPシーケンスが連続しない状態で送信していることを意味する。すなわちバッファオーバーフローが発生している。

結果的にバッファオーバーフローが受信時のRTP損失に対して大きな影響を与えていることは、送信側RTP解析と受信側RTP解析の傾向から見ても確かなようだ。

MAC層の再送限界を超えることによる損失は今回はほとんど発生していない。というのも前回のMAC層のグラフを見てもMAC層で発生した損失は2回程度であり、キャプチャミスを考慮しても、それほどの数ではないはずである。

他に興味深いデータとしては、データサイズで以下のように段階ごとに損失が見られた。

  1. 元データは94.8MB
  2. 送信側MAC層キャプチャでは91.8MB
  3. 受信側IP層キャプチャでは88.2MB
  4. 受信側アプリケーションが出力したmpgは86.7MB

2.と3.のデータサイズの差がある理由はMAC層損失の件から見てもよく分からない。3.と4.に差があるのは、再生時間までにデータが間に合わなかった(バッファ時間が足りなかった)からだろうか。色々と考えることが出てきた。

カテゴリー: 無線LAN | コメントする

解析プログラムでRTP/UDPを解析してみた

無線MAC解析プログラムができて来たので、まず、RTPでテスト。送信側でMACキャプチャ(madwifi)、受信側でIPキャプチャしたものを解析プログラムを通してgnuplotでpng出力したもの。

tshark-snd070817-1612mac.png

緑のインパルスがそのMACシーケンスコントロールを送信するために費やしたMAC送信回数を示しており、赤のラインがそのMACシーケンスコントロールのRSSIを測定している。

7秒前後で2回ほどリトライリミット回数(11回)に達しているので、高確率で受信に失敗しているはずだが…

tshark-rcv070817-1612rtp.png

緑のドットが受信できたRTPパケットの総和で、赤のインパルスが損失したRTPパケット数で前回受信したRTPシーケンスナンバーと今回受信したシーケンスナンバーから算出した損失シーケンスナンバー数。このRTPパケット損失が、動画・音声の劣化として現れる。

7秒付近では特にロストしていない。矛盾。2回とも11回で送信成功したということか。

RSSIは関連性が感じられない。というか、送信側でMACキャプチャをやっているので受信側のRSSIではない。間違い。

カテゴリー: 無線LAN | コメントする

libpcapの使い方の補足

前回の話のおさらい。

libpcapではpcap_loopやpcap_dispatchによって各フレームに対してpcap_handlerを処理させる。

pcap_handlerでは第3引数であるu_char *dataを、ネットワーク層プロトコル番号(Ethernet)->トランスポート層プロトコル番号(IP)->DCCPシーケンス番号の順にアクセスしていくことになる。

で、補足。linuxの場合だと以下のヘッダファイルをincludeすると楽。

[code]
#include
#include
#include #include [/code]

こうすると、

[code]
struct ether_header *ether_hdr =
(struct ether_header *)data;

/* htons(ether_hdr->ether_type) = ETHERTYPE_IP なら */

int index = sizeof(struct ether_header);
struct ip *ip_hdr = (struct ip *)&data[index];

/* (ip_hdr->ip_p) = IPPROTO_DCCP なら */

index += sizeof(struct ip);
struct dccp_hdr *dccp = (struct dccp_hdr *)&data[index];
[/code]

でdccp構造体まで下れる。後は、dccp->dccph_xでシーケンス番号拡張の有無を確認し、拡張されるようであれば、

[code]
index += sizeof(struct dccp_hdr);
struct dccp_hdr_ext *dccp_hdr_ext =
(struct dccp_hdr_ext *)&data[index];
[/code]

でさらに下る。あとはdccp->dccph_seqとdccp_ext->dccph_seq_lowを処理するとDCCPのシーケンス番号が出るはず(スループット測定にシーケンス番号が必要かどうか分からないけれど)。

もしかしたらip_hdr->ip_pのエンディアンを直さなきゃいけないかもしれない。

と、こんな風にヘッダファイルをincludeすると各レイヤのプロトコル処理が楽になる、という補足。前回に数行で済むとか書いたけど、それは間違いだった。

加えてipの送信元・あて先アドレス、DCCPの送信元・あて先アドレスでフィルタリングできるようにして、802.11(と、その下のprism2header)にも対応するのが、今、自分の書いているコード。

カテゴリー: 無線LAN | コメントする

libpcapの使い方

前回のコメントでlibpcapの使い方を知りたいということだったので、簡単なlibpcapプログラムを書いた。

“test.pcap”ファイルの各フレームの受信時間と、データリンク層プロトコル番号を表示する。

  1. pcap_open_offlineでpcapファイルを開く
  2. pcap_datalinkでデータリンク層プロトコル番号を取得する
  3. pcap_loopで各フレームに対してpcap_handlerを行う

pcap_loopの第2引数は処理するフレーム数であり-1でEOFまで処理を行う(はず)。

もしdatalinkがEthernet(1)であれば、handlerのdataにはEthernetフレーム以上が入っているので、ネットワーク層プロトコル番号(Ethernet)->トランスポート層プロトコル番号(IP)->DCCPシーケンス番号の順にアクセスする。

[code]
#include
#include

typedef struct {
u_int32_t datalink;
int start_flag;
struct timeval start_ts;
} user_hdr;

void
my_handler(u_char *_user,
const struct pcap_pkthdr *pkthdr,
const u_char *data)
{
user_hdr *user = (user_hdr *)_user;

if (user->start_flag == 0)
{
user->start_ts = pkthdr->ts;
user->start_flag = 1;
}
struct timeval ts;
timersub(&pkthdr->ts,
&user->start_ts,
&ts);

printf(“%lu.”, ts.tv_sec);
printf(“%06lu “, ts.tv_usec);
printf(“%u “, user->datalink);
printf(“\n”);
}

int
main()
{
const char *fname = “test.pcap”;

char *errbuf = NULL;
pcap_t *pcap = pcap_open_offline(fname, errbuf);

user_hdr *user = (user_hdr *)malloc(sizeof(user_hdr));
user->datalink = pcap_datalink(pcap);
user->start_flag = 0;

pcap_loop(pcap,
-1,
(pcap_handler)my_handler,
(u_char *)user);

free(user);

return 0;
}
[/code]

後はTCPもしくはDCCPのシーケンス番号が欲しければ10〜20行くらい書けば表示できる。ただしDCCPの場合はext seqがあるので少し面倒。

書いた後で思ったが、かなり行数少ない。シーケンス図やスループット図を描く為ならEtherealよりお勧めだなー。

カテゴリー: 無線LAN | 2件のコメント