LoginSignup
0
0

More than 3 years have passed since last update.

SCAPYでTCPを頑張ってみる(その2:はじめてのGET編)

Last updated at Posted at 2020-12-10

この文書の目的

この文書は SCAPY をインストールして TCP 通信を試してみよう、と思った人向けのものです。なお、主に仕組みを理解する過程に沿わせて書いていますので、TCP でデータを取得したいだけの人は、「その3:StreamSocket編」に直行するのが良いです。

前提

以下のことを前提に書いています。

小さなコンテンツを GET する

非常にシンプルな( 1 パケットで収まると予め分かっている)小さな HTML コンテンツをリクエストして受信するだけのコードを示します。ホスト名、パス名は適宜自分の環境に適合したものに変更して試してください。
1 パケットで入ることが明らかなコンテンツ相手なので、sr1() を用いてリクエスト送信、レスポンス受信をしています。
また、HTTP 1.0 を指定しているので、サーバはコンテンツを送り終えると即時に(クライアントのACK到達とは関係無く)コネクションを切断しに掛かります。

### open TCP connection
sport = random.randint(30000,60000)
seq = random.randint(1000,2000)
ip = IP(dst='example.com')
tcp = TCP(sport=sport,dport=80,flags='S',seq=seq)

syn = ip/tcp
syn_ack = sr1(syn)

tcp.seq = syn_ack.ack
tcp.ack = syn_ack.seq + 1
tcp.flags = 'A'
ack = ip/tcp
send(ack)

### MY ACTION HERE
get = 'GET /a.html HTTP/1.0\r\nHost: example.com\r\n\r\n'
http = ip/tcp/get
response = sr1(http)
payload = response[0][TCP][0][1]["Raw"].load
print(payload.decode())

### close TCP connection
tcp.seq += len(get)
tcp.ack += len(payload)
tcp.flags = 'FA'
fin_ack = sr1(ip/tcp)

tcp.seq += 1
tcp.ack = fin_ack.seq + 1
tcp.flags = 'A'
send(ip/tcp)

上記のコードは「その1:準備編」で示した OS の介入を受けない状況のためのものです。そこで示した手法で対処する必要がある人は、上のコードの前方にゲートウェイ(router)に IP アドレスを設定するためのコードを挿入し、
ip = IP(dst='example.com')
の記述を、
ip = IP(dst='example.com', src=fakeIP)
に修正する必要があるでしょう。

さてSCAPY での実行画面は以下のようになります。<<<< に続けてコメントを入れています。

Begin emission:
Finished sending 1 packets.
..*
Received 3 packets, got 1 answers, remaining 0 packets
.
Sent 1 packets.
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
HTTP/1.1 200 OK           <<<< ここから受信データ(payload 変数)の内容表示
Date: Tue, 08 Dec 2020 09:03:38 GMT
Server: Apache
Last-Modified: Thu, 22 Oct 2020 09:34:49 GMT
ETag: "d7-5b23f2e4040"
Accept-Ranges: bytes
Content-Length: 215
MS-Author-Via: DAV
Content-Type: text/html
Connection: close

<HTML>
<BODY bgcolor="#ffffff">
               <<<< 途中略
だらだらだらだら。
</BODY>
</HTML>

Begin emission:
Finished sending 1 packets.
..*
Received 3 packets, got 1 answers, remaining 0 packets
.
Sent 1 packets.

以下にモニタリングの結果を示します。例によって見やすさのために適度に不要な表示を除き、行番号がつけてあります。

 1: IP 192.168.1.180.52907 > example.com.http: Flags [S], seq 1662, length 0
 2: IP example.com.http > 192.168.1.180.52907: Flags [S.], seq 117483685, ack 1663, length 0
 3: IP 192.168.1.180.52907 > example.com.http: Flags [.], ack 1, length 0
 4: IP 192.168.1.180.52907 > example.com.http: Flags [.], seq 1:44, ack 1, length 43: HTTP: GET /a.html HTTP/1.0
 5: IP example.com.http > 192.168.1.180.52907: Flags [P.], seq 1:467, ack 44, length 466: HTTP: HTTP/1.1 200 OK
 6: IP example.com.http > 192.168.1.180.52907: Flags [F.], seq 467, ack 44, length 0
 7: IP 192.168.1.180.52907 > example.com.http: Flags [F.], seq 44, ack 467, length 0
 8: IP example.com.http > 192.168.1.180.52907: Flags [F.], seq 467, ack 45, length 0
 9: IP example.com.http > 192.168.1.180.52907: Flags [F.], seq 467, ack 45, length 0
10: IP example.com.http > 192.168.1.180.52907: Flags [F.], seq 467, ack 45, length 0
11: IP 192.168.1.180.52907 > example.com.http: Flags [.], ack 468, length 0
  • 1-3 までが 3 Way Handshake です。
  • 4 で HTTP GET を送信。HTTP 1.0 を指定しています。
  • 5 で HTTP response を受信しています。ここでデータは完結しており、
  • 6 ではコンテンツを送り終わって、向こうからコネクションを切断するための FIN が届いています
  • 7 では(1パケットで受信が終わることを予見してコードされているため)クライアントからも FIN が出ています
  • これを受けて、何故か 8-10 まで同じパケットが送られています(タイムスタンプを見ると0.0001sec 程度の間に検出されています)
  • 11 では 8-10 までのどれかを受信し、最後の ACK が返送されています

ひとまず(8-10番パケットが 3 連で送られてきてはいますが)教科書的に通信を完了させることができました。

少し大きなコンテンツを GET する

少し大きなコンテンツでも取得できるように、sr1( ) を繰り返して受信を継続するように修正してみました。
今度は HTTP 1.1 を指定していますから、相手からコネクションを切ることはなく、こちらから先にコネクションを閉じなければなりません。
本来は Content-Length: ヘッダを見るなり、Chunk によってコンテンツ受信の完了を検出して処理する必要がありますが、ここでは「取れるだけ取って、データが送られてこなくなったことを timeout で知る」方法を採ります。sr1( ) に timeout パラメタをセットしています。

### open TCP connection
sport = random.randint(30000,60000)
seq = random.randint(1000,2000)
ip = IP(dst='example.com')
tcp = TCP(sport=sport,dport=80,flags='S',seq=seq)

print("=== send SYN flag and WAIT ACK")
syn = ip/tcp
syn_ack = sr1(syn)

print("=== send ACK")
tcp.seq = syn_ack.ack
tcp.ack = syn_ack.seq + 1
tcp.flags = 'A'
ack = ip/tcp
send(ack)

### MY ACTION IS HERE
print("=== send HTTP get request")
get = 'GET /aa.html HTTP/1.1\r\nHost: example.com\r\n\r\n'
http = ip/tcp/get
data = b''

while True:
    response = sr1(http, timeout=1)
    if response is None: # timeout
        break
    if type(response[TCP].payload) == scapy.packet.NoPayload or type(response[TCP].payload) == scapy.packet.Padding: # Flag only
        payload = b''
    else:
        payload = response[TCP]["Raw"].load
        data = data + payload
        print("## {0}".format(payload[:20]))
        print("## {0}".format(data[len(data)-20:]))
    tcp.seq = response[TCP].ack
    tcp.ack = response[TCP].seq + len(payload)
    tcp.flags = 'A'
    http = ip/tcp

### close TCP connection
print("=== send FA flag and wait ACK")
tcp.flags = 'FA'
fin_ack = sr1(ip/tcp, timeout=1)

print("=== send A flag")
tcp.seq += 1
tcp.ack = fin_ack.seq + 1
tcp.flags = 'A'
send(ip/tcp)

### show results
print("## {0}".format(data[:40]))
print("## {0}".format(data[len(data)-40:]))

SCAPY での実行画面は以下のようになります。<<<< に続けてコメントを入れています。

=== send SYN flag and WAIT ACK
Begin emission:
Finished sending 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
=== send ACK
.
Sent 1 packets.
=== send HTTP get request  <<<< ここからHTTP処理
=== sr1()  <<<< 最初の sr1() 呼び出し
Begin emission:
Finished sending 1 packets.
.........*
Received 10 packets, got 1 answers, remaining 0 packets <<<< 最初のパケット受信
## b'HTTP/1.1 200 OK\r\nDat'
## b'\xe3\x81\xa0\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3\x82'
=== sr1()  <<<< 二回目の sr1() 呼び出し
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets <<<< 二つめのパケット受信
## b'\x89\xe3\x81\xa0\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3'
## b'\xe3\x80\x82\n</BODY>\n</HTML>\n' <<<< これで完了
=== sr1()  <<<< 三回目の sr1() 呼び出し
Begin emission:
Finished sending 1 packets.
................... <<<< timeout した(受信が無かった)
Received 19 packets, got 0 answers, remaining 1 packets  <<<< 受信ナシ(got 0)
=== send FA flag and wait ACK
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
=== send A flag
.
Sent 1 packets.
## b'HTTP/1.1 200 OK\r\nDate: Tue, 08 Dec 2020 '
## b'\x82\x89\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3\x82\x89\xe3\x81\xa0\xe3\x82\x89\xe3\x80\x82\n</BODY>\n</HTML>\n'

以下にモニタリングの結果を示します。例によって見やすさのために適度に不要な表示を除き、行番号がつけてあります。

 1: IP 192.168.1.180.47569 > example.com.http: Flags [S], seq 1374, length 0
 2: IP example.com.http > 192.168.1.180.47569: Flags [S.], seq 2209167509, ack 1375, length 0
 3: IP 192.168.1.180.47569 > example.com.http: Flags [.], ack 1, length 0
 4: IP 192.168.1.180.47569 > example.com.http: Flags [.], seq 1:40, ack 1, length 39: HTTP: GET /aa.html HTTP/1.1
 5: IP example.com.http > 192.168.1.180.47569: Flags [.], seq 1:1025, ack 40, length 1024: HTTP: HTTP/1.1 200 OK
 6: IP example.com.http > 192.168.1.180.47569: Flags [P.], seq 1025:1990, ack 40, length 965: HTTP
 7: IP 192.168.1.180.47569 > example.com.http: Flags [.], ack 1025, length 0
 8: IP 192.168.1.180.47569 > example.com.http: Flags [.], ack 1990, length 0
(ここで 1 sec 空いている)
 9: IP 192.168.1.180.47569 > example.com.http: Flags [F.], seq 40, ack 1990, length 0
10: IP example.com.http > 192.168.1.180.47569: Flags [.], ack 41, length 0
11: IP example.com.http > 192.168.1.180.47569: Flags [F.], seq 1990, ack 41, length 0
12: IP 192.168.1.180.47569 > example.com.http: Flags [.], ack 1991, length 0
  • 1-3 までが 3 Way Handshake です。
  • 4 で HTTP GET を送信。HTTP 1.0 を指定しています。
  • 5 で HTTP response を受信しています。今度はデータは完結せず 1024 バイトだけ取得。
  • 6 に続けて残りの 965 バイトを送ってきました。
  • 7 では、5 パケットの seq:1025 までの ACK を送っています
  • 8 では、6 パケットの seq:1990 までの ACK を送っています
  • このあと 1 sec 間が空きます( timeout を待っている)
  • 9 では、timeout したため、クライアントから FIN が送られています
  • 10-12 ではこれに反応した終了処理が行われています

こちらも何とか教科書的に通信を完了させることができました。試みに私がテストしたサーバで 100KB 程度のデータを取得させてみたところ、約 20 秒かかりましたが、正しくデータを取得することに成功しました。同じ環境で curl コマンドを使うと 1 秒で取得しますから、SCAPY による処理のゆっくり加減が分かります。

ところが Google では

ところが上のコードを使って www.google.com を相手に GET / を試すと、コンテンツをすべて受信してしまう前に timeout によって停止してしまいます。
以下にモニタリング結果を示します。今度はタイムスタンプを残して、アドレス部分などを大幅に簡略にしています。seq/ack のやりとりと、フラグに注目してください。

 1: 16:53:44.522006 IP (local) > (Google): Flags [S], seq 1630
 2: 16:53:44.533679 IP (Google) > (local): Flags [S.], seq 1705770183, ack 1631
 3: 16:53:44.552580 IP (local) > (Google): Flags [.], ack 1
 4: 16:53:44.571535 IP (local) > (Google): Flags [.], seq 1:41, ack 1, length 40: HTTP: GET / HTTP/1.1
 5: 16:53:44.577056 IP (Google) > (local): Flags [.], ack 41
 6: 16:53:44.594801 IP (local) > (Google): Flags [.], ack 1
 7: 16:53:44.650857 IP (Google) > (local): Flags [.], seq 1:537, ack 41, length 536: HTTP: HTTP/1.1 200 OK
 8: 16:53:44.652456 IP (Google) > (local): Flags [.], seq 537:1073, ack 41, length 536: HTTP
 9: 16:53:44.654362 IP (Google) > (local): Flags [.], seq 1073:1609, ack 41, length 536: HTTP
10: 16:53:44.654376 IP (Google) > (local): Flags [.], seq 1609:2145, ack 41, length 536: HTTP
11: 16:53:44.654378 IP (Google) > (local): Flags [.], seq 2145:2681, ack 41, length 536: HTTP
12: 16:53:44.654381 IP (Google) > (local): Flags [.], seq 2681:3217, ack 41, length 536: HTTP
13: 16:53:44.654384 IP (Google) > (local): Flags [.], seq 3217:3753, ack 41, length 536: HTTP
14: 16:53:44.654386 IP (Google) > (local): Flags [.], seq 3753:4289, ack 41, length 536: HTTP
15: 16:53:44.654389 IP (Google) > (local): Flags [.], seq 4289:4825, ack 41, length 536: HTTP
16: 16:53:44.654395 IP (Google) > (local): Flags [.], seq 4825:5361, ack 41, length 536: HTTP
17: 16:53:44.668760 IP (local) > (Google): Flags [.], ack 537
18: 16:53:44.674232 IP (Google) > (local): Flags [.], seq 5361:5897, ack 41, length 536: HTTP
19: 16:53:44.675472 IP (Google) > (local): Flags [.], seq 5897:6433, ack 41, length 536: HTTP
20: 16:53:44.904243 IP (Google) > (local): Flags [.], seq 537:1073, ack 41, length 536: HTTP
21: 16:53:44.923096 IP (local) > (Google): Flags [.], ack 1073
22: 16:53:44.928630 IP (Google) > (local): Flags [P.], seq 6433:6969, ack 41, length 536: HTTP
23: 16:53:44.929586 IP (Google) > (local): Flags [.], seq 6969:7505, ack 41, length 536: HTTP
24: 16:53:44.929593 IP (Google) > (local): Flags [.], seq 7505:8041, ack 41, length 536: HTTP
25: 16:53:44.930849 IP (Google) > (local): Flags [.], seq 1073:1609, ack 41, length 536: HTTP
26: 16:53:44.948520 IP (local) > (Google): Flags [.], ack 1609
27: 16:53:44.953667 IP (Google) > (local): Flags [.], seq 1609:2145, ack 41, length 536: HTTP
28: 16:53:44.954840 IP (Google) > (local): Flags [.], seq 2145:2681, ack 41, length 536: HTTP
29: 16:53:44.971355 IP (local) > (Google): Flags [.], ack 2145
30: 16:53:44.976893 IP (Google) > (local): Flags [.], seq 2681:3217, ack 41, length 536: HTTP
31: 16:53:44.978076 IP (Google) > (local): Flags [.], seq 3217:3753, ack 41, length 536: HTTP
######### ここで 0.5sec 空いてる。徐々に送るのが遅くなっている?#########
32: 16:53:45.494530 IP (Google) > (local): Flags [.], seq 2145:2681, ack 41, length 536: HTTP
33: 16:53:45.513551 IP (local) > (Google): Flags [.], ack 2681
34: 16:53:45.518569 IP (Google) > (local): Flags [.], seq 2681:3217, ack 41, length 536: HTTP
35: 16:53:45.519801 IP (Google) > (local): Flags [.], seq 3217:3753, ack 41, length 536: HTTP
36: 16:53:45.519806 IP (Google) > (local): Flags [.], seq 3753:4289, ack 41, length 536: HTTP
37: 16:53:45.520996 IP (Google) > (local): Flags [.], seq 4289:4825, ack 41, length 536: HTTP
38: 16:53:45.535354 IP (local) > (Google): Flags [.], ack 3217
39: 16:53:45.540360 IP (Google) > (local): Flags [.], seq 4825:5361, ack 41, length 536: HTTP
40: 16:53:45.541466 IP (Google) > (local): Flags [.], seq 5361:5897, ack 41, length 536: HTTP
######### ここで 1sec 弱空いてる。これ timeout 直前だった。#########
41: 16:53:46.518446 IP (Google) > (local): Flags [.], seq 3217:3753, ack 41, length 536: HTTP
42: 16:53:46.536540 IP (local) > (Google): Flags [.], ack 3753
43: 16:53:46.542904 IP (Google) > (local): Flags [.], seq 3753:4289, ack 41, length 536: HTTP
44: 16:53:46.543832 IP (Google) > (local): Flags [.], seq 4289:4825, ack 41, length 536: HTTP
45: 16:53:46.543848 IP (Google) > (local): Flags [.], seq 4825:5361, ack 41, length 536: HTTP
46: 16:53:46.545044 IP (Google) > (local): Flags [.], seq 5361:5897, ack 41, length 536: HTTP
47: 16:53:46.560565 IP (local) > (Google): Flags [.], ack 4289
48: 16:53:46.565549 IP (Google) > (local): Flags [.], seq 5897:6433, ack 41, length 536: HTTP
49: 16:53:46.567144 IP (Google) > (local): Flags [P.], seq 6433:6969, ack 41, length 536: HTTP
######### ここで 1sec 空いてる。これが timeout だな。#########
50: 16:53:47.584353 IP (local) > (Google): Flags [F.], seq 41, ack 4289
51: 16:53:47.602954 IP (Google) > (local): Flags [.], ack 42
52: 16:53:48.463712 IP (Google) > (local): Flags [.], seq 4289:4825, ack 42, length 536: HTTP
53: 16:53:48.481791 IP (local) > (Google): Flags [.], ack 4290
54: 16:53:52.115254 IP (Google) > (local): Flags [.], seq 4290:4825, ack 42, length 535: HTTP

以下にざっと起きていることを並べてみます。

  • Google はGET を受けるといきなり大量のパケットを送ってくる。(7-16, 18-19 がそれ)
  • SCAPY は 17 パケットでようやく 7 パケットへの ACK 537 を出した
  • Google はそれに反応して 20 パケットを送信、つまり 537 からやり直す
  • SCAPY は 21 パケットでようやく 8 パケットへの ACK 1073 を出した
  • Google は 25 パケットに 1073 からのやり直しを出すが、
  • この時点で Google はもう 8041 まで送り出している(かなり錯綜している)
  • 31 パケットのあと、Google はパケット送信を 0.5sec 停止する(それより前に SCAPY が送った ACK の最後は 2145)
  • Google は 32 パケットつまり 2145 から送信を再開するが、しかし相変わらずお構いなくどんどん送ってくる
  • 40 パケットの後、Google はパケット送信を今度は 1sec 弱停止する
  • 41 から再びお構いなしに送信を開始する
  • 49 パケットの後、Google は 1sec 強の停止を行い、これがクライアント側の timeout を発生させる
  • 50 パケットで SCAPY が FIN パケットを送り、終了シーケンスに入る(が、まだ Google はデータを送ってくる)

sr1() の timeout を 3sec などに長くすれば、それなりに長めには生き残る。しばらくすると停止時間は 30sec になるが、それ以上は伸びないようです。私が試した範囲では、20分経った頃に突然、なにも送ってこなくなりました。(2020/12現在)

こういうことをされると SCAPY でパケット操作を直接コードして ACK 処理をする方法ではどうにも対応できません。

Google の挙動が示すように、相手を選ばず TCP より上のレイヤーでの振る舞いだけをテストするには、どうやら OS に ACK 処理をさせるしかないようです。
その方向で TCP のプログラミングをしてみたい人は、「その3:StreamSocket編」に進んでください。

もちろん SCAPY で ACK 処理を観察することは、こうした各種の実装の中身を知る、優れた方法です。その意味でこうしたトライをすることには意味があると思える実験となりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0