go TCP 连接到http服务 如何获取响应?

目标:golang 服务,准备获取ip:port的banner信息.
如果ip:port为http,即可获取http响应,如果为ssh服务,即可获取ssh服务输出信息.如版本信息等.

前置条件:对应tcpAddr可以访问(该http服务输出helloworld)
问题:通过代码

    conn, err := net.DialTimeout("tcp", tcpAddr.String(),timeout)
    if err != nil {
        return false
    }
    reply := make([]byte, 4096)
    conn.Read(reply)
    conn.SetReadDeadline(time.Now().Add(time.Second))
    byte, err := ioutil.ReadAll(conn)
    fmt.Println("srv reply:" + string(byte))
    defer conn.Close()

发现线程阻塞到read上.
请问如何避免这种阻塞,并能获取到对应endpoint banner信息?

追加:如果先SetReadDeadline,会在超时后解除阻塞.但获取到的reply为空.

阅读 7.1k
3 个回答

假定你要做一个 SSH 与 HTTP 的服务扫描器,那你可能没有发现 SSH 与 HTTP 服务对于客户端的区别。

对 SSH 服务来说,客户端完成 TCP 连接后,就会收到服务器发送的 banner 信息,类似这样的

SSH-2.0-xxxxxx\r\n...

但是 HTTP 服务器不一样,客户端完成 TCP 连接后,必须发送一个请求,如

HEAD / HTTP/1.0\r\n\r\n

服务器才会返回内容,如

HTTP/1.0 404 NotFound\r\n
...

下面是参考代码

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "net"
    "sync"
    "time"
)

// 假定是 SSH 服务。
// 返回 banner 第一行。
func assume_ssh(address string) (string, error) {
    conn, err := net.DialTimeout("tcp", address, time.Second*10)
    if err != nil {
        return "", err
    }
    defer conn.Close()
    tcpconn := conn.(*net.TCPConn)
    // 设置读取的超时时间
    tcpconn.SetReadDeadline(time.Now().Add(time.Second * 5))
    reader := bufio.NewReader(conn)
    return reader.ReadString('\n')
}

func split_http_head(data []byte, atEOF bool) (advance int, token []byte, err error) {
    head_end := bytes.Index(data, []byte("\r\n\r\n"))
    if head_end == -1 {
        return 0, nil, nil
    }
    return head_end + 4, data[:head_end+4], nil
}

// 假定是 HTTP 服务。
// 返回 "/" HTTP 返回头。
func assume_http(address string) (string, error) {
    conn, err := net.DialTimeout("tcp", address, time.Second*10)
    if err != nil {
        return "", err
    }
    defer conn.Close()
    tcpconn := conn.(*net.TCPConn)
    // 设置写的超时时间
    tcpconn.SetWriteDeadline(time.Now().Add(time.Second * 5))
    if _, err := conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")); err != nil {
        return "", err
    }
    // 设置读的超时时间
    tcpconn.SetReadDeadline(time.Now().Add(time.Second * 5))
    scanner := bufio.NewScanner(conn)
    scanner.Split(split_http_head)
    if scanner.Scan() {
        return scanner.Text(), nil
    }
    err = scanner.Err()
    if err == nil {
        err = io.EOF
    }
    return "", err
}

func check_address(address string) {
    result := make(chan string, 2)
    done := make(chan int, 1)
    var g sync.WaitGroup
    g.Add(2)
    go func() {
        if r, e := assume_ssh(address); e == nil {
            result <- fmt.Sprintf("SSH: %s", r)
        }
        g.Done()
    }()
    go func() {
        if r, e := assume_http(address); e == nil {
            result <- fmt.Sprintf("HTTP: %s", r)
        }
        g.Done()
    }()
    go func() {
        g.Wait()
        done <- 1
    }()
    select {
    case <-done:
        fmt.Printf("# %s\n无结果", address)
    case r := <-result:
        fmt.Printf("# %s\n%s", address, r)
    }
}

func main() {
    check_address("github.com:80")
    check_address("github.com:22")
}

运行结果

# github.com:80
HTTP: HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https:///
Connection: close

# github.com:22
SSH: SSH-2.0-libssh_0.7.0

tcp在传输层,http在应用层

是否能这样直接使用呢?这个存在疑问。

因为我是没有这样用过,基本上服务端是http,client就使用http协议去对接。tcp也一样。

使用select吧,多路复用,不存在所谓的阻塞问题。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题