Open15
写経:Black Hat Go
ピン留めされたアイテム
経典
目的
- Goの文法や特徴を理解して、ツールを書けるようになること
TCP:1-1024までを対象としたスキャナを書いているが、25で止まるので多分インタラクティブ待ちで捕まっている
goroutineを使ってスキャナを早くする
func main() {
for i := 1; i <= 1024; i++ {
go func(j int) {...}(i)
}
}
go func(j int) {...}(i)
の最後の(i)は、多分無名関数を定義して早速使うにあたっての引数なんだと思う
単純なforループだと4分
real 4m38.298s
user 0m0.734s
sys 0m0.745s
goroutine版、まだ期待する動作になっていない気がする
ubuntu@ip-172-31-47-10:~/work/black-hat-go$ go run 23_tcp-scanner-goroutine/main.go
ubuntu@ip-172-31-47-10:~/work/black-hat-go$ 20:19:49.715461 IP ip-172-31-47-10.ap-northeast-1.compute.internal.46094 > scanme.nmap.org.4: Flags [S], seq 1277316848, win 62727, options [mss 8961,sackOK,TS val 1595356208 ecr 0,nop,wscale 7], length 0
20:19:49.715575 IP ip-172-31-47-10.ap-northeast-1.compute.internal.52974 > scanme.nmap.org.5: Flags [S], seq 188098094, win 62727, options [mss 8961,sackOK,TS val 1595356208 ecr 0,nop,wscale 7], length 0
20:19:49.715621 IP ip-172-31-47-10.ap-northeast-1.compute.internal.45210 > scanme.nmap.org.tcpmux: Flags [S], seq 992382574, win 62727, options [mss 8961,sackOK,TS val 1595356208 ecr 0,nop,wscale 7], length 0
20:19:49.715672 IP ip-172-31-47-10.ap-northeast-1.compute.internal.38486 > scanme.nmap.org.2: Flags [S], seq 4054056135, win 62727, options [mss 8961,sackOK,TS val 1595356208 ecr 0,nop,wscale 7], length 0
20:19:49.715715 IP ip-172-31-47-10.ap-northeast-1.compute.internal.59272 > scanme.nmap.org.3: Flags [S], seq 1680214321, win 62727, options [mss 8961,sackOK,TS val 1595356208 ecr 0,nop,wscale 7], length 0
20:19:49.819562 IP scanme.nmap.org.5 > ip-172-31-47-10.ap-northeast-1.compute.internal.52974: Flags [R.], seq 0, ack 188098095, win 0, length 0
20:19:49.819570 IP scanme.nmap.org.4 > ip-172-31-47-10.ap-northeast-1.compute.internal.46094: Flags [R.], seq 0, ack 1277316849, win 0, length 0
20:19:49.820320 IP scanme.nmap.org.tcpmux > ip-172-31-47-10.ap-northeast-1.compute.internal.45210: Flags [R.], seq 0, ack 992382575, win 0, length 0
20:19:49.820417 IP scanme.nmap.org.2 > ip-172-31-47-10.ap-northeast-1.compute.internal.38486: Flags [R.], seq 0, ack 4054056136, win 0, length 0
20:19:49.820437 IP scanme.nmap.org.3 > ip-172-31-47-10.ap-northeast-1.compute.internal.59272: Flags [R.], seq 0, ack 1680214322, win 0, length 0
ubuntu@ip-172-31-47-10:~/work/black-hat-go$
全ポート分の終了を待たずに、ループが終わったらそこで試合終了しちゃってるのか(解説)
sync.Waitgroupを使って処理待ちを実装した後、sudo tcpdump dst host scanme.nmap.org &
しつつgoroutine版を実行してみた。
やっぱり25で処理が詰まっている。2, 4, 6, 8と再送制御しているのが分かる。
20:27:05.513236 IP ip-172-31-47-10.ap-northeast-1.compute.internal.54358 > scanme.nmap.org.http: Flags [.], ack 180570265, win 491, options [nop,nop,TS val 1595792006 ecr 2104254820], length 0
20:27:05.515455 IP ip-172-31-47-10.ap-northeast-1.compute.internal.54358 > scanme.nmap.org.http: Flags [F.], seq 0, ack 1, win 491, options [nop,nop,TS val 1595792008 ecr 2104254820], length 0
20:27:05.615128 IP ip-172-31-47-10.ap-northeast-1.compute.internal.47886 > scanme.nmap.org.ssh: Flags [R], seq 64874809, win 0, length 0
20:27:05.615379 IP ip-172-31-47-10.ap-northeast-1.compute.internal.47886 > scanme.nmap.org.ssh: Flags [R], seq 64874809, win 0, length 0
20:27:05.620979 IP ip-172-31-47-10.ap-northeast-1.compute.internal.54358 > scanme.nmap.org.http: Flags [.], ack 2, win 491, options [nop,nop,TS val 1595792113 ecr 2104254928], length 0
20:27:06.441093 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595792934 ecr 0,nop,wscale 7], length 0
20:27:07.465111 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595793958 ecr 0,nop,wscale 7], length 0
20:27:08.489103 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595794982 ecr 0,nop,wscale 7], length 0
20:27:09.513104 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595796006 ecr 0,nop,wscale 7], length 0
20:27:10.537088 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595797030 ecr 0,nop,wscale 7], length 0
20:27:12.585108 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595799078 ecr 0,nop,wscale 7], length 0
20:27:16.617153 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595803110 ecr 0,nop,wscale 7], length 0
20:27:25.129104 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595811622 ecr 0,nop,wscale 7], length 0
20:27:41.513100 IP ip-172-31-47-10.ap-northeast-1.compute.internal.53084 > scanme.nmap.org.smtp: Flags [S], seq 3830421029, win 62727, options [mss 8961,sackOK,TS val 1595828006 ecr 0,nop,wscale 7], length 0
結果、速くはなっている。
real 2m13.181s
user 0m0.310s
sys 0m0.202s
channelを使ったコードを書いているが、なんでworkerはforループしているのか?
func worker(ports chan int, wg *sync.WaitGroup) {
for p := range ports {
fmt.Println(p)
wg.Done()
}
}
func main() {
ports := make(chan int, 100)
var wg sync.WaitGroup
for i := 0; i < cap(ports); i++ {
go worker(ports, &wg)
}
for i := 1; i <= 1024; i++ {
wg.Add(i)
ports <- i
}
wg.Wait()
close(ports)
}
chanは配列じみた存在で、main関数内で以下が行われているって解釈でいいのかな
- 1つ目のforループで同じchanを共有するworkerを100個作る
- 2つ目のforループでchanにポート番号を突っ込んでいく
- それぞれのworkerはchanに入ったポート番号を早い者勝ちで処理していく
ついで
- capでchannelの容量が分かる
- lenだと実際にキューされているメッセージ数が分かる
100個のworkerがみんな同じportsというchannelを参照してループしようとしているイメージだけど、最初のほうはportsのlenが0だからループがすぐ終わっちゃうということはないのか?
例として適切なのか分からないけど、これでなんとなくworker内の for t:= range tasks
でブロックされているのかなという直観を得ている
package main
import (
"fmt"
"time"
)
func worker(tasks, results chan int, id int) {
fmt.Printf("worker %d: wake up.\n", id)
for t := range tasks {
fmt.Printf("worker %d: task %d is received.\n", t, id)
results <- t
}
fmt.Printf("worker %d: finieshed.\n", id)
}
func main() {
fmt.Println("Code is started.")
tasks := make(chan int, 10)
results := make(chan int)
for i := 0; i < 5; i++ {
go worker(tasks, results, i)
}
fmt.Println("Wait 5 seconds.")
time.Sleep(5 * time.Second)
tasks <- 179
result := <- results
fmt.Printf("Task %d done.\n", result)
}
結果
$ go run main.go
Code is started.
Wait 5 seconds.
worker 0: wake up.
worker 2: wake up.
worker 1: wake up.
worker 3: wake up.
worker 4: wake up.
worker 179: task 0 is received.
Task 179 done.
エコーサーバーの実装を通じたio.Reader / io.Writerの理解まとめ
- Goのio.Readerとio.Writerは、インターフェイスとして実装されている
- つまり、
[]byte
を引数として受け取って、(int, error)
を返すReadあるいはWriteというメソッドを持っていればインターフェイスを満たし、同様のものとして扱える - その証明としてFooReaderとFooWriterを実装し、io.Copyの引数として与えて、エコーサーバーとしてちゃんと稼働することを確認した
- io.Copyの本来の引数はio.Writerとio.Readerなので、自作のFooReaderとFooWriterでもインターフェイスを満たしているので問題なく動く
- FooReaderとFooWriterで実際に戻り値の
(int, error)
を担保しているのは、os.Stdin.Read()とos.Stdout.Write()
エコーサーバーを3パターン作った
- conn.Read, conn.Writeを使ったもの
- bufio.NewReader, bufio.NewWriterを使ったもの
- io.Copyを使ったもの