# 20220529 低レベル勉強会 Go言語でTCP/IP をみんなでチャレンジ!
## 用意するもの
PC、ないし何らかのLinux環境
- RaspberryPi や WSL でもOK
- Docker でも OKかな?
- Goをインストールする必要があります
Mac OSX はうまく行かない?
→TCPIPスタックが異なるので下記にあるエラーが出てしまう
MacはDockerでするのが吉?
## go-tcpip レポジトリ
今回のワークショップはこのレポジトリのサンプルプログラムを用います
https://github.com/sat0ken/go-tcpip
## pingを試す
```shell=
git clone https://github.com/sat0ken/go-tcpip.git
cd go-tcpip
go get
cd example
vim ping.go # destのIPアドレスとインターフェイスを自分の環境に書き換える
go build ping.go
sudo ./ping # root権限が必要なんで注意
```
実行例
```
ffffffffffffb827eb4e681308060001080006040001b827eb4e6813c0a82a08000000000000c0a82a01
ARP Reply : d4f756b49066
send icmp packet
ICMP Reply is 0, OK!
```
### 注意点
- IPアドレスは今のネットワークアドレス内のものを指定しよう
- 127.0.0.1 を使う場合はインターフェースを lo 用に変更
## Mac OSX 環境でうまくいかないぞ!
```
deth@~/Documents/Study/go-tcpip-main/example$ go get
# tcpip
../arp.go:51:10: undefined: syscall.SockaddrLinklayer
../arp.go:56:32: undefined: syscall.AF_PACKET
../arp.go:56:79: undefined: syscall.ETH_P_ALL
../connection.go:36:10: undefined: syscall.SockaddrLinklayer
../dns.go:49:10: undefined: syscall.SockaddrLinklayer
../dns.go:54:32: undefined: syscall.AF_PACKET
../dns.go:54:79: undefined: syscall.ETH_P_ALL
../icmp.go:37:10: undefined: syscall.SockaddrLinklayer
../icmp.go:42:32: undefined: syscall.AF_PACKET
../socket.go:66:42: undefined: syscall.SockaddrLinklayer
../icmp.go:42:32: too many errors
note: module requires Go 1.17
```
Docker 使って回避?
## Dockerでテスト
Dockerfile:
```dockerfile=
FROM golang:1.18.2-bullseye
RUN apt-get update && apt-get install -y \
iproute2 \
vim \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir /go/src/app
WORKDIR /go/src/app
COPY go.mod /go/src/app
COPY go.sum /go/src/app
RUN go mod download
ADD . /go/src/app
```
docker build と実行(`--net=host`オプションが無いと動かない)
```shell=
#Dockerfileがあるディレクトリで実行
docker build -t go-tcpip .
docker run --net=host -it go-tcpip
```
:::info
go.modの以下の箇所をコメントアウトしないとエラーが出るかもしれない
```
replace (
github.com/lucas-clemente/quic-go => ./debug/quic-go
github.com/marten-seemann/qtls-go1-17 => ./debug/qtls-go1-17
)
```
:::
```shell=
cd example
vim ping.go # network if eth0
go build ping.go
./ping
```
## Go 環境をインストールしよう!
### 公式からインストール
goのインストール
https://go.dev/doc/install
### Raspberry Pi に公式パッケージをインストール
https://qiita.com/nanbuwks/items/9de251ec171c6757eebe
同様にして、 WSL2でもインストールできました
### Ubuntu にレポジトリを追加してインストール
普通にインストールした場合、goのバージョンが古い可能性があります。
↓
ubuntu@primary:~/go-tcpip/example$ go build ping.go
#tcpip
../utils.go:81:12: undefined: tls.CipherSuites
note: module requires Go 1.17
20.04のリポジトリ追加
```
$ sudo add-apt-repository ppa:longsleep/golang-backports
$ sudo apt update
$ sudo apt install golang
```
## Tips
### WSL
Windows で WSL を使う場合の注意
- WSL2だと仮想マシンとなり別NWとなる
- WSL2 で 127.0.0.1/lo をターゲットにしても返事が返ってこないぞ 原因は不明
- 今回のプログラムはTCPヘッダサイズを 20Bytes としている。Widnowsはヘッダに付加情報を付けるので何か影響が出るかも?
今回のプログラムで Ping を送る対象は同一ネットワーク上のものとなります。127.0.0.1 が使えないぽいので送る対象はコンテナを起動させてそれにARPやPingを送ってみるとか、ホストWindowsに送るなどの工夫が必要です。
以下はホストWindows にPingを送る例です
WSL2でホストWindowsのアドレスを取得
```
$ ip route | grep 'default via'
```
取得例
```
default via 172.18.160.1 dev eth0
```
/etc/resolv.conf を参照したり Windows側で IPCONFIG コマンドを使っても取得できます。
取得したアドレスに WSL2上のUbuntuコマンドで ping が通る状態にしておきます。
通常は ICMP 応答が Windows 側で遮断されているので、以下の資料などを基に遮断を解除します。
「【Windows 11対応】Windowsのファイアウォールで「ping」コマンドへの応答を許可する」
https://atmarkit.itmedia.co.jp/ait/articles/1712/21/news018.html
WSL2上のネットワークアダプターなどの情報を取得する
```
$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether de:89:07:12:6b:5d brd ff:ff:ff:ff:ff:ff
3: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 32:5c:6f:c4:7f:83 brd ff:ff:ff:ff:ff:ff
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:cf:c5:ce brd ff:ff:ff:ff:ff:ff
inet 172.18.160.195/20 brd 172.18.175.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fecf:c5ce/64 scope link
valid_lft forever preferred_lft forever
5: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
6: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
```
上記の例を基にした場合、以下のように ping.go を設定することになります。
```
// 各自の環境に変えてください
destip := "172.18.160.1"
// wlp3s0は各自の環境に変えてください
localif, err := tcpip.GetLocalInterface("eth0")
```
上記のように設定したものの実行例です
```
$ sudo ./ping
ffffffffffff00155dcfc5ce0806000108000604000100155dcfc5ceac12a0c3000000000000ac12a001
ARP Reply : 00155d7b5138
send icmp packet
ICMP Reply is 0, OK!
```
### RaspberryPi でエラー
RaspberryPi OS として 32bit のものを使っているのが原因かな。
```
$ go build ping.go
# tcpip
../tcpip.go:33:49: cannot use 4294967295 (untyped int constant) as int value in argument to rand.Intn (overflows)
```
該当箇所がこうなっているので
```
binary.BigEndian.PutUint32(b, uint32(rand.Intn(4294967295)))
```
以下のように変更して動きました
```
binary.BigEndian.PutUint32(b, uint32(rand.Uint32()))
```
### docker build で RUN go mod downloadでエラー
go.modファイルの以下をコメントアウトしてください。
```
replace (
github.com/lucas-clemente/quic-go => ./debug/quic-go
github.com/marten-seemann/qtls-go1-17 => ./debug/qtls-go1-17
)
```
### multipath
multipassを使ってM1 Mac上でUbuntu VM (ARM版)を動かしてみた - Qiita
https://qiita.com/notakaos/items/928987623fc61e815363
#### Windowsの場合
1. インストーラをダウンロード。
https://multipass.run/download/windows
3. インストーラ実行
基本的に「次へ」で進めます。
参考:multipass を Windowsで使う
https://qiita.com/ynott/items/7b462172890140aad738
5. PC再起動
6. コマンドプロンプトかパワーシェルを起動
7. ubuntuを起動するため以下のコマンドを実行
multipass shell
8. ubuntuが起動
9. 「Go 環境をインストールしよう!」「Ubuntu にレポジトリを追加してインストール」を参考にgoインストール。
10. あとは「pingを試す」の項目と流れは同じ
11. 終了したい時は exit でシェルを抜ける。
12. ubuntuを終了するには下記コマンドを実行。
multipass stop
以上
#### Macの場合
1. Homebrewにてインストール
brew install --cask multipass
2. Ubuntu VMを作成する
multipass launch --cpus 2 --disk 20G --mem 4G --name ubuntu
3. ubuntuを起動するため以下のコマンドを実行
multipass shell