Open15

写経:Black Hat Go

shinobe179shinobe179

TCP:1-1024までを対象としたスキャナを書いているが、25で止まるので多分インタラクティブ待ちで捕まっている

shinobe179shinobe179

goroutineを使ってスキャナを早くする

func main() {
    for i := 1; i <= 1024; i++ {
        go func(j int) {...}(i)
    }
}

go func(j int) {...}(i) の最後の(i)は、多分無名関数を定義して早速使うにあたっての引数なんだと思う

shinobe179shinobe179

単純なforループだと4分

real    4m38.298s
user    0m0.734s
sys     0m0.745s
shinobe179shinobe179

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$
shinobe179shinobe179

全ポート分の終了を待たずに、ループが終わったらそこで試合終了しちゃってるのか(解説)

shinobe179shinobe179

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
shinobe179shinobe179

結果、速くはなっている。

real    2m13.181s
user    0m0.310s
sys     0m0.202s
shinobe179shinobe179

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)
}
shinobe179shinobe179

chanは配列じみた存在で、main関数内で以下が行われているって解釈でいいのかな

  • 1つ目のforループで同じchanを共有するworkerを100個作る
  • 2つ目のforループでchanにポート番号を突っ込んでいく
  • それぞれのworkerはchanに入ったポート番号を早い者勝ちで処理していく
shinobe179shinobe179

100個のworkerがみんな同じportsというchannelを参照してループしようとしているイメージだけど、最初のほうはportsのlenが0だからループがすぐ終わっちゃうということはないのか?

shinobe179shinobe179

例として適切なのか分からないけど、これでなんとなく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.
shinobe179shinobe179

エコーサーバーの実装を通じた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()
shinobe179shinobe179

エコーサーバーを3パターン作った

  • conn.Read, conn.Writeを使ったもの
  • bufio.NewReader, bufio.NewWriterを使ったもの
  • io.Copyを使ったもの