NiceLeeのBlog 用爱发电 bilibili~

Go 如何在http handler中获取client hello的原始数据

2024-08-21
nIceLee

阅读:

 

主要是实现思路。

思路

  • 在函数func (w http.ResponseWriter, r *http.Request)中,我们可以得到每次请求的r.Context()。 接下来我们想办法将数据放到这个context里面来。

  • http.Server的配置中,能够通过ConnContext,将每次的net.Conn放到context里面去。
    接下来我们想办法,将net.Conn封装,进行二次设计。
    &http.Server{
      Addr: BindAddr,
      TLSConfig: &tls.Config{ ... },
      ConnContext: func(ctx context.Context, c net.Conn) context.Context {
          return context.WithValue(ctx, ConnContextKey, &c)
      },
    }
    
  • 在原来的net.Conn的基础上,封装的conn存储了建立TCP连接后的第一次ClientHello消息。
    接下来我们想办法,让每次accept的不是原始net.Conn,而是封装的conn
    这需要对net.Listener进行封装。

    type JA3Capture interface {
        IsCaptured() bool
        CapturedBytes() []byte
        JA3() *ja3.JA3
    }
    
    type conn struct {
        net.Conn
        ja3             *ja3.JA3
        firstFrame      []byte
        isNotFirstFrame bool
    }
    
    func (c *conn) Read(b []byte) (int, error) {
        n, e := c.Conn.Read(b)
        if e == nil && !c.isNotFirstFrame {
            if c.firstFrame == nil {
                c.firstFrame = b[:n]
            } else {
                c.firstFrame = append(c.firstFrame, b[:n]...)
            }
            j, err := ja3.ComputeJA3FromSegment(c.firstFrame)
            if err == nil {
                c.isNotFirstFrame = true
                c.ja3 = j
                // log.Println("Success: len(c.firstFrame):", len(c.firstFrame))
            } else if len(c.firstFrame) > 2048 {
                // log.Println("Fail: len(c.firstFrame):", len(c.firstFrame))
                return n, err
            }
        }
        return n, e
    }
    
    func (c *conn) IsCaptured() bool {
        return c.isNotFirstFrame
    }
    func (c *conn) CapturedBytes() []byte {
        return c.firstFrame
    }
    
    func (c *conn) JA3() *ja3.JA3 {
        return c.ja3
    }
    
  • 在原来的net.Listener的基础上,封装的listener每一次连接建立后返回的都是封装的conn
    至此,所有关节已经全部打通,无需再想办法了。

    type listener struct {
        net.Listener
    }
    
    func (l *listener) Accept() (net.Conn, error) {
        c, err := l.Listener.Accept()
        if err != nil {
            return nil, err
        }
        return &conn{Conn: c}, nil
    }
    
    func NewListener(inner net.Listener) net.Listener {
        l := new(listener)
        l.Listener = inner
        return l
    }
    
  • 初始化示例:
    srv := &http.Server{
        Addr: BindAddr,
        TLSConfig: &tls.Config{ ... },
        ConnContext: func(ctx context.Context, c net.Conn) context.Context {
            return context.WithValue(ctx, ConnContextKey, &c)
        },
    }
    
    // 监听TCP端口
    ln, e := net.Listen("tcp", BindAddr)
    if e != nil {
        panic(e)
    }
    // defer ln.Close()
    
    lnWrap := NewListener(ln)
    defer lnWrap.Close()
    srv.ServeTLS(lnWrap, "", "")
    
  • Handler举例:
    func HandlerCapJSON(w http.ResponseWriter, r *http.Request) {
        // 尝试获取Client Hello消息
        netConn := r.Context().Value(ConnContextKey).(*net.Conn)
        tlsConn, ok := (*netConn).(*tls.Conn)
        if !ok {
            w.WriteHeader(400)
            w.Write([]byte("The HTTP should over TLS!!!"))
            log.Println("The HTTP should over TLS!!!")
            return
        }
        cap, ok := tlsConn.NetConn().(JA3Capture)
        if !ok {
            w.WriteHeader(400)
            w.Write([]byte("There should be a JA3Capture"))
            log.Println("There should be a JA3Capture")
            return
        }
        // 返回结果
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(200)
        ja3 := cap.JA3()
        result := make(map[string]string)
        result["sni"] = ja3.GetSNI()
        result["ja3_hash"] = ja3.GetJA3Hash()
        result["ja3_str"] = ja3.GetJA3String()
        result["ua"] = r.Header.Get("User-Agent")
    
        re, _ := json.Marshal(result)
        w.Write(re)
    }
    

内容
隐藏