mirror of
https://github.com/kevwan/tproxy.git
synced 2026-05-23 07:40:35 +00:00
9ec58b02f9
- Add protocol field to http2Interop struct to track actual protocol - Use stored protocol name instead of hardcoded 'grpc' in output - Now http2 mode shows 'http2:' prefix and grpc mode shows 'grpc:' prefix - Move grpcHeaderLen constant to grpc.go to avoid duplication
236 lines
6.3 KiB
Go
236 lines
6.3 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/kevwan/tproxy/display"
|
|
"golang.org/x/net/http2"
|
|
"golang.org/x/net/http2/hpack"
|
|
)
|
|
|
|
const (
|
|
http2HeaderLen = 9
|
|
http2Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
|
http2SettingsPayloadLen = 6
|
|
)
|
|
|
|
type (
|
|
dataExplainer interface {
|
|
explain(b []byte) string
|
|
}
|
|
|
|
http2Interop struct {
|
|
explainer dataExplainer
|
|
protocol string
|
|
}
|
|
)
|
|
|
|
func (i *http2Interop) Dump(r io.Reader, source string, id int, quiet bool) {
|
|
i.readPreface(r, source, id)
|
|
|
|
data := make([]byte, bufferSize)
|
|
for {
|
|
n, err := r.Read(data)
|
|
if n > 0 && !quiet {
|
|
var buf strings.Builder
|
|
buf.WriteString(color.HiGreenString("from %s [%d]\n", source, id))
|
|
|
|
var index int
|
|
for index < n {
|
|
frameInfo, moreInfo, offset := i.explain(data[index:n])
|
|
protocol := i.protocol
|
|
if len(protocol) == 0 {
|
|
protocol = http2Protocol
|
|
}
|
|
buf.WriteString(fmt.Sprintf("%s%s%s\n",
|
|
color.HiBlueString("%s:(", protocol),
|
|
color.HiYellowString(frameInfo),
|
|
color.HiBlueString(")")))
|
|
end := index + offset
|
|
if end > n {
|
|
end = n
|
|
}
|
|
buf.WriteString(fmt.Sprint(hex.Dump(data[index:end])))
|
|
if len(moreInfo) > 0 {
|
|
buf.WriteString(fmt.Sprintf("\n%s\n\n", strings.TrimSpace(moreInfo)))
|
|
}
|
|
index += offset
|
|
}
|
|
|
|
display.PrintfWithTime("%s\n\n", strings.TrimSpace(buf.String()))
|
|
}
|
|
if err != nil && err != io.EOF {
|
|
fmt.Printf("unable to read data %v", err)
|
|
break
|
|
}
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (i *http2Interop) explain(b []byte) (string, string, int) {
|
|
if len(b) < http2HeaderLen {
|
|
return "", "", len(b)
|
|
}
|
|
|
|
frame, err := http2.ReadFrameHeader(bytes.NewReader(b[:http2HeaderLen]))
|
|
if err != nil {
|
|
return "", "", len(b)
|
|
}
|
|
|
|
frameLen := http2HeaderLen + int(frame.Length)
|
|
maxOffset := frameLen
|
|
if maxOffset > len(b) {
|
|
maxOffset = len(b)
|
|
}
|
|
|
|
switch frame.Type {
|
|
case http2.FrameSettings:
|
|
switch frame.Flags {
|
|
case http2.FlagSettingsAck:
|
|
return "http2:settings:ack", "", frameLen
|
|
default:
|
|
return i.explainSettings(b[http2HeaderLen:maxOffset]), "", frameLen
|
|
}
|
|
case http2.FramePing:
|
|
id := hex.EncodeToString(b[http2HeaderLen:maxOffset])
|
|
switch frame.Flags {
|
|
case http2.FlagPingAck:
|
|
return fmt.Sprintf("http2:ping:ack %s", id), "", frameLen
|
|
default:
|
|
return fmt.Sprintf("http2:ping %s", id), "", frameLen
|
|
}
|
|
case http2.FrameWindowUpdate:
|
|
increment := binary.BigEndian.Uint32(b[http2HeaderLen : http2HeaderLen+4])
|
|
return fmt.Sprintf("http2:window_update window_size_increment:%d", increment), "", frameLen
|
|
case http2.FrameHeaders:
|
|
info, headers := i.explainHeaders(frame, b[http2HeaderLen:maxOffset])
|
|
var builder strings.Builder
|
|
for _, header := range headers {
|
|
builder.WriteString(fmt.Sprintf("%s: %s\n", header.Name, header.Value))
|
|
}
|
|
return info, builder.String(), frameLen
|
|
case http2.FrameData:
|
|
if frame.Flags == http2.FlagDataEndStream {
|
|
var info string
|
|
if i.explainer != nil {
|
|
info = i.explainer.explain(b[http2HeaderLen:maxOffset])
|
|
}
|
|
return fmt.Sprintf("http2:%s stream:%d len:%d end_stream",
|
|
strings.ToLower(frame.Type.String()), frame.StreamID, frame.Length), info, frameLen
|
|
} else {
|
|
// TODO: handle data frame without end_stream flag
|
|
return fmt.Sprintf("http2:%s stream:%d len:%d",
|
|
strings.ToLower(frame.Type.String()), frame.StreamID, frame.Length), "", frameLen
|
|
}
|
|
}
|
|
|
|
if frame.StreamID > 0 {
|
|
desc := fmt.Sprintf("http2:%s stream:%d len:%d",
|
|
strings.ToLower(frame.Type.String()), frame.StreamID, frame.Length)
|
|
return desc, "", frameLen
|
|
}
|
|
|
|
return "http2:" + strings.ToLower(frame.Type.String()), "", frameLen
|
|
}
|
|
|
|
func (i *http2Interop) explainHeaders(frame http2.FrameHeader, b []byte) (string, []hpack.HeaderField) {
|
|
var padded int
|
|
var weight int
|
|
if frame.Flags&http2.FlagHeadersPadded != 0 {
|
|
padded = int(b[0])
|
|
b = b[1 : len(b)-padded]
|
|
}
|
|
if frame.Flags&http2.FlagHeadersPriority != 0 {
|
|
b = b[4:]
|
|
weight = int(b[0])
|
|
b = b[1:]
|
|
}
|
|
|
|
var buf strings.Builder
|
|
buf.WriteString(fmt.Sprintf("http2:headers stream:%d", frame.StreamID))
|
|
|
|
switch {
|
|
case frame.Flags&http2.FlagHeadersEndStream != 0:
|
|
buf.WriteString(" end_stream")
|
|
case frame.Flags&http2.FlagHeadersEndHeaders != 0:
|
|
buf.WriteString(" end_headers")
|
|
case frame.Flags&http2.FlagHeadersPadded != 0:
|
|
buf.WriteString(" padded")
|
|
case frame.Flags&http2.FlagHeadersPriority != 0:
|
|
buf.WriteString(" priority")
|
|
}
|
|
|
|
if weight > 0 {
|
|
buf.WriteString(fmt.Sprintf(" weight:%d", weight))
|
|
}
|
|
|
|
if frame.Flags&http2.FlagHeadersEndStream != 0 || frame.Flags&http2.FlagHeadersEndHeaders != 0 {
|
|
headers, err := hpack.NewDecoder(0, nil).DecodeFull(b)
|
|
if err != nil {
|
|
return buf.String(), nil
|
|
}
|
|
|
|
return buf.String(), headers
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func (i *http2Interop) explainSettings(b []byte) string {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteString("http2:settings")
|
|
for i := 0; i < len(b)/http2SettingsPayloadLen; i++ {
|
|
start := i * http2SettingsPayloadLen
|
|
flag := binary.BigEndian.Uint16(b[start : start+2])
|
|
value := binary.BigEndian.Uint32(b[start+2 : start+http2SettingsPayloadLen])
|
|
|
|
switch http2.SettingID(flag) {
|
|
case http2.SettingHeaderTableSize:
|
|
builder.WriteString(fmt.Sprintf(" header_table_size:%d", value))
|
|
case http2.SettingEnablePush:
|
|
builder.WriteString(fmt.Sprintf(" enable_push:%d", value))
|
|
case http2.SettingMaxConcurrentStreams:
|
|
builder.WriteString(fmt.Sprintf(" max_concurrent_streams:%d", value))
|
|
case http2.SettingInitialWindowSize:
|
|
builder.WriteString(fmt.Sprintf(" initial_window_size:%d", value))
|
|
case http2.SettingMaxFrameSize:
|
|
builder.WriteString(fmt.Sprintf(" max_frame_size:%d", value))
|
|
case http2.SettingMaxHeaderListSize:
|
|
builder.WriteString(fmt.Sprintf(" max_header_list_size:%d", value))
|
|
}
|
|
}
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func (i *http2Interop) readPreface(r io.Reader, source string, id int) {
|
|
if source != ClientSide {
|
|
return
|
|
}
|
|
|
|
preface := make([]byte, len(http2Preface))
|
|
n, err := r.Read(preface)
|
|
if err != nil || n < len(http2Preface) {
|
|
return
|
|
}
|
|
|
|
fmt.Println()
|
|
var builder strings.Builder
|
|
builder.WriteString(color.HiGreenString("from %s [%d]\n", source, id))
|
|
builder.WriteString(fmt.Sprintf("%s%s%s\n",
|
|
color.HiBlueString("%s:(", grpcProtocol),
|
|
color.YellowString("http2:preface"),
|
|
color.HiBlueString(")")))
|
|
builder.WriteString(fmt.Sprint(hex.Dump(preface)))
|
|
display.PrintlnWithTime(builder.String())
|
|
}
|