实现一个端口扫描器
端口的开放与否基于如下判断

第一个端口扫描代码
package mainimport ("fmt""net")func main() {_, err := net.Dial("tcp", "129.211.73.107:80")if err == nil {fmt.Println("Connection successful")}else{fmt.Println("connect error")}}
看一下Dial有文档
➜ p2 go doc net.Dialwarning: pattern "all" matched no module dependenciespackage net // import "net"func Dial(network, address string) (Conn, error) //注意这里第二个参为string类型Dial connects to the address on the named network.Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp","udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6"(IPv6-only), "unix", "unixgram" and "unixpacket".For TCP and UDP networks, the address has the form "host:port". The hostmust be a literal IP address, or a host name that can be resolved to IPaddresses. The port must be a literal port number or a service name. If thehost is a literal IPv6 address it must be enclosed in square brackets, as in"[2001:db8::1]:80" or "[fe80::1%zone]:80". The zone specifies the scope ofthe literal IPv6 address as defined in RFC 4007. The functions JoinHostPortand SplitHostPort manipulate a pair of host and port in this form. Whenusing TCP, and the host resolves to multiple IP addresses, Dial will tryeach IP address in order until one succeeds.Examples:Dial("tcp", "golang.org:http")Dial("tcp", "192.0.2.1:http")Dial("tcp", "198.51.100.1:80")Dial("udp", "[2001:db8::1]:domain")Dial("udp", "[fe80::1%lo0]:53")Dial("tcp", ":80")For IP networks, the network must be "ip", "ip4" or "ip6" followed by acolon and a literal protocol number or a protocol name, and the address hasthe form "host". The host must be a literal IP address or a literal IPv6address with zone. It depends on each operating system how the operatingsystem behaves with a non-well known protocol number such as "0" or "255".Examples:Dial("ip4:1", "192.0.2.1")Dial("ip6:ipv6-icmp", "2001:db8::1")Dial("ip6:58", "fe80::1%lo0")For TCP, UDP and IP networks, if the host is empty or a literal unspecifiedIP address, as in ":80", "0.0.0.0:80" or "[::]:80" for TCP and UDP, "","0.0.0.0" or "::" for IP, the local system is assumed.For Unix networks, the address must be a file system path.
扫描1024端口
package mainimport ("fmt""net")func main() {for i:=1;i<1024;i++{address := fmt.Sprintf("129.211.73.107:%d",i)_,err := net.Dial("tcp",address)if err != nil{continue} else {fmt.Printf("[+] port open -> %d \n",i)}}}
goroutine版本
import ("fmt""net")func main() {for i := 1; i <= 1024; i++ {go func(j int) {address := fmt.Sprintf("129.211.73.107:%d", j)conn, err := net.Dial("tcp", address)if err != nil {return}conn.Close()fmt.Printf("%d open\n", j)}(i)}}
但很遗憾这是没有输出的。原因在上一章已经说明,主线程如果这样来写是不会等待goroutine结束的,当循环1024后就直接退出,也就没有goroutine输出了。
waitGroup版本
package mainimport ("fmt""net""sync""time")var wg sync.WaitGroup // 声名waitgroupfunc main() {stime := time.Now()for i:=1;i<1024;i++{go func(j int) {wg.Add(1) // 当前队伍等待数量--defer wg.Done() // defer是一个关键字,当前函数所有操作完成后再执行address := fmt.Sprintf("129.211.73.107:%d",j)_,err :=net.Dial("tcp",address)if err == nil{fmt.Printf("[+] port open -> %d\n",j)}}(i)}wg.Wait() // 主线程等待等待队伍为0再结束fmt.Println(time.Since(stime)) // 计算程序运行时间}
workerpool版本
虽然我也不知道上面的版本有啥问题?没有排序?
package mainimport ("fmt""net""sort")func worker(ports, results chan int) {for p := range ports {address := fmt.Sprintf("129.211.73.107:%d", p)conn, err := net.Dial("tcp", address)if err != nil {results <- 0continue}conn.Close()results <- p}}func main() {ports := make(chan int, 100)results := make(chan int)var openports []intfor i := 0; i < cap(ports); i++ {go worker(ports, results)}go func() {for i := 1; i <= 1024; i++ {ports <- i}}()for i := 0; i < 1024; i++ {port := <-resultsif port != 0 {openports = append(openports, port)}}close(ports)close(results)sort.Ints(openports)for _, port := range openports {fmt.Printf("%d open\n", port)}}

使用两个chan来输入和输出结果,好在能够对结果数据进行处理,但并不能实时显示。
课后改进版
package mainimport ("flag"//"debug/elf""fmt""net""strconv""strings""sync")var wg sync.WaitGroupvar (h stringd stringp string)func scan(host string,ports []int) {for _,i := range(ports){go func(j int) {wg.Add(1)defer wg.Done()address := fmt.Sprintf("%s:%d",host,j)_,err :=net.Dial("tcp",address)if err == nil{fmt.Printf("[+] port open -> %d\n",j)}}(i)}wg.Wait()}func parsePorts(pstring string) []int {var ports = make([]int,0)parts := strings.Split(pstring,",")for _,part := range(parts){if strings.Index(part,"-") == -1{port,err := strconv.Atoi(part)if err == nil{ports = append(ports, port)}else {fmt.Println("psrse error")return nil}}else {left_right := strings.Split(string(part),"-")start,err := strconv.Atoi(left_right[0])end,err2 := strconv.Atoi(left_right[1])if err == nil && err2 == nil{for i := start;i<=end;i++{ports = append(ports,i)}}else {}}}//fmt.Println(ports)return ports}func main() {flag.StringVar(&h,"h","","host")flag.StringVar(&p,"p","","ports to scan")flag.Parse()//stime := time.Now()//fmt.Println(time.Since(stime))if h!="" && p != ""{scan(h,parsePorts(p))}else {flag.Usage()}}
TCP代理
stdin和stdout
两个接口
type Reader interface {Read(p []byte) (n int, err error)}type Writer interface {Write(p []byte) (n int, err error)}
package mainimport ("fmt""log""os")// FooReader defines an io.Reader to read from stdin.type FooReader struct{}// Read reads data from stdin.func (fooReader *FooReader) Read(b []byte) (int, error) { // 这里实现了Read接口fmt.Print("in > ")return os.Stdin.Read(b) // 这个函数从系统的标准输入len(b)长度内读取字符,返回读取到的字符长度,此时input内已经存入“123"和4093空字符}// FooWriter defines an io.Writer to write to Stdout.type FooWriter struct{}// Write writes data to Stdout.func (fooWriter *FooWriter) Write(b []byte) (int, error) { // 这里实现了Write接口fmt.Print("out> ")return os.Stdout.Write(b) // 向系统标准输出写入b}func main() {// Instantiate reader and writer.var (reader FooReaderwriter FooWriter)// Create buffer to hold input/output.input := make([]byte, 4096)// Use reader to read input.s, err := reader.Read(input)if err != nil {log.Fatalln("Unable to read data")}fmt.Printf("Read %d bytes from stdin\n", s)// Use writer to write output.s, err = writer.Write(input)if err != nil {log.Fatalln("Unable to write data")}fmt.Printf("Wrote %d bytes to stdout\n", s)}
os.Stdin.Read 这个函数从系统的标准输入len(b)长度内读取字符,返回读取到的字符长度,此时input内已经存入”123”和4093空字符所以将input再写入stdout就会全部写出。
io.Copy函数
func main() {var (reader FooReaderwriter FooWriter)if _, err := io.Copy(&writer, &reader)❶; err != nil {log.Fatalln("Unable to read/write data")}}
这个函数比较魔幻,隐式调用Read和Write方法,儿没有繁琐的细节。
io on tcp
package mainimport ("io""log""net")// echo is a handler function that simply echoes received data.func echo(conn net.Conn) {defer conn.Close() // 函数运行完成关闭Conn// Create a buffer to store received data.b := make([]byte, 512)for {// Receive data via conn.Read into a buffer.size, err := conn.Read(b[0:])if err == io.EOF {log.Println("Client disconnected")break}if err != nil {log.Println("Unexpected error")break}log.Printf("Received %d bytes: %s\n", size, string(b))// Send data via conn.Write.log.Println("Writing data")if _, err := conn.Write(b[0:size]); err != nil {log.Fatalln("Unable to write data")}}}func main() {// Bind to TCP port 20080 on all interfaces.listener, err := net.Listen("tcp", ":20080")if err != nil {log.Fatalln("Unable to bind to port")}log.Println("Listening on 0.0.0.0:20080")for {// Wait for connection. Create net.Conn on connection established.conn, err := listener.Accept()log.Println("Received connection")if err != nil {log.Fatalln("Unable to accept connection")}// Handle the connection. Using goroutine for concurrency.go echo(conn) // go关键字执行非阻塞}}
这里的Conn
type netFD struct {pfd poll.FD// immutable until Closefamily intsotype intisConnected bool // handshake completed or use of association with peernet stringladdr Addrraddr Addr}
bufio
func echo(conn net.Conn) {defer conn.Close()reader := bufio.NewReader(conn)s, err := reader.ReadString('\n') // 指定读取到回车停止if err != nil {log.Fatalln("Unable to read data")}log.Printf("Read %d bytes: %s", len(s), s)log.Println("Writing data")writer := bufio.NewWriter(conn)if _, err := writer.WriteString(s); err != nil {log.Fatalln("Unable to write data")}writer.Flush()}
注意的是
- bufio.NewReader
- reader.ReadString
- bufio.NewWriter
- writer.WriteString
再次简化
没错使用io.Copy,上面已经说过了,Conn实现了Read和Write方法,那么就可以直接传入io.Copy函数,由io.Copy函数自动调用Read和Write方法。func echo(conn net.Conn) {defer conn.Close()// Copy data from io.Reader to io.Writer via io.Copy().if _, err := io.Copy(conn, conn); err != nil {log.Fatalln("Unable to read/write data")}}
实现一个端口转发
```go package main
import ( “flag” “fmt” “io” “log” “net” “os” )
var ( lp int r string rp int )
func main() { Init() listener,err := net.Listen(“tcp”,fmt.Sprintf(“:%d”,lp)) if err != nil{ log.Fatal(“bind lport error !”) return } log.Printf(“start listen on 0.0.0.0:%d … “,lp) for { conn,err := listener.Accept() if err !=nil{ log.Fatal(“receive conn error !”) } log.Printf(“receive connection from %s\n”,conn.RemoteAddr()) go handler(conn) } }
func Init() { flag.IntVar(&lp,”l”,4444,”listen localhost port”) flag.StringVar(&r,”r”,””,”remote host”) flag.IntVar(&rp,”p”,80,”remote port”) flag.Parse() if r ==””{ flag.Usage() os.Exit(0) } }
func handler(conn net.Conn) { defer conn.Close() dstconn,err :=net.Dial(“tcp”,fmt.Sprintf(“%s:%d”,r,rp)) if err != nil{ log.Fatal(“bind port error ! “) return } log.Printf(“handling data …”) go func() { if ,err = io.Copy(dstconn,conn);err!=nil{ log.Fatalln(“connect error !”) } log.Printf(“>>> %s:%d\n”,r,rp) }() ,err = io.Copy(conn,dstconn) if err != nil{ log.Fatal(“proxy error ! “) } log.Printf(“<<< %s:%d\n”,r,rp)
}
这里的精髓就是57到62行需要用go关键字进行异步执行,否则数据就有去无回。<br /><a name="bNWGO"></a>### netcatexec```gopackage mainimport ("fmt""io""net""os""os/exec")var (lp intr stringrp int)func main() {if len(os.Args) != 2{fmt.Println("netcatexec 8080")os.Exit(0)}port := os.Args[1]listener,err := net.Listen("tcp",fmt.Sprintf(":%s",port))if err!=nil{fmt.Printf("bind port %d error\n",port)}for{conn,err := listener.Accept()if err != nil {fmt.Println("accept error !")}handler(conn)}}func handler(conn net.Conn) {defer conn.Close()cmd := exec.Command("/bin/sh","-i")rp,wp := io.Pipe()cmd.Stdin = conncmd.Stdout = wpgo io.Copy(conn,rp)cmd.Run()}


