package fwd import ( "bufio" "encoding/binary" "io" "log/slog" "proxy-server/pkg/utils" "proxy-server/server/fwd/socks" "strings" "github.com/pkg/errors" ) func analysisAndLog(conn socks.ProxyConn, reader io.Reader) error { buf := bufio.NewReader(reader) domain, proto, err := sniffing(buf) if err != nil { err = errors.Wrap(err, "analysis sniffing error") } else { slog.Info( "用户访问记录", slog.Uint64("uid", uint64(conn.Uid)), slog.String("user", conn.Conn.RemoteAddr().String()), slog.String("proxy", "socks"), slog.String("node", conn.Conn.LocalAddr().String()), slog.String("proto", proto), slog.String("dest", conn.Dest), slog.String("domain", domain), ) } go func() { discord(buf) }() return err } func sniffing(reader *bufio.Reader) (string, string, error) { peek, err := reader.Peek(8) if err != nil { return "", "", errors.Wrap(err, "sniffing peek error") } method, ok := isHttp(peek) if ok { domain, err := analysisHttp(reader) return domain, "http(" + method + ")", err } tlsType, tlsVersion, ok := isTls(peek) if ok { var domain string if tlsType == "handshake" { domain, err = analysisTls(reader) } return domain, "tls(" + tlsType + "," + tlsVersion + ")", err } return "nil", "tcp", nil } func isHttp(bytes []byte) (string, bool) { var blankIndex int for i := range bytes { if bytes[i] == ' ' { blankIndex = i break } } method := string(bytes[:blankIndex]) switch method { case "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE", "CONNECT": return method, true } return "", false } func isTls(bytes []byte) (string, string, bool) { var tlsType string switch bytes[0] { case 0x14: tlsType = "change-cipher-spec" case 0x15: tlsType = "alert" case 0x16: tlsType = "handshake" case 0x17: tlsType = "application-data" } var tlsVersion string if bytes[1] == 0x03 { switch bytes[2] { case 0x00: tlsVersion = "SSL3.0" case 0x01: tlsVersion = "TLS1.0" case 0x02: tlsVersion = "TLS1.1" case 0x03: tlsVersion = "TLS1.2" } } if tlsType != "" && tlsVersion != "" { return tlsType, tlsVersion, true } else { return "", "", false } } func analysisHttp(reader *bufio.Reader) (string, error) { slog.Debug("analysis http") // reade top top, err := httpReadLine(reader) if err != nil { return "", errors.Wrap(err, "analysis http read top error") } // read header host := strings.Split(top, " ")[1] for { line, err := httpReadLine(reader) if err != nil { return "", err } if line == "" { break } if strings.HasPrefix(line, "Host: ") { host = strings.TrimPrefix(line, "Host: ") } } return host, nil } func httpReadLine(reader *bufio.Reader) (line string, err error) { var lineStr strings.Builder for { line, prefix, err := reader.ReadLine() if err != nil { return "", errors.Wrap(err, "analysis http read line error") } lineStr.Write(line) if !prefix { break } } return lineStr.String(), nil } func analysisTls(reader *bufio.Reader) (string, error) { slog.Debug("analysis https") // tls record _, err := utils.ReadBuffer(reader, 5) if err != nil { return "", errors.Wrap(err, "analysis https read head error") } // tls type hsType, err := reader.ReadByte() if err != nil { return "", errors.Wrap(err, "analysis https read hsType error") } switch hsType { case 0x01: // client hello // length _, err = utils.ReadBuffer(reader, 3) if err != nil { return "", errors.Wrap(err, "analysis https read tls length error") } // version _, err = utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read version error") } // random _, err = utils.ReadBuffer(reader, 32) if err != nil { return "", errors.Wrap(err, "analysis https read random error") } // session id length sessionIdLen, err := reader.ReadByte() if err != nil { return "", errors.Wrap(err, "analysis https read sessionIdLen error") } // session id _, err = utils.ReadBuffer(reader, int(sessionIdLen)) if err != nil { return "", errors.Wrap(err, "analysis https read sessionId error") } // cipher suites length cLenBuf, err := utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read cLen error") } cLen := binary.BigEndian.Uint16(cLenBuf) // cipher suites _, err = utils.ReadBuffer(reader, int(cLen)) if err != nil { return "", errors.Wrap(err, "analysis https read c error") } // compression methods length cmLen, err := reader.ReadByte() if err != nil { return "", errors.Wrap(err, "analysis https read cmLen error") } // compression methods _, err = utils.ReadBuffer(reader, int(cmLen)) if err != nil { return "", errors.Wrap(err, "analysis https read cm error") } // extensions length eLenBuf, err := utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read eLen error") } eLen := binary.BigEndian.Uint16(eLenBuf) // extensions host := "" for i := 0; i < int(eLen); { // extension type eTypeBuf, err := utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read extension type error") } eType := binary.BigEndian.Uint16(eTypeBuf) // extension length eLenBuf, err := utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read extension length error") } eLen := binary.BigEndian.Uint16(eLenBuf) // server name if eType == 0x00 { // server name list length _, err = utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read server name list length error") } // server name type _, err = reader.ReadByte() if err != nil { return "", errors.Wrap(err, "analysis https read server name type error") } // server name length sLenBuf, err := utils.ReadBuffer(reader, 2) if err != nil { return "", errors.Wrap(err, "analysis https read server name length error") } sLen := binary.BigEndian.Uint16(sLenBuf) // server name bytes, err := utils.ReadBuffer(reader, int(sLen)) if err != nil { return "", errors.Wrap(err, "analysis https read server name error") } host = string(bytes) return host, nil } else { // other extension _, err = utils.ReadBuffer(reader, int(eLen)) if err != nil { return "", errors.Wrap(err, "analysis https read extension error") } } i += 4 + int(eLen) } default: return "", nil } return "", errors.New("analysis https error") } func discord(reader *bufio.Reader) { _, err := io.Copy(io.Discard, reader) if err != nil { slog.Error("analysis discord err", "err", err) } }