AUCapture icon indicating copy to clipboard operation
AUCapture copied to clipboard

TCP通信の実装について

Open p1atdev opened this issue 2 years ago • 8 comments

私は、「Network ExtensionでiOSデバイス上で動くパケットキャプチャを作る」のスライドや、このレポジトリのコードを参照しながら、パケットキャプチャを作っています。数少ない、Swift でのパケットの扱いに関する解説なので非常に参考になっています。最終的な目標は Proxyman のように HTTP や HTTPS の通信を見れるようにすることです。

これらのサンプルコードを真似しながら UDP での通信は実装することができました。QUIC 、HTTP/3 対応の Web サイトを閲覧できました。 しかし、TCP 通信はよくわからず、色々と調べても実装できなかったのでご教授頂ければ幸いです。

質問したい内容

質問の内容は大きく分けて 2 つです。

  1. packetFlow.writePacketObjects() の役割について
  2. TCP パケットの処理について

packetFlow.writePacketObjects() の役割について

Apple のドキュメントでは

IP packets with matching destination addresses will then be diverted to Packet Tunnel Provider and can be read using the packetFlow property. The Packet Tunnel Provider can then encapsulate the IP packets per a custom tunneling protocol and send them to a tunnel server. When the Packet Tunnel Provider decapsulates IP packets received from the tunnel server, it can use the packetFlow property to inject the packets into the networking stack.

Packet Tunnel Provider | Apple Developer Documentation

Write multiple IP packets to the TUN interface.

writePacketObjects(_:) | Apple Developer Documentation

と書いてあります。しかし、今回のサンプルコードではトンネルサーバーはないので、パケットがどこに行くのかよくわかりません。 ここでは通信して返ってきた IP パケットのデータを、(通信元の)アプリケーション側に返す処理がされるという認識であっているのでしょうか? それとも、この PacketTunnelProvider 自体がトンネルサーバーになっているのでしょうか?このあたりの理解が不足しているのでわからないです。

TCP パケットの処理について

TCP パケットの処理はスライドでは、

private func sendTCPPacket(_ packet: IPPacket) {
    let header = packet.tcpHeader
    if header.isSyn {
        packetFlow.writePacketObjects(
            packetBuilder.replySynAck()
        )
    } else if header.isAck {
        ...
        if packet.data.count > 0 {
            tcpSession.send(packet)
            
            tcpSession.onReceive { [self] (data) in
                ...
            
                packetflow.writePacketObjects(
                    [
                        NEPacket(
                            data: data,
                            protocolFamily: sa_family(AF_INET)
                        )
                    ]
                )
            }
        } else {
            packetFlow.writePacketObjects(
                packetBuilder.ackFinAck()
            )
        }
        ...
    } else if header.isFin {
        packetFlow.writePacketObjects(packetBuilder.ackFinAck())
    } else if header.isRst {
        packetFlow.writePacketObjects(packetBuilder.reset())
    }
}

となっていました。

このサンプルコードでは TCP ヘッダからフラグを取得して、3ウェイハンドシェイクをしているように見えます。 packetBuilder.replySynAck()packetBuilder.ackFinAck() ではどのような処理をして、どのようなパケットを返しているのでしょうか?

また、UDP 通信では UDPSession を使っていましたが、TCP では TCPSession を使ってそのまま通信することはできないのでしょうか? UDPSessionTCPSession では、NWConnection ではエンドポイントとプロトコルを指定し、receiveMessage で完全なメッセージを受け取る仕組みだと思います。この、NWConnection での通信は3ウェイハンドシェイクを含まず、自分でハンドシェイクをする必要があるということでしょうか? 自分で試した際は、 TCPSession を使うだけだとなぜかうまくいかず、通信が確立できませんでした。

この辺りについて調べてみたのですが、このクラスについての情報や例がとても少なく、いくつかの NWConnection を使った TCP 通信のサンプルコードでは別途でハンドシェイクをする処理は実装していなかったので、このハンドシェイクを自前で実装する処理は不要なように思われます。

まとめ

まとめると、

  • packetFlow.writePacketObjects() で何が起きているのか
  • packetBuilder.replySynAck() でどのようなことをして、何を返しているのか
  • TCP 通信は UDP と同様に TCPSessionNWConnection を使うだけではできないのか

という点について教えていただきたいです。

よろしくお願いします。

p1atdev avatar Jun 13 '22 13:06 p1atdev

Deviceで通信が発生(SafariやTwitterアプリなど) => (Packet Tunnel Provider) packetFlow.readPacket => パケットを外部に送ったりいろいろする => packetFlow.writePacket => Device

^ パケットの動きはこうなります(すべてのパケットがPacket Tunnel Providerに来るかどうかは設定による)。Packet Tunnel Providerは通常外に向かうパケットを一旦フックすることができて、普通にVPNとして振る舞う場合はパケットをカプセル化してVPNサーバーに送るわけです。VPNサーバは本来のリクエストを送ってレスポンスをカプセル化して返す、Packet Tunnel ProviderはレスポンスをwritePacketでデバイスに返す、です。

すべてDeviceでやる場合は外部のVPNサーバがいないのでなんとかして自分でやるわけです。 でUDPはStatelessなプロトコルで単純なので来たパケットをそのまま代理で外に送って返ってきたものを返す、でいいわけですが、TCPはStatefulなプロトコルのでそう単純ではないということです。

おっしゃる通り、デバイスからのパケットはTCPの接続確立のための3ウェイハンドシェイクが来るので、デバイスだけでやるならば当然3ウェイハンドシェイクをやる必要があります。そのまま外に同じパケットを送ればいいわけですが、IPパケットを送らないといけないので結局ある程度自分でパケットを作る必要があります。 TCPSessionは通信のレイヤーが違って、TCPの接続確立はTCPSessionがやってしまうので、デバイスからはTCPの接続確立のパケットが来ているだけなので、その段階ではTCPSessionを使おうと思っても送りようがない、ということです。

packetBuilder.replySynAck()がやってるのは発表にあるようにダミーの接続が確立されたように見えるパケットをデバイスに返して、HTTPなどの本当に外に送らなければならないパケットを強制的に送らせて、それを外に送ってレスポンスを返す、みたいなことをしています。

「packetBuilder.replySynAck() でどのようなことをして、何を返しているのか」ということの解答は、外とは通信せずに3ウェイハンドシェイクが確立されるようなパケットを自作して送っている、ということになります。ダミーのパケットを勝手に送ってるので本来のサーバーが実際にはダウンしていても接続が確立されたようにデバイスには見えます。

「TCP 通信は UDP と同様に TCPSession で NWConnection を使うだけではできないのか」という点はPacket Tunnel Providerで外部のVPNサーバなどを使わずにパケットを中継するというのであればそうです。NWConnectionはTCPレイヤーの通信をするのでIPレイヤーの通信をこれでやるわけにはいかないわけです。UDPについてもレイヤーのミスマッチはあるのですが、UDPパケットは簡単なのでUDPパケットの中身を見て通信して結果をIPパケットにして返すっていうのはデバイス内でもできるわけです。

kishikawakatsumi avatar Jun 15 '22 07:06 kishikawakatsumi

@p1atdev そもそもOn Deviceでやることにこだわる必要はあまりないと思うんですよね。パケットキャプチャが目的ならTLSをなんとかする必要もありますし。素直に外にサーバを置いてそれと通信したらいいと思いますが、実際にやりたいことはなんでしょうか?

kishikawakatsumi avatar Jun 15 '22 20:06 kishikawakatsumi

回答ありがとうございます

最終的に実現したいことは、AUCaptureのように、特定のゲームの通信を監視して、記録をしたりユーザーに即時の通知を送るようなものが作りたいです。 外にサーバーを置くことも考えたのですが、そのサーバーの作り方がわからなかったのでデバイス上で処理しようとしていました。 プロキシサーバーを作るにあたって役に立ちそうな情報などあれば教えていただきたいです。

p1atdev avatar Jun 15 '22 20:06 p1atdev

@kishikawakatsumi could you share the whole code? especially about how to send TCP packets. Thanks a lot.

mike1023 avatar Aug 01 '23 02:08 mike1023

@mike1023 The code to send TCP packets is not implemented. To achieve this, it is necessary to implement TCP packet construction and handshaking, which is not implemented.

kishikawakatsumi avatar Aug 01 '23 02:08 kishikawakatsumi

@kishikawakatsumi got it, but I still feel confused about why we can not use NWConnection to send TCP requests just like UDP. Why do we have to construct TCP packets by ourselves?

mike1023 avatar Aug 01 '23 03:08 mike1023

I try to send TCP packets directly with NWConnection, but the state is always in preparing and then failed.

mike1023 avatar Aug 11 '23 09:08 mike1023

image

hi, I'm sorry to bother you, but I'm not clear about how this code works, I test it with my code and I can not get the right result.

image

mike1023 avatar Aug 21 '23 14:08 mike1023