diff --git a/.travis.yml b/.travis.yml index f3a64a1..77472cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: go -go: 1.5.1 -script: sudo -E bash -c "source /etc/profile && eval '$(gimme 1.5.1)' && export GOPATH=$HOME/gopath:$GOPATH && go get && GORACE='halt_on_error=1' go test ./... -v -timeout 60s -race" \ No newline at end of file +go: 1.6 +script: sudo -E bash -c "source /etc/profile && eval '$(gimme 1.6)' && export GOPATH=$HOME/gopath:$GOPATH && go get && GORACE='halt_on_error=1' go test ./... -v -timeout 60s -race" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 31ce205..d915390 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,5 @@ ADD . /go/src/github.com/buger/gor/ RUN javac -cp /tmp/commons-io-2.4/commons-io-2.4.jar ./examples/middleware/echo.java -RUN apt-get install graphviz -y - RUN go get -u github.com/golang/lint/golint RUN go get \ No newline at end of file diff --git a/Makefile b/Makefile index 0ad9d3f..0ab4a70 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,59 @@ SOURCE = emitter.go gor.go gor_stat.go input_dummy.go input_file.go input_raw.go input_tcp.go limiter.go output_dummy.go output_file.go input_http.go output_http.go output_tcp.go plugins.go settings.go test_input.go elasticsearch.go http_modifier.go http_modifier_settings.go http_client.go middleware.go protocol.go - -SOURCE_PATH = /gopath/src/github.com/buger/gor/ +SOURCE_PATH = /go/src/github.com/buger/gor/ +RUN = docker run -v `pwd`:$(SOURCE_PATH) -p 0.0.0.0:8000:8000 -t -i gor release: release-x86 release-x64 release-x64: - docker run -v `pwd`:$(SOURCE_PATH) -t --env GOOS=linux --env GOARCH=amd64 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION $(VERSION)"&& tar -czf gor_$(VERSION)_x64.tar.gz gor && rm gor + docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go build -ldflags "-X main.VERSION=$(VERSION)"&& tar -czf gor_$(VERSION)_x64.tar.gz gor && rm gor release-x86: - docker run -v `pwd`:$(SOURCE_PATH) -t --env GOOS=linux --env GOARCH=386 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION $(VERSION)" && tar -czf gor_$(VERSION)_x86.tar.gz gor && rm gor + docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GOOS=linux --env GOARCH=386 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION=$(VERSION)" && tar -czf gor_$(VERSION)_x86.tar.gz gor && rm gor -dbuild: +build: docker build -t gor . profile: go build && ./gor --output-http="http://localhost:9000" --input-dummy 0 --input-raw :9000 --input-http :9000 --memprofile=./mem.out --cpuprofile=./cpu.out --stats --output-http-stats --output-http-timeout 100ms -dlint: - docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor golint $(PKG) +lint: + $(RUN) golint $(PKG) -drace: - docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor go test ./... $(ARGS) -v -race -timeout 15s +race: + $(RUN) go test ./... $(ARGS) -v -race -timeout 15s -dtest: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go test ./... -timeout 60s $(ARGS) -v +test: + $(RUN) go test ./... -timeout 10s $(ARGS) -v -dcover: - docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor go test $(ARGS) -race -v -timeout 15s -coverprofile=coverage.out +testone: + $(RUN) go test ./... -timeout 4s -run $(TEST) $(ARGS) -v + +cover: + $(RUN) go test $(ARGS) -race -v -timeout 15s -coverprofile=coverage.out go tool cover -html=coverage.out -dfmt: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go fmt ./... +fmt: + $(RUN) go fmt ./... -dvet: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go vet +vet: + $(RUN) go vet -dbench: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go test -v -run NOT_EXISTING -bench HTTP +bench: + $(RUN) go test -v -run NOT_EXISTING -bench HTTP # Used mainly for debugging, because docker container do not have access to parent machine ports -drun: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw :9000 --input-http :9000 --verbose --debug --middleware "./examples/middleware/echo.sh" +run: + $(RUN) go run $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw :9000 --input-http :9000 --verbose --debug --middleware "./examples/middleware/echo.sh" -drun-2: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-file ./fixtures/requests.gor --output-dummy=0 +run-2: + $(RUN) go run $(SOURCE) --input-file ./fixtures/requests.gor --output-dummy=0 -drecord: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-dummy=0 --output-file=requests.gor --verbose --debug +record: + $(RUN) go run $(SOURCE) --input-dummy=0 --output-file=requests.gor --verbose --debug -dreplay: - docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-file=requests.bin --output-tcp=:9000 --verbose -h +replay: + $(RUN) go run $(SOURCE) --input-file=requests.bin --output-tcp=:9000 --verbose -h -dbash: - docker run -v `pwd`:$(SOURCE_PATH) -p 0.0.0.0:8000:8000 -t -i gor /bin/bash +bash: + $(RUN) /bin/bash diff --git a/input_raw.go b/input_raw.go index fcfd883..3b36a20 100644 --- a/input_raw.go +++ b/input_raw.go @@ -59,7 +59,7 @@ func (i *RAWInput) listen(address string) { log.Fatal("input-raw: error while parsing address", err) } - i.listener = raw.NewListener(host, port, i.expire, true) + i.listener = raw.NewListener(host, port, i.expire) for { select { diff --git a/input_raw_test.go b/input_raw_test.go index 0d75443..ec267c2 100644 --- a/input_raw_test.go +++ b/input_raw_test.go @@ -68,13 +68,12 @@ func TestInputRAW100Expect(t *testing.T) { wg := new(sync.WaitGroup) quit := make(chan int) - fileContent, _ := ioutil.ReadFile("README.md") + fileContent, _ := ioutil.ReadFile("LICENSE.txt") // Origing and Replay server initialization origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ioutil.ReadAll(r.Body) - wg.Done() })) defer origin.Close() @@ -119,7 +118,7 @@ func TestInputRAW100Expect(t *testing.T) { // Origin + Response/Request Test Output + Request Http Output wg.Add(4) - curl := exec.Command("curl", "http://"+originAddr, "--data-binary", "@README.md") + curl := exec.Command("curl", "http://"+originAddr, "--data-binary", "@LICENSE.txt") err := curl.Run() if err != nil { log.Fatal(err) diff --git a/middleware_test.go b/middleware_test.go index 6589794..9d9e794 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -148,6 +148,7 @@ func TestEchoMiddleware(t *testing.T) { } func TestTokenMiddleware(t *testing.T) { + Settings.verbose = true var resp, token []byte wg := new(sync.WaitGroup) @@ -194,15 +195,15 @@ func TestTokenMiddleware(t *testing.T) { // Should receive 2 requests from original + 2 from replayed wg.Add(4) - client := NewHTTPClient(from.URL, &HTTPClientConfig{Debug: false}) + client := NewHTTPClient(from.URL, &HTTPClientConfig{Debug: true}) // Sending traffic to original service resp, _ = client.Get("/token") token = proto.Body(resp) // When delay is too smal, middleware does not always rewrite requests in time - // Hopefuly client will have delay more then 10ms :) - time.Sleep(10 * time.Millisecond) + // Hopefuly client will have delay more then 100ms :) + time.Sleep(100 * time.Millisecond) resp, _ = client.Get("/secure?token=" + string(token)) if !bytes.Equal(proto.Status(resp), []byte("202")) { diff --git a/raw_socket_listener/listener.go b/raw_socket_listener/listener.go index 7af2ed0..67af01b 100644 --- a/raw_socket_listener/listener.go +++ b/raw_socket_listener/listener.go @@ -26,6 +26,7 @@ import ( // Listener handle traffic capture type Listener struct { // buffer of TCPMessages waiting to be send + // ID -> TCPMessage messages map[string]*TCPMessage // Expect: 100-continue request is send in 2 tcp messages @@ -34,8 +35,12 @@ type Listener struct { // To get ACK of second message we need to compute its Seq and wait for them message seqWithData map[uint32]uint32 + // Ack -> Req respAliases map[uint32]*request + // Ack -> ID + respWithoutReq map[uint32]string + // Messages ready to be send to client packetsChan chan *TCPPacket @@ -47,8 +52,6 @@ type Listener struct { messageExpire time.Duration - captureResponse bool - conn net.PacketConn quit chan bool } @@ -59,8 +62,8 @@ type request struct { } // NewListener creates and initializes new Listener object -func NewListener(addr string, port string, expire time.Duration, captureResponse bool) (l *Listener) { - l = &Listener{captureResponse: captureResponse} +func NewListener(addr string, port string, expire time.Duration) (l *Listener) { + l = &Listener{} l.packetsChan = make(chan *TCPPacket, 10000) l.messagesChan = make(chan *TCPMessage, 10000) @@ -70,6 +73,7 @@ func NewListener(addr string, port string, expire time.Duration, captureResponse l.ackAliases = make(map[uint32]uint32) l.seqWithData = make(map[uint32]uint32) l.respAliases = make(map[uint32]*request) + l.respWithoutReq = make(map[uint32]string) l.addr = addr _port, _ := strconv.Atoi(port) @@ -82,7 +86,11 @@ func NewListener(addr string, port string, expire time.Duration, captureResponse l.messageExpire = expire go l.listen() - go l.readRAWSocket() + + // Special case for testing + if l.port != 0 { + go l.readRAWSocket() + } return } @@ -93,10 +101,17 @@ func (t *Listener) listen() { for { select { case <-t.quit: - t.conn.Close() + if t.conn != nil { + t.conn.Close() + } return // We need to use channels to process each packet to avoid data races case packet := <-t.packetsChan: + maxLen := len(packet.Data) + if maxLen > 500 { + maxLen = 500 + } + t.processTCPPacket(packet) case <- gcTicker: @@ -115,14 +130,30 @@ func (t *Listener) dispatchMessage(message *TCPMessage) { delete(t.ackAliases, message.Ack) delete(t.messages, message.ID) - if !message.IsIncoming { + if message.IsIncoming { + // If there were response before request + if respID, ok := t.respWithoutReq[message.ResponseAck]; ok { + if resp, rok := t.messages[respID]; rok { + if resp.RequestAck == 0 { + resp.RequestAck = message.Ack + resp.RequestStart = message.Start + + if resp.IsFinished() { + defer t.dispatchMessage(resp) + } + } + } + } + } else { delete(t.respAliases, message.Ack) + delete(t.respWithoutReq, message.Ack) // Do not track responses which have no associated requests if message.RequestAck == 0 { return } } + t.messagesChan <- message } @@ -146,7 +177,6 @@ func (t *Listener) readRAWSocket() { if strings.HasSuffix(err.Error(), "closed network connection") { return } else { - log.Println("Raw listener error:", err) continue } } @@ -171,7 +201,7 @@ func (t *Listener) isValidPacket(buf []byte) bool { srcPort := binary.BigEndian.Uint16(buf[0:2]) // Because RAW_SOCKET can't be bound to port, we have to control it by ourself - if destPort == t.port || (t.captureResponse && srcPort == t.port) { + if destPort == t.port || srcPort == t.port { // Get the 'data offset' (size of the TCP header in 32-bit words) dataOffset := (buf[12] & 0xF0) >> 4 @@ -206,6 +236,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) { if parentAck, ok := t.seqWithData[packet.Seq]; ok { t.ackAliases[packet.Ack] = parentAck + packet.Ack = parentAck delete(t.seqWithData, packet.Seq) } @@ -215,7 +246,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) { var responseRequest *request - if t.captureResponse && !isIncoming { + if !isIncoming { responseRequest, _ = t.respAliases[packet.Ack] } @@ -224,12 +255,16 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) { message, ok := t.messages[mID] if !ok { - message = NewTCPMessage(mID, packet.Ack, isIncoming) + message = NewTCPMessage(mID, packet.Seq, packet.Ack, isIncoming) t.messages[mID] = message - if !isIncoming && responseRequest != nil { - message.RequestStart = responseRequest.start - message.RequestAck = responseRequest.ack + if !isIncoming { + if responseRequest != nil { + message.RequestStart = responseRequest.start + message.RequestAck = responseRequest.ack + } else { + t.respWithoutReq[packet.Ack] = mID + } } } @@ -237,21 +272,36 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) { if len(packet.Data) > 4 && bytes.Equal(packet.Data[0:4], bPOST) { // reading last 20 bytes (not counting CRLF): last header value (if no body presented) if bytes.Equal(packet.Data[len(packet.Data)-24:len(packet.Data)-4], bExpect100ContinueCheck) { - t.seqWithData[packet.Seq+uint32(len(packet.Data))] = packet.Ack + seq := packet.Seq+uint32(len(packet.Data)) + t.seqWithData[seq] = packet.Ack + + // In case if sequence packet came first + for _id, m := range t.messages { + if m.Seq == seq { + t.ackAliases[m.Ack] = packet.Ack + + for _, pkt := range m.packets { + message.AddPacket(pkt) + } + + delete(t.messages, _id) + } + } // Removing `Expect: 100-continue` header packet.Data = append(packet.Data[:len(packet.Data)-24], packet.Data[len(packet.Data)-2:]...) } } - if t.captureResponse && isIncoming { + if isIncoming { // If message have multiple packets, delete previous alias if len(message.packets) > 0 { delete(t.respAliases, message.ResponseAck) } - responseAck := packet.Seq + uint32(len(packet.Data)) + responseAck := packet.Seq + uint32(message.BodySize()) + uint32(len(packet.Data)) t.respAliases[responseAck] = &request{message.Start, message.Ack} + message.ResponseAck = responseAck } @@ -259,7 +309,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) { message.AddPacket(packet) // If message contains only single packet immediately dispatch it - if !message.IsMultipart() { + if message.IsFinished() { t.dispatchMessage(message) } } @@ -271,6 +321,8 @@ func (t *Listener) Receive() *TCPMessage { func (t *Listener) Close() { close(t.quit) - t.conn.Close() + if t.conn != nil { + t.conn.Close() + } return } diff --git a/raw_socket_listener/listener_test.go b/raw_socket_listener/listener_test.go new file mode 100644 index 0000000..44e11c0 --- /dev/null +++ b/raw_socket_listener/listener_test.go @@ -0,0 +1,143 @@ +package rawSocket + +import ( + "testing" + "time" + "bytes" + _ "log" +) + +func TestRawListenerInput(t *testing.T) { + var req, resp *TCPMessage + + listener := NewListener("", "0", 10 * time.Millisecond) + defer listener.Close() + + reqPacket := buildPacket(true, 1, 1, []byte("GET / HTTP/1.1")) + + listener.packetsChan <- reqPacket + + respAck := reqPacket.Seq + uint32(len(reqPacket.Data)) + respPacket := buildPacket(false, respAck, reqPacket.Seq + 1, []byte("HTTP/1.1 200 OK")) + listener.packetsChan <- respPacket + + + select { + case req = <- listener.messagesChan: + case <- time.After(time.Millisecond): + t.Error("Should return respose immediately") + return + } + + if !req.IsIncoming { + t.Error("Should be request") + } + + select { + case resp = <- listener.messagesChan: + case <- time.After(time.Millisecond): + t.Error("Should return response immediately") + return + } + + if resp.IsIncoming { + t.Error("Should be response") + } +} + +func TestRawListenerResponse(t *testing.T) { + var req, resp *TCPMessage + + listener := NewListener("", "0", 10 * time.Millisecond) + defer listener.Close() + + reqPacket := buildPacket(true, 1, 1, []byte("GET / HTTP/1.1")) + respPacket := buildPacket(false, 1 + uint32(len(reqPacket.Data)), 2, []byte("HTTP/1.1 200 OK")) + + // If response packet comes before request + listener.packetsChan <- respPacket + listener.packetsChan <- reqPacket + + select { + case req = <- listener.messagesChan: + case <- time.After(time.Millisecond): + t.Error("Should return respose immediately") + return + } + + if !req.IsIncoming { + t.Error("Should be request") + } + + select { + case resp = <- listener.messagesChan: + case <- time.After(time.Millisecond): + t.Error("Should return response immediately") + return + } + + if resp.IsIncoming { + t.Error("Should be response") + } + + if !bytes.Equal(resp.UUID(), req.UUID()) { + t.Error("Resp and Req UUID should be equal") + } +} + +func TestRawListener100Continue(t *testing.T) { + var req, resp *TCPMessage + + listener := NewListener("", "0", 10 * time.Millisecond) + defer listener.Close() + + reqPacket1 := buildPacket(true, 1, 1, []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\nExpect: 100-continue\r\n\r\n")) + + // Packet with data have different Seq + reqPacket2 := buildPacket(true, 2, reqPacket1.Seq + uint32(len(reqPacket1.Data)), []byte("a")) + reqPacket3 := buildPacket(true, 2, reqPacket2.Seq + 1, []byte("b")) + + respPacket1 := buildPacket(false, 10, 3, []byte("HTTP/1.1 100 Continue\r\n")) + + // panic(int(uint32(len(reqPacket1.Data)) + uint32(len(reqPacket2.Data)) + uint32(len(reqPacket3.Data)))) + respPacket2 := buildPacket(false, reqPacket3.Seq + 2 /* len of data */, 2, []byte("HTTP/1.1 200 OK\r\n")) + + listener.processTCPPacket(reqPacket1) + listener.processTCPPacket(reqPacket2) + listener.processTCPPacket(reqPacket3) + + listener.processTCPPacket(respPacket1) + listener.processTCPPacket(respPacket2) + + select { + case req = <- listener.messagesChan: + break + case <- time.After(11 * time.Millisecond): + t.Error("Should return response after expire time") + return + } + + if !bytes.Equal(req.Bytes(), []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\nab")) { + t.Error("Should receive full message", string(req.Bytes())) + } + + if !req.IsIncoming { + t.Error("Should be request") + } + + select { + case resp = <- listener.messagesChan: + break + case <- time.After(100 * time.Millisecond): + t.Error("Should return response after expire time") + return + } + + if resp.IsIncoming { + t.Error("Should be response") + } + + if !bytes.Equal(resp.UUID(), req.UUID()) { + t.Error("Resp and Req UUID should be equal") + } +} diff --git a/raw_socket_listener/tcp_message.go b/raw_socket_listener/tcp_message.go index a4dbce1..aa8c6a8 100644 --- a/raw_socket_listener/tcp_message.go +++ b/raw_socket_listener/tcp_message.go @@ -18,6 +18,7 @@ import ( // Message is received if we didn't receive any packets for 2000ms type TCPMessage struct { ID string // Message ID + Seq uint32 Ack uint32 ResponseAck uint32 RequestStart time.Time @@ -32,8 +33,8 @@ type TCPMessage struct { } // NewTCPMessage pointer created from a Acknowledgment number and a channel of messages readuy to be deleted -func NewTCPMessage(ID string, Ack uint32, IsIncoming bool) (msg *TCPMessage) { - msg = &TCPMessage{ID: ID, Ack: Ack, IsIncoming: IsIncoming} +func NewTCPMessage(ID string, Seq, Ack uint32, IsIncoming bool) (msg *TCPMessage) { + msg = &TCPMessage{ID: ID, Seq: Seq, Ack: Ack, IsIncoming: IsIncoming} msg.Start = time.Now() return @@ -48,8 +49,12 @@ func (t *TCPMessage) Bytes() (output []byte) { return output } -// Size returns total size of message -func (t *TCPMessage) Size() (size int) { +// Size returns total body size +func (t *TCPMessage) BodySize() (size int) { + if len(t.packets) == 0 { + return 0 + } + size += len(proto.Body(t.packets[0].Data)) for _, p := range t.packets[1:] { @@ -59,6 +64,19 @@ func (t *TCPMessage) Size() (size int) { return } +// Size returns total size of message +func (t *TCPMessage) Size() (size int) { + if len(t.packets) == 0 { + return 0 + } + + for _, p := range t.packets { + size += len(p.Data) + } + + return +} + // AddPacket to the message and ensure packet uniqueness // TCP allows that packet can be re-send multiple times func (t *TCPMessage) AddPacket(packet *TCPPacket) { @@ -86,18 +104,14 @@ func (t *TCPMessage) AddPacket(packet *TCPPacket) { } // isMultipart returns true if message contains from multiple tcp packets -func (t *TCPMessage) IsMultipart() bool { - if len(t.packets) > 1 { - return true - } - +func (t *TCPMessage) IsFinished() bool { payload := t.packets[0].Data m := payload[:4] if t.IsIncoming { // If one GET, OPTIONS, or HEAD request if bytes.Equal(m, []byte("GET ")) || bytes.Equal(m, []byte("OPTI")) || bytes.Equal(m, []byte("HEAD")) { - return false + return true } else { // Sometimes header comes after the body :( if bytes.Equal(m, []byte("POST")) || bytes.Equal(m, []byte("PUT ")) || bytes.Equal(m, []byte("PATC")) { @@ -105,28 +119,38 @@ func (t *TCPMessage) IsMultipart() bool { l, _ := strconv.Atoi(string(length)) // If content-length equal current body length - if l > 0 && l == t.Size() { - return false + if l > 0 && l == t.BodySize() { + return true } } } } } else { + // Request not found + // Can be because response came first or request request was just missing + if t.RequestAck == 0 { + return false + } + if length := proto.Header(payload, []byte("Content-Length")); len(length) > 0 { if length[0] == '0' { - return false + return true } l, _ := strconv.Atoi(string(length)) // If content-length equal current body length - if l > 0 && l == t.Size() { - return false + if l > 0 && l == t.BodySize() { + return true + } + } else { + if enc := proto.Header(payload, []byte("Transfer-Encoding")); len(enc) == 0 { + return true } } } - return true + return false } func (t *TCPMessage) UUID() []byte { @@ -146,3 +170,4 @@ func (t *TCPMessage) UUID() []byte { return uuid } + diff --git a/raw_socket_listener/tcp_message_test.go b/raw_socket_listener/tcp_message_test.go new file mode 100644 index 0000000..fb96d7e --- /dev/null +++ b/raw_socket_listener/tcp_message_test.go @@ -0,0 +1,155 @@ +package rawSocket + +import ( + "testing" + "net" + "strconv" + "bytes" + _ "log" +) + +func buildPacket(isIncoming bool, Ack, Seq uint32, Data []byte) (packet *TCPPacket) { + packet = &TCPPacket{ + Addr: &net.IPAddr{net.IP{}, ""}, + Ack: Ack, + Seq: Seq, + Data: Data, + } + + // For tests `listening` port is 0 + if isIncoming { + packet.SrcPort = 1 + } else { + packet.DestPort = 1 + } + + return packet +} + +func buildMessage(p *TCPPacket) *TCPMessage { + id := p.Addr.String() + strconv.Itoa(int(p.DestPort)) + strconv.Itoa(int(p.Ack)) + + isIncoming := false + if p.SrcPort == 1 { + isIncoming = true + } + + m := NewTCPMessage(id, p.Seq, p.Ack, isIncoming) + m.AddPacket(p) + + return m +} + +func TestTCPMessagePacketsOrder(t *testing.T) { + msg := buildMessage(buildPacket(true, 1, 1, []byte("a"))) + msg.AddPacket(buildPacket(true, 1, 2, []byte("b"))) + + if !bytes.Equal(msg.Bytes(), []byte("ab")) { + t.Error("Should contatenate packets in right order") + } + + // When first packet have wrong order (Seq) + msg = buildMessage(buildPacket(true, 1, 2, []byte("b"))) + msg.AddPacket(buildPacket(true, 1, 1, []byte("a"))) + + if !bytes.Equal(msg.Bytes(), []byte("ab")) { + t.Error("Should contatenate packets in right order") + } + + // Should ignore packets with same sequence + msg = buildMessage(buildPacket(true, 1, 1, []byte("a"))) + msg.AddPacket(buildPacket(true, 1, 1, []byte("a"))) + + if !bytes.Equal(msg.Bytes(), []byte("a")) { + t.Error("Should ignore packet with same Seq") + } +} + +func TestTCPMessageSize(t *testing.T) { + msg := buildMessage(buildPacket(true, 1, 1, []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\na"))) + msg.AddPacket(buildPacket(true, 1, 2, []byte("b"))) + + if msg.BodySize() != 2 { + t.Error("Should count only body", msg.BodySize()) + } + + if msg.Size() != 40 { + t.Error("Should count all sizes", msg.Size()) + } +} + + +func TestTCPMessageIsFinished(t *testing.T) { + methodsWithoutBodies := []string{"GET","OPTIONS","HEAD"} + + for _, m := range methodsWithoutBodies { + msg := buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1"))) + + if !msg.IsFinished() { + t.Error(m, " request should be finished") + } + } + + methodsWithBodies := []string{"POST","PUT","PATCH"} + + for _, m := range methodsWithBodies { + msg := buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1\r\nContent-Length: 1\r\n\r\na"))) + + if !msg.IsFinished() { + t.Error(m, " should be finished as body length == content length") + } + + msg = buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1\r\nContent-Length: 2\r\n\r\na"))) + + if msg.IsFinished() { + t.Error(m, " should not be finished as body length != content length") + } + } + + msg := buildMessage(buildPacket(true, 1, 1, []byte("UNKNOWN / HTTP/1.1\r\n\r\n"))) + if msg.IsFinished() { + t.Error("non http or wrong methods considered as not finished") + } + + // Responses + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\n\r\n"))) + msg.RequestAck = 1 + if !msg.IsFinished() { + t.Error("Should mark simple response as finished") + } + + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\n\r\n"))) + msg.RequestAck = 0 + if msg.IsFinished() { + t.Error("Should not mark responses without associated requests") + } + + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"))) + msg.RequestAck = 1 + + if msg.IsFinished() { + t.Error("Should mark chunked response as non finished") + } + + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"))) + msg.RequestAck = 1 + + if !msg.IsFinished() { + t.Error("Should mark Content-Length: 0 respones as finished") + } + + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\na"))) + msg.RequestAck = 1 + + if !msg.IsFinished() { + t.Error("Should mark valid Content-Length respones as finished") + } + + + msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\na"))) + msg.RequestAck = 1 + + if msg.IsFinished() { + t.Error("Should not mark not valid Content-Length respones as finished") + } +} diff --git a/raw_socket_listener/tcp_packet.go b/raw_socket_listener/tcp_packet.go index 2ed2855..ca3925a 100644 --- a/raw_socket_listener/tcp_packet.go +++ b/raw_socket_listener/tcp_packet.go @@ -70,8 +70,8 @@ func (t *TCPPacket) ParseBasic() { // String output for a TCP Packet func (t *TCPPacket) String() string { maxLen := len(t.Data) - if maxLen > 500 { - maxLen = 500 + if maxLen > 200 { + maxLen = 200 } return strings.Join([]string{ @@ -99,9 +99,3 @@ func (t *TCPPacket) String() string { "Data:" + string(t.Data[:maxLen]), }, "\n") } - -type sortBySeq []*TCPPacket - -func (a sortBySeq) Len() int { return len(a) } -func (a sortBySeq) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a sortBySeq) Less(i, j int) bool { return a[i].Seq < a[j].Seq }