diff --git a/README.md b/README.md index c2d618c..5fc145f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ httpdump can read from pcap file, or capture data from network interfaces: Filter by port, if either source or target port is matched, the packet will be processed. -pretty Try to format and prettify json content + -status string + Filter by response status code ``` ## Samples diff --git a/config_struct.go b/config_struct.go new file mode 100644 index 0000000..6db3ba8 --- /dev/null +++ b/config_struct.go @@ -0,0 +1,111 @@ +package main + +import ( + "errors" + "strconv" + "strings" +) + +// Config is user config for http traffics +type Config struct { + level string + filterIP string + filterPort uint16 + host string + uri string + status *IntSet + force bool + pretty bool + output string +} + +// parse int set +func ParseIntSet(str string) (*IntSet, error) { + if str == "" { + return nil, errors.New("empty str") + } + var intSet IntSet + for _, item := range strings.Split(str, ":") { + var numbers = strings.Split(item, "-") + if len(numbers) > 2 { + return nil, errors.New("illegal range str: " + item) + } + if len(numbers) == 1 { + start, err := strconv.Atoi(numbers[0]) + if err != nil { + return nil, err + } + intSet.ranges = append(intSet.ranges, NewIntRange(start, start)) + } else if len(numbers) == 2 { + start, err := strconv.Atoi(numbers[0]) + if err != nil { + return nil, err + } + end, err := strconv.Atoi(numbers[1]) + if err != nil { + return nil, err + } + intSet.ranges = append(intSet.ranges, NewIntRange(start, end)) + } + } + return &intSet, nil +} + +// A set of int values +type IntSet struct { + ranges []IntRange +} + +// Create new IntSet +func NewIntSet(ranges ...IntRange) *IntSet { + return &IntSet{ + ranges: ranges, + } +} + +// implement Stringer +func (s *IntSet) String() string { + var sb strings.Builder + for index, r := range s.ranges { + if index > 0 { + sb.WriteRune(':') + } + if r.Start == r.End { + sb.WriteString(strconv.Itoa(r.Start)) + } else { + sb.WriteString(strconv.Itoa(r.Start)) + sb.WriteRune('-') + sb.WriteString(strconv.Itoa(r.End)) + } + } + return sb.String() +} + +// If this set contains int value +func (s *IntSet) Contains(value int) bool { + for _, r := range s.ranges { + if r.Contains(value) { + return true + } + } + return false +} + +// Range of int value +type IntRange struct { + Start int // inclusive + End int // inclusive +} + +// Create new int range +func NewIntRange(start int, end int) IntRange { + return IntRange{ + Start: start, + End: end, + } +} + +// If this range contains the value +func (r *IntRange) Contains(value int) bool { + return value >= r.Start && value <= r.End +} diff --git a/config_struct_test.go b/config_struct_test.go new file mode 100644 index 0000000..8973228 --- /dev/null +++ b/config_struct_test.go @@ -0,0 +1,18 @@ +package main + +import "testing" +import "github.com/stretchr/testify/assert" + +func TestIntSet_String(t *testing.T) { + assert.Equal(t, "1", NewIntSet(NewIntRange(1, 1)).String()) + assert.Equal(t, "1:1-2", NewIntSet(NewIntRange(1, 1), NewIntRange(1, 2)).String()) +} + +func TestParseIntSet(t *testing.T) { + intRange, err := ParseIntSet("1:1-2") + assert.NoError(t, err) + assert.Equal(t, 1, intRange.ranges[0].Start) + assert.Equal(t, 1, intRange.ranges[0].End) + assert.Equal(t, 1, intRange.ranges[1].Start) + assert.Equal(t, 2, intRange.ranges[1].End) +} diff --git a/http_traffic_handler.go b/http_traffic_handler.go index 3bc3b05..6a51c8d 100644 --- a/http_traffic_handler.go +++ b/http_traffic_handler.go @@ -17,7 +17,7 @@ import ( "github.com/google/gopacket/tcpassembly/tcpreader" ) -// ConnectionKey contains src and dst endpoint idendity a connection +// ConnectionKey contains src and dst endpoint identify a connection type ConnectionKey struct { src Endpoint dst Endpoint @@ -121,7 +121,7 @@ func (h *HTTPTrafficHandler) handle(connection *TCPConnection) { break } - if h.config.status != 0 && h.config.status != resp.StatusCode { + if h.config.status != nil && !h.config.status.Contains(resp.StatusCode) { filtered = true } diff --git a/main.go b/main.go index d1b54de..68cb433 100644 --- a/main.go +++ b/main.go @@ -26,19 +26,6 @@ func init() { var waitGroup sync.WaitGroup var printerWaitGroup sync.WaitGroup -// Config is user config for http traffics -type Config struct { - level string - filterIP string - filterPort uint16 - host string - uri string - status int - force bool - pretty bool - output string -} - func listenOneSource(handle *pcap.Handle) chan gopacket.Packet { packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) packets := packetSource.Packets() @@ -90,9 +77,7 @@ func openSingleDevice(device string, filterIP string, filterPort uint16) (localP } if err := setDeviceFilter(handle, filterIP, filterPort); err != nil { - if err != nil { - logger.Warn("set capture filter failed, ", err) - } + logger.Warn("set capture filter failed, ", err) } localPackets = listenOneSource(handle) return @@ -107,7 +92,7 @@ func main() { var filterPort = flagSet.Uint("port", 0, "Filter by port, if either source or target port is matched, the packet will be processed.") var host = flagSet.String("filter-host", "", "Filter by request host, using wildcard match(*, ?)") var uri = flagSet.String("filter-uri", "", "Filter by request url path, using wildcard match(*, ?)") - var status = flagSet.Int("status", 0, "Filter by response status code") + var status = flagSet.String("status", "", "Filter by response status code") var force = flagSet.Bool("force", false, "Force print unknown content-type http body even if it seems not to be text content") var pretty = flagSet.Bool("pretty", false, "Try to format and prettify json content") var output = flagSet.String("output", "", "Write result to file [output] instead of stdout") @@ -118,13 +103,22 @@ func main() { *filterPort = 0 } + var statusSet *IntSet + if *status != "" { + var err error + if statusSet, err = ParseIntSet(*status); err != nil { + fmt.Fprint(os.Stderr, "status range not valid ", *status) + return + } + } + var config = &Config{ level: *level, filterIP: *filterIP, filterPort: uint16(*filterPort), host: *host, uri: *uri, - status: *status, + status: statusSet, force: *force, pretty: *pretty, output: *output,