🍻

ネットワーク入門。golangでルーターもどきを実装して失敗した話。

2022/12/10に公開

はじめに

情報ネットワークを学習する機会があったので、golangでルーターもどきを実装してみました。
物理的な機器の話を中心に書いてみたので、インフラに苦手意識のあるwebエンジニアや、バックエンドエンジニアの方にも、読んでもらえると嬉しいですー!

この記事は、Supershipグループ Advent Calendar 2022 9日目の記事になります。(しれっと、アドカレを1日すっぽかしました)

対象読者

  • ネットワークやインフラに苦手意識がある方
  • 基本情報技術者を勉強中の方

など

1. インターネットの歴史と概要

インターネットで使われる機器の話をする前に、インターネットがどのようなものか整理しておきましょう。
インターネットとは、様々なデバイスや機器が接続された、世界規模のネットワークです。インターネットは、主にインターネットプロトコル(IP)と呼ばれるプロトコルを使用して、異なる種類のネットワークを接続し、各種データを送受信することができます。

その歴史は古く、1973年には、IPの肝であるパケット交換技術による、コンピューターネットワークの実験が開始されていました。しかし、一般ユーザーに広く普及したのは1990年代に入ってからです。

こちらが、1970年代から1990年代までのインターネットのざっくりとした歴史です。

  1. 1970年代。インターネットの前身となるARPANETが米国国防高等研究計画局(DARPA)によって開発される
  2. 1975年。TPC/IP誕生。
  3. 1982年。トランスポート層のプロトコルであるTCPが改訂され、TCP/IPと呼ばれるプロトコルスタックが完成。UNIXに実装されてたらしい。これにより、さまざまなタイプのコンピュータがインターネット上で通信できるように。
  4. 1990年代初頭。インターネットが一般のユーザーに広く普及しはじめる。

策定自体は早かったのですが、1990年代の普及までは、コンピューター自体が高価であったり、メーカー独自の仕様でしか通信できなかったりと、多くの壁がありました。1990年代に安価なPCが広まった事で、メーカーがネットワーク通信を標準仕様に寄せていったり、企業や一般向けのインターネットサービスプロバイダーが普及したりで、利用がどんどん増えていきました。情報通信の革命ですね。

このような歴史を持つネットワークですが、それを構成する機器は、PCやスマートフォンといったデバイスだけでなく、ルータやスイッチといった専用機器も含まれています。昔は、ルーターも高いコンピューターでやってたけど、安価でええやんて専用機器になっていったらしいです。

2. ネットワークを構成する機器

機器の説明に入る前に、OSI参照モデルについて、簡単に触れておきます。

OSI参照モデル

OSI参照モデル(Open Systems Interconnection Reference Model)は、通信プロトコルを分類するためのモデルです。このモデルでは、通信を行う際に必要な処理を7つのレイヤーに分類し、それぞれのレイヤーが担当する処理を定義しています。(頭文字とって、アプセトネデブで覚えろ、って言われるやつっす)

プロトコルというワードが出てきました。(エンジニアであれば、めちゃくちゃ聞き覚えあると思いますが) 通信の約束事ですね。プロトコルに従って実装すると、01の数字列が意味のあるデータへと変わるわけです。

レイヤ 層名 担当する処理 例・備考
7 アプリケーション層 特定のアプリケーションに特化した処理。 電子メール、ファイル転送、HTTP通信など。
6 プレゼンテーション層 データ形式の変換。データ形式が違う場合の整合性担保係。 文字列や画像、音声などの情報の表現の違いを吸収する。
5 セッション層 通信の管理。コネクションの確立/切断など。 コネクションをいつ張って、いつ切断するか?など。
4 トランスポート層 繋がっているノード(機器)間のデータ転送の管理。確実に届けるようにする。 通信のエラーや重複を検出して解決するなど。
3 ネットワーク層 通信の経路を決定する。 ネットワーク内にはたくさんの機器があるので、どの経路で届けるか?とか。
2 データリンク層 直接接続された機器間での通信。 0と1の数字列を意味のある塊に分けて、直接接続された機器に送る。
1 物理層 電気的な信号を送受信する。 0と1の数字列(bit)を、電圧の高低や、光の点滅に変換したり、逆にこれらの物理量をビット列に変換する。

(参考: マスタリングTCP/IP 入門編 P.24)

各レイヤーは、上位のレイヤーから受け取った情報を加工し、下位のレイヤーに渡します。通信の始点と終点では、各レイヤーの処理が逆順に行われます。例えば、送信側では、アプリケーション層から物理層までの処理が行われますが、受信側では、物理層からアプリケーション層までの処理が行われます。要は、同じ手順で送って、同じ手順で読み取るってことです。

OSI参照モデルは、通信プロトコルを分類する際に用いられることが多く、通信の仕組みや処理の流れを理解する上で重要なモデルです。あくまで、ネットワークの理解や議論に有用なモデルでしかない、という点は注意してください。例えば、TCP/IPプロトコルは、OSI参照モデルに沿ってはいますが、7階層あるわけではありません。ガイドラインあって、実装ではないのです。

各層の詳細については、多くの解説記事があるため、ここでは詳細は省きます。

(物理層では、物理現象を電気信号に変換することで、遠く離れた場所と同じ情報をやり取りすることを実現しています。光の速度でデータの元が飛んでくるんで、そりゃインターネット早いっすよね。アナログとデジタルの変換もとても面白かったので、ぜひ調べてみてください。)

(物理の公式、よく分からんかったので、ガンバル。あと、移動体通信って、ほんとすごいと思う)

ネットワークを構成する機器

ネットワークを構成する機器と、役割を整理しましょう。

物理層・データリンク層、レイヤなどのワードが出てきますが、これはOSI参照モデルの概念を参照してください。

機器 役割
ネットワークインターフェース デバイスがネットワークに接続するために必要な装置。 NIC, ExpressCard, (WiFiドングルも当てはまるのかな?)
リピーター 物理層で信号を増幅・整形して、ネットワークを延長する装置。 バンドリピーター、ファイバーリピーター
ブリッジ/レイヤ2スイッチ 物理的に隣接するネットワークを、データリンク層で繋いでネットワークを延長する装置。 ブリッジ、スイッチングハブ
ルーター/レイヤ3スイッチ ネットワーク層で、異なるネットワーク間でデータ(パケット)を転送する装置 ルーター、ブロードバンドルーター
レイヤ4-7スイッチ トランスポート層より上の層で、通信の性能や安定性を高める装置 ロードバランサー、ファイアーフォール
ゲートウェイ プロトコルを変換する役割 アプリケーションゲートウェイ(プロキシサーバ)

(参考: マスタリングTCP/IP 入門編 P.43)

ネットワークインターフェース

ネットワークインターフェース機器とは、PCなどの端末がネットワークに接続するために必要な機器です。

NIC(Network Interface Card)は、コンピュータに接続するためのネットワークカードです。端末に組み込んでない場合は、USBで繋いで使うこともできます。TCP/IPのデータリンク層で使われるイーサネット(Ethernet)プロトコルに対応しています。有線通信ですね。

これは私がよく分かってないんですが、WiFiやBluetoothのドングルも、ネットワークインターフェース機器と言えるんですかね?無線通信を受け取ってくれるものなので。

リピーター

リピーターは、OSI参照モデルの物理層で通信中の信号を増幅(強化)したり、整形したりして、ネットワークの通信範囲を物理的に延長する機器です。

通信経路が長くなり、信号が弱まるといった問題を解決するために使用されます。リピーターで受けた信号を物理的に増幅して、再送する感じですね。物理的な信号は、通信過程で減衰することがあるため、使われます。

音楽で使うアンプみたいなものですかね。(音の物理的な入力を、増幅して出力する)

ブリッジ/レイヤ2スイッチ

ブリッジ/レイヤ2スイッチ(L2スイッチ)は、データリンク層(2層)でネットワーク同士を接続する機器です。

ネットワーク同士といっても、物理的に隣接するネットワークをつなげるのが役割であり、異なるネットワークに接続することはできません。ブリッジはMACアドレスなど、NICを一意に識別する物理アドレスを元にデータを転送しており、ネットワーク層のIPプロトコルに基づいてるわけではないので、異なるネットワークにつなげるための情報を持っていないからです。

あくまで、同一のネットワーク内で、データを転送してネットワークを延長してくれるものです。

(※でも、別なネットワークもいけるのでは?と思い調べたところ、無理やりつなげることはできるみたいです。つなげることはできるが、やはり役割が違うね、という結論でした。参考: 異なるNWへの通信は本当にL2スイッチで出来ないのか

ルーター/レイヤ3スイッチ

ルーター/レイヤ3スイッチ(L3スイッチ)は、ネットワーク層(3層)で、ネットワークとネットワークを接続して、パケット(データ)を中継する機器です。

通信相手のIPアドレスのホストまで、パケットを届ける役割を持っており、ブリッジと異なり、ネットワーク層のアドレス(TCP/IPにおいてはIPアドレス)で処理を行います。

レイヤ3での処理を担うルーターは、データリンク層が異なるプロトコルの場合でも、相互に接続することができます。イーサネットで有線LANで通信されようが、無線LANで通信されようが、IPプロトコルはデータリンクの通信を抽象化しているので、扱うことができるのです。

ここでよく聞く「パケット」というのは、データを表す単位で、小包に例えられます。

先ほど、IPは、データレイヤー層の通信プロトコルが異なってるも接続できると書きましたが、それは言い換えればデータの大きさも統一的ではない、ということです。それらの異なる通信データを抽象的に扱えるよう、IPではデータを分割処理(フラグメンテーション)します。ちょうどこの分割して次のネットワークへと転送する様が、データを小包に分けて、トラックで運送するような感じという事のようです。

このように、ルーターはデータリンク層から来たパケットを、適切な宛先に送り届ける役割を担っており、異なるネットワーク同士をつなぐ機能で、世界的に巨大なネットワーク(インターネット)を実現しています。

ちなみに、自宅でルーターを設定する際、見たことあるかもしれませんが、ルーター機器でもNATやファイアーフォールなど、ネットワーク層より上位層の機能をもっている場合もあります。

レイヤ4-7スイッチ

レイヤ4-7スイッチは、OSI参照モデルの4つのレイヤー(4層、5層、6層、7層)、トランスポート層からアプリケーション層で動作する機器です。レイヤ4-7スイッチは、各層の通信プロトコルに基づき、通信内容をチェック・適切な通信先を選択することで、通信の性能や安定性を高めることができます。

私が一番想像しやすかったのは、ロードバランサーでした。ロードバランサーは種類にもよりますが、アプリケーション層や、トランスポート層で通信先を分散し、アプリケーションサーバなどへの負荷分散を可能としてくれます。

また、特定のIPからの通信を遮断するファイアーフォールなども該当します。単なるIPアドレスでの遮断であればネットワーク層でも可能ですが、例えばHTTPプロトコルなどで通信相手についてより多くの情報を把握した上でフィルタリングしたい場合に、レイヤ4-7スイッチでの処理が優位となります。

ゲートウェイ

ゲートウェイもトランスポート層からアプリケーション層で動作します。データを通信のやり取りに必要な情報を変換して中継する機器です。

その名の通り、直接通信できない異なるプロトコルの翻訳作業などを行います。

例えば、プロキシサーバがアプリケーションサーバとクライアントの通信を代理するのは、アプリケーションゲートウェイと呼ばれます。

また、インターネットのメールと携帯電話のメールは本来プロトコルが異なりますが、どちらも意識せず送受信ができます。これも、ゲートウェイが双方でやり取りができるよう変換してくれているからです。

割とざっくりですが、インターネットを支えるネットワーク機器について、説明してみました。
どんな役割の機器がいるのか、イメージができた所で、プロトコルについてお話します。

3. TCP/IPプロトコル

このように、物理的に飛んできた信号を、増幅したり、横に渡したりしながら、電気信号に変換して、プロトコルに沿って意味のあるデータとして扱い、必要としている人の元へ届けるのが、インターネットの仕組みでした。

中でも、インターネットにおける中心的なプロトコルはTCP/IPでしょう。
TCP/IPはそれぞれ、別レイヤーのプロトコルです。

プロトコル 主な役割
TCP トランスポート層のプロトコルで、双方向のデータ通信を実現する。データが送信される前に、送信元と送信先の接続を確立し、データを送信した後に、接続を切断する。
IP ネットワーク層のプロトコルで、インターネット上で通信するために必要な情報をパケットに付加する。送信元と送信先のIPアドレスを指定し、パケットを送信する。

TCP/IPプロトコルは、通信に必要なプロトコルをレイヤー構造で4階層に分類し、それぞれの層で異なる役割を担っています。OSI参照モデルより階層が少なく見えますが、1つのプロトコルが複数の役割を実装しています。

プロトコル 主な役割
アプリケーション層 HTTP、FTP、SMTPなど アプリケーション間の通信を実現する。
トランスポート層 TCP、UDP 通信端点間での双方向データ通信を実現する。
ネットワーク層 IP、ICMP、ARP 送信元と送信先を指定し、通信するために必要な情報を付加する。
データリンク層 Ethernet、PPPoE、Wi-Fiなど ネットワークインタフェースによるデータの送受信を実

具体的な通信のイメージ図です。
各プロトコルごとにヘッダが付与し、それを解析することで、お互いに情報を交換することができます。誰に、何を、どうやって届けるか?情報をまとめる約束事をみんなで守ることで通信しているのです。

4. golangでネットワーク跨いでパケット渡せるルーターもどきを作る

というわけで、ルーターぽいものを実装してみましょう。
ルーターは、ネットワーク層でパケットを中継する機器でしたね。

基本的に、ネットワーク周りのAPIはカーネルが実装してくれていますが、syscallを多用して作れるほど成熟していなかったので、pcapをラップしたgopacketを使って実装してみます。
プロトコルスタックの実装ではなく、実装済みのスタック・ライブラリを使ってルーターのソフトウェアをざっくりと書いてみる感じです。

※あくまで、こんな感じかな?っていう実装です。考慮してない点や、上手く動いてない部分、めっちゃありますのでご承知おきください。

要件

  • 異なるネットワークへ、パケットと転送できること
  • 処理のイメージを固めるのが目的なので、ライブラリは使ってOK

ネットワーク構成

ネットワークは、Dockerを使って仮想的に分離してみました。(最初はnamespaceなどでやってたのですが、ならdocker使えば早いじゃんという事に気づき…)
2つのネットワークを構成し、送信元のサーバと、パケットを受信するサーバをそれぞれ別のネットワークへ配置しました。
そこへ、どちらのネットワークにも接続しているルーター役のサーバを追加します。

docker networkの情報です。
この、c1コンテナをルーター役にし、ネットワークの異なるc5とc2コンテナ間でルーティングに挑戦しました。

> docker network inspect test
[
    {
        "Name": "test",
        "Driver": "bridge",
				...
        "Containers": {
            "7e4722ddeb2824bf8783678a657588abeb1feca2c4b3e3e3d12db5b805a78770": {
                "Name": "c2",
                "EndpointID": "fb9f570fdc4b9c28eef40719ea689bf48b2afc8985279057a83aa61812cfa799",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/16",
                "IPv6Address": ""
            },
            "e9636acbfead8af836762d0d48fd7965698d2fa417bd46c242515a59ce5f0d4b": {
                "Name": "c1",
                "EndpointID": "9535392b2ec0090c52e8e099150c735cc9c5dd3077b632be4be4435f0dd61f6f",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/16",
                "IPv6Address": ""
            },
					...
        },
    }
]

> docker network inspect test2
[
    {
        "Name": "test2",
        "Driver": "bridge",
        ...
        "Containers": {
            "0158df9ab352b7c401ee79b3d57156b7754dc859d0edeb0114aa2628588e2148": {
                "Name": "c5",
                "EndpointID": "d463a150017bf0765398afae92f8d4a6d566a3b601e110082c97a67131edf6a2",
                "MacAddress": "02:42:ac:15:00:04",
                "IPv4Address": "172.21.0.4/16",
                "IPv6Address": ""
            },
            "e9636acbfead8af836762d0d48fd7965698d2fa417bd46c242515a59ce5f0d4b": {
                "Name": "c1",
                "EndpointID": "06dabaa8a9cc0b1816fa5ceaca3bfe48b8038456495266c340b4efe62fea4dc1",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            },
					...
        }
    }
]

実装

先に結論書いちゃいますが、この実装はパケットの送り方がおかしいです…。
IPヘッダ欠落しちゃってました。直しきれなかったのですが、失敗もまた勉強ということで記事にしますw

次回に持ち越す課題は2つです。

  1. テスト用のパケットの送信側で、自分が送りたいデータを上手く送ることができなかった。(pingはいけた)
  2. ルーターでのパケットの作り方が悪いようで、IPヘッダが上手く作れていなさそう。

パケットが多分ちゃんと作れてなくて、IPヘッダが取れず…。レイヤ1までしか解析できませんでした。
今回はルーティングしたかったので、テスト用に自分宛てのパケットを受けて、宛先を上書きして、pingのパケットを別ネットワークに流すようにしてみました。

見づらいですが、ルーターもどきの実行結果です。本来pingで直接繋がらないネットワークへ、(思ったんと違いますが)パケットが流れました。

ルーターの処理手順は、以下のようなものだろう、という感じで実装しています。

  1. ネットワークインターフェースから入力データを受け取る
  2. 入力データを解析して、IPアドレスを取り出す
  3. 通信の経路を決定する
    • アドレスが自分宛の場合
      • NATとかfirewallとかの処理を挟む。プロトコルヘッダで上位層のプロトコルをチェックして、適切な処理に流す感じだと思っている。
    • ルーティングテーブルを参照し、該当のネットワークアドレスの次の宛先IPアドレスを決める。
  4. ARPやTTLの減算を行う
  5. 宛先アドレスへ、パケットを送り出す。

ルーターもどきのコードです。
↑の手順にそったつもりですが、いろんな処理省かれてます。

package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/moznion/go-iprtb"
)

var (
	ip4     layers.IPv4
	eth     layers.Ethernet
	options gopacket.SerializeOptions
)

type StaticArpTable struct {
	IP               string
	Mac              string
	NetworkInterface string
}

func main() {
	fmt.Println("---------\nListen Packet\n---------")
	ctx := context.Background()

	// pcapめっちゃ便利です。パケットのキャプチャや解析が、くっそ楽にできます。
	// パケットをキャプチャするデバイスを指定する。
	handle, err := pcap.OpenLive("eth1", 65536, true, pcap.BlockForever)
	if err != nil {
		panic("Error opening pcap: " + err.Error())
	}
	defer handle.Close()

	decodedLayers := []gopacket.LayerType{}
	parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4)

	// IPのルーティングテーブルを初期化する。固定値にしてるが、動的に追加する仕組みなど必要。
  // 宛先のIPアドレスから、次に転送するネットワークを特定するものです。ネットワーク空間が近いものが選ばれます。ライブラリお借りました。
	srtb := iprtb.NewRouteTable()
	initStaicRoutingTable(ctx, srtb)
	// ARPテーブルを初期化する。こちらも、各機器をscanするなどして動的に追加する仕組みが必要。
  // IPアドレスから、転送先のMACアドレスとネットワークインターフェースを特定するものです。
	arp := []StaticArpTable{
		{"172.20.0.3", "02:42:ac:14:00:03", "eth0"},
	}

	// パケットのキャプチャを開始する
	src := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
	// チャンネルを作る
	in := src.Packets()

	for {
		select {
		case packet := <-in:
			err := parser.DecodeLayers(packet.Data(), &decodedLayers)
			for _, typ := range decodedLayers {
				switch typ {
				case layers.LayerTypeEthernet:
					// ルーターのMACアドレス宛てなので、IPヘッダをチェックして、ルーティング処理に入る。
					ipLayer := packet.Layer(layers.LayerTypeIPv4)
					if ipLayer == nil {
						continue
					}

					fmt.Println("IPv4 layer detected.")
					ip, _ := ipLayer.(*layers.IPv4)
					if matchLocalIP(ip.DstIP) {
						// ルーター宛のパケットなので、転送せず次のレイヤーの処理に渡す。
						// NATとか、Firewallとか、役割をおうケースもある。
						fmt.Println("my packet detected. handle next layer")
						// nextLayer(ip4)

						// ※今回はルーティングするパケットを上手く送れなかったので、IP層で自分宛てに受けて、宛先だけ上書きして飛ばしてみる。
						// ip.DstIP = net.IPv4(172, 20, 0, 3)
						// routing(ctx, srtb, arp, ip)
						continue
					}

					// ルーティング処理
					routing(ctx, srtb, arp, ip)
				case layers.LayerTypeIPv4:
					fmt.Printf("Source ip = %s - Destination ip = %s \n", ip4.SrcIP.String(), ip4.DstIP.String())
				case layers.LayerTypeIPv6:
					fmt.Println("handle IPv6")
				}
				//etc ....
			}
			if len(decodedLayers) == 0 {
				fmt.Println("Packet truncated")
			}

			if err != nil {
				// fmt.Printf("Layer not found : %s", err)
			}
		}
	}
}

main以外の関数です。
ルーティングテーブルは、コメントで補足してますが、宛先のIPアドレスから、次に転送するネットワークを特定するものです。ネットワーク空間が近いものが選ばれます。Metricで優先度などもつけれます。
今回は静的なテーブルになっていますが、一般的には動的なルーティングテーブルに対応しています。

func initStaicRoutingTable(ctx context.Context, srtb *iprtb.RouteTable) {
	err := srtb.AddRoute(ctx, &iprtb.Route{
		Destination: &net.IPNet{
			IP:   net.IPv4(172, 20, 0, 3),
			Mask: net.IPv4Mask(255, 255, 0, 0),
		}, // 宛先IPアドレス
		Gateway:          net.IPv4(172, 20, 0, 3), // 次に転送するIP
		NetworkInterface: "eth0",                  // eth0で繋がる別ネットワーク
		Metric:           1,
	})
	if err != nil {
		panic(err)
	}
}

func routing(ctx context.Context, srtb *iprtb.RouteTable, arp []StaticArpTable, ip *layers.IPv4) {
	// routeTableから次の転送先を探索
	maybeRoute, err := srtb.MatchRoute(ctx, ip.DstIP)
	if err != nil {
		panic(err)
	}
	next := maybeRoute.Unwrap().Gateway
	if next == nil {
		fmt.Println("send default gateway")
		return
	}

	// 次の宛先に送信する
	writePacket(ip, next, arp)
}

ARP(Address Resolution Protocol)は、IPアドレスからMACアドレスを調査するためのプロトコルです。宛先やデフォルトゲートウェイのMACアドレスを調べるのに使います。通信を送る際は、各プロトコルの仕様に沿う必要があり、データリンク層では物理的なMACアドレスを用いるため、転送にも必要となるのです。

func matchArp(arp []StaticArpTable, ip string) StaticArpTable {
	for _, v := range arp {
		if v.IP == ip {
			return v
		}
	}
	return StaticArpTable{}
}

パケット送信の関数は、上手くIPヘッダを送れていなさそう。プロトコルスタックの理解度上げて、再挑戦します。

func writePacket(ip *layers.IPv4, gateway net.IP, arp []StaticArpTable) {
	srcMac, err := net.ParseMAC("02:42:ac:14:00:04")
	if err != nil {
		log.Fatal(err)
		return
	}
	// arpTableからMacアドレスを特定して、次の転送先へ渡す。
	// 次の転送先が、またルーターである可能性もあり、宛先アドレスに辿り着くまで、ルーターがネットワークを橋渡ししていく。
	dst := matchArp(arp, gateway.String())
	dstMac, err := net.ParseMAC(dst.Mac)
	if err != nil {
		log.Fatal(err)
		return
	}

	ethernetLayer := &layers.Ethernet{
		SrcMAC: srcMac,
		DstMAC: dstMac,
	}
	// IPパケットは詰め直しはするが、宛先・送信元アドレスは変わらない
	ipLayer := &layers.IPv4{
		SrcIP: ip.SrcIP,
		DstIP: ip.DstIP,
	}
	b := gopacket.NewSerializeBuffer()
	gopacket.SerializeLayers(b, options,
		ethernetLayer,
		ipLayer,
		gopacket.Payload(ip.Payload),
	)
	h, _ := pcap.OpenLive(dst.NetworkInterface, 65536, true, pcap.BlockForever)
	defer h.Close()
	err = h.WritePacketData(b.Bytes())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("packet send next pop")
}

func matchLocalIP(ip net.IP) bool {
	addresses, err := net.InterfaceAddrs()
	if err != nil {
		log.Fatal(err)
		return false
	}

	matched := false
	for _, a := range addresses {
		ipNet, ok := a.(*net.IPNet)
		if !ok && ipNet.IP.IsLoopback() {
			continue
		}
		if ipNet.IP.To4() != nil && ipNet.IP.Equal(ip) {
			matched = true
			break
		}
	}
	return matched
}

宛先のサーバーですが、こんな感じで、tcpdumpが見たことない状態になりました。Ethernetプロトコルで受け取っていて、IPヘッダがついてなさそうな感じしますね。

root@7e4722ddeb28:/# tcpdump -tnl -i eth0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
 [|llc]
 [|llc]
 [|llc]
 [|llc]

キャプチャできたので、とりあえず受け取ってはくれていそうです。

まだプトロコルスタック自体の実装への理解度が足りないので、よく分からんというのが、正直な感想です。
言葉で整理するだけだと簡単そうに見えるのですが、実際に書いてみると、いやー理解できてない部分多かったです。

次はTCP/IPのプロトコルスタックを実装してから、再挑戦しようと思います!参考文献も買った!
プロトコルスタックは状態遷移やACKの手順が多いので、結構カロリーかかりそうですが、やってみますー!

割と残念なオチでしたが、ここまで読んでくださり、ありがとうございました!
内容へのフィードバックや質問などあれば、ぜひお願いします!

ではではー。

(PR)
Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
Supership株式会社 採用サイト

参考文献

マスタリングTPC/IP 入門編 第5版
ネットワークはなぜつながるのか
情報ネットワーク(放送大学′18)
Goならわかるシステムプログラミング
ソースコードで体感するネットワークの仕組み

参考記事

異なるNWへの通信は本当にL2スイッチで出来ないのか
ネットワーク機器講座
Dockerのブリッジネットワークについて調べました
Goでパケットキャプチャを実践してみる

Discussion