From 9908e8d4d9194173de76b49f090ddc8180e11a82 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Fri, 27 Sep 2013 23:04:13 +0200 Subject: [PATCH 1/9] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 890c58b..96fb0c4 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,7 @@ Yes. ~~Right now it supports only "GET" requests.~~ 4. Push to the branch (git push origin my-new-feature) 5. Create new Pull Request -## TODO +## Companies using Gor -Use buffering for request throttling instead of simple rate limiting. - -Better stats - -Optimize for load testing cases +* http://granify.com +* To add your company drop me a line to github.com/buger or leonsbox@gmail.com From de93abeab87185c0db65ec3839762cf04b01f904 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Sun, 29 Sep 2013 13:39:55 +0200 Subject: [PATCH 2/9] Added RawTCPListener test --- listener/listener_test.go | 56 +++++++++++++++++---- listener/raw_tcp_listener.go | 63 +++++++++++++---------- listener/raw_tcp_listener_test.go | 84 +++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 37 deletions(-) create mode 100644 listener/raw_tcp_listener_test.go diff --git a/listener/listener_test.go b/listener/listener_test.go index 319ed55..d9c2150 100644 --- a/listener/listener_test.go +++ b/listener/listener_test.go @@ -5,34 +5,36 @@ import ( "fmt" "net" "testing" + //"time" ) func getTCPMessage() (msg *TCPMessage) { - packet := &TCPPacket{Data: []byte("GET /pub/WWW/ HTTP/1.1\nHost: www.w3.org\r\n\r\n")} + packet1 := &TCPPacket{Data: []byte("GET /pub/WWW/ HTTP/1.1\nHost: www.w3.org\r\n\r\n")} + packet2 := &TCPPacket{Data: []byte("asd=asdasd&zxc=qwe\r\n\r\n")} - return &TCPMessage{packets: []*TCPPacket{packet}} + return &TCPMessage{packets: []*TCPPacket{packet1, packet2}} } -func mockReplayServer() (listener net.Listener) { - listener, _ = net.Listen("tcp", "127.0.0.1:0") +func mockServer() (replay net.Listener) { + replay, _ = net.Listen("tcp", "127.0.0.1:0") - Settings.ReplayAddress = listener.Addr().String() - - fmt.Println(listener.Addr().String()) + fmt.Println(replay.Addr().String()) return } func TestSendMessage(t *testing.T) { - Settings.Verbose = true + Settings.Verbose = false - listener := mockReplayServer() + replay := mockServer() + + Settings.ReplayAddress = replay.Addr().String() msg := getTCPMessage() sendMessage(msg) - conn, _ := listener.Accept() + conn, _ := replay.Accept() defer conn.Close() buf := make([]byte, 1024) @@ -43,3 +45,37 @@ func TestSendMessage(t *testing.T) { t.Errorf("Original and reveived requests does not match") } } + +/* +func TestPerformance(t *testing.T) { + Settings.Verbose = true + + replay := mockReplayServer() + + msg := getTCPMessage() + + for y := 0; y < 10; y++ { + go func() { + for { + conn, _ := replay.Accept() + + go func() { + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + buf = buf[0:n] + + conn.Close() + }() + } + }() + } + + for y := 0; y < 10; y++ { + for i := 0; i < 500; i++ { + go sendMessage(msg) + } + + time.Sleep(time.Millisecond * 1000) + } +} +*/ diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index ce2529b..789f9bc 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -28,9 +28,9 @@ type RAWTCPListener struct { func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener = &RAWTCPListener{} - listener.c_packets = make(chan *TCPPacket) - listener.c_messages = make(chan *TCPMessage) - listener.c_del_message = make(chan *TCPMessage) + listener.c_packets = make(chan *TCPPacket, 100) + listener.c_messages = make(chan *TCPMessage, 50) + listener.c_del_message = make(chan *TCPMessage, 10) listener.addr = addr listener.port = port @@ -98,37 +98,46 @@ func (t *RAWTCPListener) readRAWSocket() { if err != nil { Debug("Error:", err) + continue } if n > 0 { - // To avoid full packet parsing every time, we manually parsing values needed for packet filtering - // http://en.wikipedia.org/wiki/Transmission_Control_Protocol - dest_port := binary.BigEndian.Uint16(buf[2:4]) - - // Because RAW_SOCKET can't be bound to port, we have to control it by ourself - if int(dest_port) == t.port { - // Check TCPPacket code for more description - flags := binary.BigEndian.Uint16(buf[12:14]) & 0x1FF - f_psh := (flags & TCP_PSH) != 0 - - // We need only packets with data inside - // TCP PSH flag indicate that packet have data inside - if f_psh { - // We should create new buffer because go slices is pointers. So buffer data shoud be immutable. - new_buf := make([]byte, n) - copy(new_buf, buf[:n]) - - // To avoid socket locking processing packet in new goroutine - go func(buf []byte) { - packet := NewTCPPacket(new_buf) - t.c_packets <- packet - }(new_buf) - } - } + t.parsePacket(buf[:n]) } } } +func (t *RAWTCPListener) parsePacket(buf []byte) { + if isIncomingDataPacket(buf, t.port) { + new_buf := make([]byte, len(buf)) + copy(new_buf, buf) + + packet := NewTCPPacket(new_buf) + t.c_packets <- packet + } +} + +func isIncomingDataPacket(buf []byte, port int) bool { + // To avoid full packet parsing every time, we manually parsing values needed for packet filtering + // http://en.wikipedia.org/wiki/Transmission_Control_Protocol + dest_port := binary.BigEndian.Uint16(buf[2:4]) + + // Because RAW_SOCKET can't be bound to port, we have to control it by ourself + if int(dest_port) == port { + // Check TCPPacket code for more description + flags := binary.BigEndian.Uint16(buf[12:14]) & 0x1FF + + // We need only packets with data inside + // TCP PSH flag indicate that packet have data inside + if (flags & TCP_PSH) != 0 { + // We should create new buffer because go slices is pointers. So buffer data shoud be immutable. + return true + } + } + + return false +} + // Trying to add packet to existing message or creating new message // // For TCP message unique id is Acknowledgment number (see tcp_packet.go) diff --git a/listener/raw_tcp_listener_test.go b/listener/raw_tcp_listener_test.go new file mode 100644 index 0000000..8c32908 --- /dev/null +++ b/listener/raw_tcp_listener_test.go @@ -0,0 +1,84 @@ +package listener + +import ( + "encoding/binary" + "math/rand" + "net" + "strconv" + "sync" + "testing" +) + +func createHeader(ack uint32, port int) (header []byte, o_ack uint32) { + if ack == 0 { + ack = rand.Uint32() + } + + seq := rand.Uint32() + + header = make([]byte, 256) + + binary.BigEndian.PutUint16(header[2:4], uint16(port)) + binary.BigEndian.PutUint32(header[4:8], seq) + binary.BigEndian.PutUint32(header[8:12], ack) + header[12] = 4 << 4 + header[13] = 8 // Setting PSH flag + + return header, ack +} + +func getPackets(port int) [][]byte { + if rand.Int()%2 == 0 { + tcp, _ := createHeader(uint32(0), port) + tcp = append(tcp, []byte("GET /pub/WWW/ HTTP/1.1\nHost: www.w3.org\r\n\r\n")...) + + return [][]byte{tcp} + } else { + tcp1, ack := createHeader(uint32(0), port) + tcp1 = append(tcp1, []byte("POST /pub/WWW/ HTTP/1.1\nHost: www.w3.org\r\n\r\n")...) + + tcp2, _ := createHeader(ack, port) + tcp2 = append(tcp2, []byte("a=1&b=2\r\n\r\n")...) + + return [][]byte{tcp1, tcp2} + } + +} + +func TestRawTCPListener(t *testing.T) { + Settings.Verbose = true + + server := mockServer() + //server_addr := server.Addr().String() + host, port_str, _ := net.SplitHostPort(server.Addr().String()) + port, _ := strconv.Atoi(port_str) + + // Accept all packets + go func() { + conn, _ := server.Accept() + conn.Close() + }() + + listener := RAWTCPListen(host, port) + + var wg sync.WaitGroup + + go func() { + for { + listener.Receive() + wg.Done() + } + }() + + for i := 0; i < 100000; i++ { + wg.Add(1) + + packets := getPackets(port) + + for _, packet := range packets { + listener.parsePacket(packet) + } + } + + wg.Wait() +} From cbc4204e500b35fd9cb0bcd6e08de548c4ab6f63 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 09:02:27 +0200 Subject: [PATCH 3/9] Some minor changes --- listener/raw_tcp_listener.go | 9 ++++----- listener/raw_tcp_listener_test.go | 2 +- listener/tcp_packet.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index 789f9bc..81c2717 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -28,9 +28,9 @@ type RAWTCPListener struct { func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener = &RAWTCPListener{} - listener.c_packets = make(chan *TCPPacket, 100) - listener.c_messages = make(chan *TCPMessage, 50) - listener.c_del_message = make(chan *TCPMessage, 10) + listener.c_packets = make(chan *TCPPacket, 1000) + listener.c_messages = make(chan *TCPMessage, 1000) + listener.c_del_message = make(chan *TCPMessage, 1000) listener.addr = addr listener.port = port @@ -112,8 +112,7 @@ func (t *RAWTCPListener) parsePacket(buf []byte) { new_buf := make([]byte, len(buf)) copy(new_buf, buf) - packet := NewTCPPacket(new_buf) - t.c_packets <- packet + t.c_packets <- ParseTCPPacket(new_buf) } } diff --git a/listener/raw_tcp_listener_test.go b/listener/raw_tcp_listener_test.go index 8c32908..ca82bbd 100644 --- a/listener/raw_tcp_listener_test.go +++ b/listener/raw_tcp_listener_test.go @@ -70,7 +70,7 @@ func TestRawTCPListener(t *testing.T) { } }() - for i := 0; i < 100000; i++ { + for i := 0; i < 10000; i++ { wg.Add(1) packets := getPackets(port) diff --git a/listener/tcp_packet.go b/listener/tcp_packet.go index e466d6e..73d0007 100644 --- a/listener/tcp_packet.go +++ b/listener/tcp_packet.go @@ -36,7 +36,7 @@ type TCPPacket struct { Data []byte } -func NewTCPPacket(b []byte) (p *TCPPacket) { +func ParseTCPPacket(b []byte) (p *TCPPacket) { p = &TCPPacket{Data: b} p.ParseFast() From 3cb98aaf9455297b722b64c7f533f77bcce2e60d Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 09:12:22 +0200 Subject: [PATCH 4/9] Use less buffer --- listener/raw_tcp_listener.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index 81c2717..43492f9 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -28,9 +28,9 @@ type RAWTCPListener struct { func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener = &RAWTCPListener{} - listener.c_packets = make(chan *TCPPacket, 1000) - listener.c_messages = make(chan *TCPMessage, 1000) - listener.c_del_message = make(chan *TCPMessage, 1000) + listener.c_packets = make(chan *TCPPacket, 100) + listener.c_messages = make(chan *TCPMessage, 100) + listener.c_del_message = make(chan *TCPMessage, 100) listener.addr = addr listener.port = port From c3330cb4be1f271e25af0629ce146a1d7d9b7655 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 09:17:32 +0200 Subject: [PATCH 5/9] Use close(channel) instead of creating new channel --- listener/tcp_message.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/listener/tcp_message.go b/listener/tcp_message.go index b34ec8c..fda4617 100644 --- a/listener/tcp_message.go +++ b/listener/tcp_message.go @@ -19,10 +19,7 @@ type TCPMessage struct { timer *time.Timer // Used for expire check - expired bool - c_packets chan *TCPPacket - c_closing chan int c_del_message chan *TCPMessage } @@ -31,7 +28,6 @@ func NewTCPMessage(Ack uint32, c_del chan *TCPMessage) (msg *TCPMessage) { msg = &TCPMessage{Ack: Ack} msg.c_packets = make(chan *TCPPacket) - msg.c_closing = make(chan int) msg.c_del_message = c_del // used for notifying that message completed or expired // Every time we receive packet we reset this timer @@ -45,17 +41,19 @@ func NewTCPMessage(Ack uint32, c_del chan *TCPMessage) (msg *TCPMessage) { func (t *TCPMessage) listen() { for { select { - case <-t.c_closing: - close(t.c_packets) - return // Stop loop if message completed/expired - case packet := <-t.c_packets: - t.AddPacket(packet) + case packet, more := <-t.c_packets: + if more { + t.AddPacket(packet) + } else { + // Stop loop if channel closed + return + } } } } func (t *TCPMessage) Timeout() { - t.c_closing <- 1 // Notify to stop listen loop and close channel + close(t.c_packets) // Notify to stop listen loop and close channel t.c_del_message <- t // Notify RAWListener that message is ready to be send to replay server } @@ -81,11 +79,6 @@ func (t *TCPMessage) Bytes() (output []byte) { // Add packet to the message and ensure packet uniquiness // TCP allows that packet can be re-send multiple times func (t *TCPMessage) AddPacket(packet *TCPPacket) { - if t.expired { - Debug("Adding packet to expired message") - return - } - packetFound := false for _, pkt := range t.packets { From fa684d4645059b8be08b42a270d610bcc441fa28 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 09:24:47 +0200 Subject: [PATCH 6/9] Used maps instead of slices for storing messages --- listener/raw_tcp_listener.go | 44 +++++------------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index 43492f9..fc810fe 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -14,7 +14,7 @@ import ( // Since we can't use default TCP libraries RAWTCPLitener implements own TCP layer // TCP packets is parsed using tcp_packet.go, and flow control is managed by tcp_message.go type RAWTCPListener struct { - messages []*TCPMessage // buffer of TCPMessages waiting to be send + messages map[uint32]*TCPMessage // buffer of TCPMessages waiting to be send c_packets chan *TCPPacket c_messages chan *TCPMessage // Messages ready to be send to client @@ -31,6 +31,7 @@ func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener.c_packets = make(chan *TCPPacket, 100) listener.c_messages = make(chan *TCPMessage, 100) listener.c_del_message = make(chan *TCPMessage, 100) + listener.messages = make(map[uint32]*TCPMessage) listener.addr = addr listener.port = port @@ -47,7 +48,7 @@ func (t *RAWTCPListener) listen() { // If message ready for deletion it means that its also complete or expired by timeout case message := <-t.c_del_message: t.c_messages <- message - t.deleteMessage(message) + delete(t.messages, message.Ack) // We need to use channgels to process each packet to avoid data races case packet := <-t.c_packets: @@ -56,32 +57,6 @@ func (t *RAWTCPListener) listen() { } } -// Deleting messages that came from t.c_del_message channel -func (t *RAWTCPListener) deleteMessage(message *TCPMessage) bool { - var idx int = -1 - - // Searching for given message in messages buffer - for i, m := range t.messages { - if m.Ack == message.Ack { - idx = i - break - } - } - - if idx == -1 { - return false - } - - // Delete element from array - // Note: that this version for arrays that consist of pointers - // https://code.google.com/p/go-wiki/wiki/SliceTricks - copy(t.messages[idx:], t.messages[idx+1:]) - t.messages[len(t.messages)-1] = nil // Ensure that value will be garbage-collected. - t.messages = t.messages[:len(t.messages)-1] - - return true -} - func (t *RAWTCPListener) readRAWSocket() { conn, e := net.ListenPacket("ip4:tcp", t.addr) defer conn.Close() @@ -143,19 +118,12 @@ func isIncomingDataPacket(buf []byte, port int) bool { func (t *RAWTCPListener) processTCPPacket(packet *TCPPacket) { var message *TCPMessage - // Searching for message with same Ack - for _, msg := range t.messages { - if msg.Ack == packet.Ack { - message = msg - break - } - } + message, ok := t.messages[packet.Ack] - if message == nil { + if !ok { // We sending c_del_message channel, so message object can communicate with Listener and notify it if message completed message = NewTCPMessage(packet.Ack, t.c_del_message) - - t.messages = append(t.messages, message) + t.messages[packet.Ack] = message } // Adding packet to message From f07586a12e57cd995fbfaf135dc770187ce696b9 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 09:26:07 +0200 Subject: [PATCH 7/9] isIncomingDataPacket should be struct function --- listener/raw_tcp_listener.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index fc810fe..81e9ed8 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -83,7 +83,7 @@ func (t *RAWTCPListener) readRAWSocket() { } func (t *RAWTCPListener) parsePacket(buf []byte) { - if isIncomingDataPacket(buf, t.port) { + if t.isIncomingDataPacket(buf) { new_buf := make([]byte, len(buf)) copy(new_buf, buf) @@ -91,13 +91,13 @@ func (t *RAWTCPListener) parsePacket(buf []byte) { } } -func isIncomingDataPacket(buf []byte, port int) bool { +func (t *RAWTCPListener) isIncomingDataPacket(buf []byte) bool { // To avoid full packet parsing every time, we manually parsing values needed for packet filtering // http://en.wikipedia.org/wiki/Transmission_Control_Protocol dest_port := binary.BigEndian.Uint16(buf[2:4]) // Because RAW_SOCKET can't be bound to port, we have to control it by ourself - if int(dest_port) == port { + if int(dest_port) == t.port { // Check TCPPacket code for more description flags := binary.BigEndian.Uint16(buf[12:14]) & 0x1FF From a1f9cc501b207ec8a4d9853bac5b4842aa7fcb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20=C3=81lvarez?= Date: Mon, 2 Sep 2013 22:16:07 +0200 Subject: [PATCH 8/9] General review: - Fix typos - Follow some Go good practices - Better documentation, more consistent godoc.org - Refactoring --- CHANGELOG.md | 6 +++--- gor.go | 18 +++++++++--------- integration_test.go | 16 +++++++--------- listener/listener.go | 13 +++++++------ listener/listener_test.go | 2 +- listener/raw_tcp_listener.go | 6 ++++-- listener/settings.go | 14 ++++++++------ listener/tcp_message.go | 12 +++++++----- listener/tcp_packet.go | 26 ++++++++++++-------------- replay/replay.go | 11 ++++++----- replay/request_factory.go | 12 +++++++----- replay/request_stats.go | 18 +++++++++--------- replay/settings.go | 18 +++++++++++------- 13 files changed, 91 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda6098..8c27605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ v0.3.5 - 15 Sep 2013 -* Significally improved test coverage +* Significantly improved test coverage * Fixed bug with redirect replay https://github.com/buger/gor/pull/15 * Added limit on listener side * Improved stability (catch and log panic, instead of exiting) @@ -7,8 +7,8 @@ v0.3.5 - 15 Sep 2013 v0.3.3 - 22 Jun 2013 * Using TCP instead of UDP for communication between Listener and Replay -* Significally improved performance -* Fixed bugs causing locking and message droping (concurrency issues) +* Significantly improved performance +* Fixed bugs causing locking and message dropping (concurrency issues) * Rewrote concurrency model to use more channels v0.3 - 10 Jun 2013 diff --git a/gor.go b/gor.go index fb14b49..9bf6d78 100644 --- a/gor.go +++ b/gor.go @@ -8,27 +8,29 @@ package main import ( "flag" "fmt" - "github.com/buger/gor/listener" - "github.com/buger/gor/replay" "log" "os" "runtime/pprof" "time" + + "github.com/buger/gor/listener" + "github.com/buger/gor/replay" ) const ( VERSION = "0.3.5" ) -var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") -var memprofile = flag.String("memprofile", "", "write memory profile to this file") +var ( + mode string + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + memprofile = flag.String("memprofile", "", "write memory profile to this file") +) func main() { defer func() { if r := recover(); r != nil { - var ok bool - _, ok = r.(error) - if !ok { + if _, ok := r.(error); !ok { fmt.Errorf("pkg: %v", r) } } @@ -36,8 +38,6 @@ func main() { fmt.Println("Version:", VERSION) - mode := "unknown" - if len(os.Args) > 1 { mode = os.Args[1] } diff --git a/integration_test.go b/integration_test.go index 24b8d78..4086371 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1,18 +1,15 @@ package main import ( - "testing" - - "github.com/buger/gor/listener" - "github.com/buger/gor/replay" - - "time" - "fmt" "net/http" "strconv" - "sync/atomic" + "testing" + "time" + + "github.com/buger/gor/listener" + "github.com/buger/gor/replay" ) func isEqual(t *testing.T, a interface{}, b interface{}) { @@ -56,7 +53,7 @@ func (e *Env) startListener(port int, replayPort int) { listener.Settings.Port = port if e.ListenerLimit != 0 { - listener.Settings.ReplayAddress += "|" + strconv.Itoa(e.ListenerLimit) + listener.Settings.ReplayLimit = e.ListenerLimit } listener.Run() @@ -65,6 +62,7 @@ func (e *Env) startListener(port int, replayPort int) { func (e *Env) startReplay(port int, forwardPort int) { replay.Settings.Verbose = e.Verbose replay.Settings.Host = "127.0.0.1" + replay.Settings.Address = "127.0.0.1:" + strconv.Itoa(port) replay.Settings.ForwardAddress = "127.0.0.1:" + strconv.Itoa(forwardPort) replay.Settings.Port = port diff --git a/listener/listener.go b/listener/listener.go index deeb671..77418e3 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -1,7 +1,7 @@ // Listener capture TCP traffic using RAW SOCKETS. // Note: it requires sudo or root access. // -// Rigt now it suport only HTTP +// Right now it supports only HTTP package listener import ( @@ -16,16 +16,17 @@ import ( "time" ) -// Enable debug logging only if "--verbose" flag passed +// Debug enables logging only if "--verbose" flag passed func Debug(v ...interface{}) { if Settings.Verbose { log.Println(v...) } } +// ReplayServer returns a connection to the replay server and error if some func ReplayServer() (conn net.Conn, err error) { - // Connection to reaplay server - conn, err = net.Dial("tcp", Settings.ReplayServer()) + // Connection to replay server + conn, err = net.Dial("tcp", Settings.ReplayAddress) if err != nil { log.Println("Connection error ", err, Settings.ReplayAddress) @@ -34,7 +35,7 @@ func ReplayServer() (conn net.Conn, err error) { return } -// Because its sub-program, Run acts as `main` +// Run acts as `main` function of a listener func Run() { if os.Getuid() != 0 { fmt.Println("Please start the listener as root or sudo!") @@ -43,7 +44,7 @@ func Run() { } fmt.Println("Listening for HTTP traffic on", Settings.Address+":"+strconv.Itoa(Settings.Port)) - fmt.Println("Forwarding requests to replay server:", Settings.ReplayServer(), "Limit:", Settings.ReplayLimit) + fmt.Println("Forwarding requests to replay server:", Settings.ReplayAddress, "Limit:", Settings.ReplayLimit) // Sniffing traffic from given address listener := RAWTCPListen(Settings.Address, Settings.Port) diff --git a/listener/listener_test.go b/listener/listener_test.go index 319ed55..775399c 100644 --- a/listener/listener_test.go +++ b/listener/listener_test.go @@ -40,6 +40,6 @@ func TestSendMessage(t *testing.T) { buf = buf[0:n] if bytes.Compare(buf, msg.Bytes()) != 0 { - t.Errorf("Original and reveived requests does not match") + t.Errorf("Original and received requests does not match") } } diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index ce2529b..911c83c 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -25,6 +25,7 @@ type RAWTCPListener struct { port int // Port to listen } +// RAWTCPListen creates a listener to capture traffic from RAW_SOCKET func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener = &RAWTCPListener{} @@ -49,7 +50,7 @@ func (t *RAWTCPListener) listen() { t.c_messages <- message t.deleteMessage(message) - // We need to use channgels to process each packet to avoid data races + // We need to use channels to process each packet to avoid data races case packet := <-t.c_packets: t.processTCPPacket(packet) } @@ -58,7 +59,7 @@ func (t *RAWTCPListener) listen() { // Deleting messages that came from t.c_del_message channel func (t *RAWTCPListener) deleteMessage(message *TCPMessage) bool { - var idx int = -1 + idx := -1 // Searching for given message in messages buffer for i, m := range t.messages { @@ -154,6 +155,7 @@ func (t *RAWTCPListener) processTCPPacket(packet *TCPPacket) { message.c_packets <- packet } +// Receive TCP messages from the listener channel func (t *RAWTCPListener) Receive() *TCPMessage { return <-t.c_messages } diff --git a/listener/settings.go b/listener/settings.go index 4d6cd1c..fe2b9c9 100644 --- a/listener/settings.go +++ b/listener/settings.go @@ -14,6 +14,7 @@ const ( defaultReplayAddress = "localhost:28020" ) +// ListenerSettings contain all the needed configuration for setting up the listener type ListenerSettings struct { Port int Address string @@ -27,14 +28,15 @@ type ListenerSettings struct { var Settings ListenerSettings = ListenerSettings{} -func (s *ListenerSettings) ReplayServer() string { - host_info := strings.Split(s.ReplayAddress, "|") +// ReplayServer generates ReplayLimit and ReplayAddress settings out of the replayAddress +func (s *ListenerSettings) ReplayServer(replayAddress string) { + host_info := strings.Split(replayAddress, "|") if len(host_info) > 1 { s.ReplayLimit, _ = strconv.Atoi(host_info[1]) } - return host_info[0] + s.ReplayAddress = host_info[0] } func init() { @@ -43,10 +45,10 @@ func init() { } flag.IntVar(&Settings.Port, "p", defaultPort, "Specify the http server port whose traffic you want to capture") + flag.StringVar(&Settings.Address, "ip", defaultAddress, "Specify IP address to listen") - flag.StringVar(&Settings.Address, "ip", defaultAddress, "Specifi IP address to listen") - - flag.StringVar(&Settings.ReplayAddress, "r", defaultReplayAddress, "Address of replay server.") + replayAddress := flag.String("r", defaultReplayAddress, "Address of replay server.") + Settings.ReplayServer(*replayAddress) flag.BoolVar(&Settings.Verbose, "verbose", false, "Log requests") } diff --git a/listener/tcp_message.go b/listener/tcp_message.go index b34ec8c..0172149 100644 --- a/listener/tcp_message.go +++ b/listener/tcp_message.go @@ -10,9 +10,9 @@ const MSG_EXPIRE = 200 * time.Millisecond // TCPMessage ensure that all TCP packets for given request is received, and processed in right sequence // Its needed because all TCP message can be fragmented or re-transmitted // -// Each TCP Packet have 2 ids: acknowledgement - message_id, and sequence - packet_id +// Each TCP Packet have 2 ids: acknowledgment - message_id, and sequence - packet_id // Message can be compiled from unique packets with same message_id which sorted by sequence -// Message is received if we did't receive any packets for 200ms +// Message is received if we didn't receive any packets for 200ms type TCPMessage struct { Ack uint32 // Message ID packets []*TCPPacket @@ -27,6 +27,7 @@ type TCPMessage struct { c_del_message chan *TCPMessage } +// NewTCPMessage pointer created from a Acknowledgment number and a channel of messages readuy to be deleted func NewTCPMessage(Ack uint32, c_del chan *TCPMessage) (msg *TCPMessage) { msg = &TCPMessage{Ack: Ack} @@ -54,12 +55,13 @@ func (t *TCPMessage) listen() { } } +// Timeout notifies message to stop listening, close channel and message ready to be sent func (t *TCPMessage) Timeout() { t.c_closing <- 1 // Notify to stop listen loop and close channel - t.c_del_message <- t // Notify RAWListener that message is ready to be send to replay server + t.c_del_message <- t // Notify RAWListener that message is ready to be sent to replay server } -// Sort packets in right orders and return message content +// Bytes sorts packets in right orders and return message content func (t *TCPMessage) Bytes() (output []byte) { mk := make([]int, len(t.packets)) @@ -78,7 +80,7 @@ func (t *TCPMessage) Bytes() (output []byte) { return } -// Add packet to the message and ensure packet uniquiness +// AddPacket to the message and ensure packet uniqueness // TCP allows that packet can be re-send multiple times func (t *TCPMessage) AddPacket(packet *TCPPacket) { if t.expired { diff --git a/listener/tcp_packet.go b/listener/tcp_packet.go index e466d6e..0020279 100644 --- a/listener/tcp_packet.go +++ b/listener/tcp_packet.go @@ -36,43 +36,41 @@ type TCPPacket struct { Data []byte } -func NewTCPPacket(b []byte) (p *TCPPacket) { - p = &TCPPacket{Data: b} - p.ParseFast() +// NewTCPPacket pointer from data in []byte +func NewTCPPacket(b []byte) (t *TCPPacket) { + t = &TCPPacket{Data: b} + t.ParseBasic() - return p + return t } -// Inspired by: https://github.com/miekg/pcap/blob/master/packet.go +// Parse TCP Packet, inspired by: https://github.com/miekg/pcap/blob/master/packet.go func (t *TCPPacket) Parse() { + t.ParseBasic() t.SrcPort = binary.BigEndian.Uint16(t.Data[0:2]) t.DestPort = binary.BigEndian.Uint16(t.Data[2:4]) - t.Seq = binary.BigEndian.Uint32(t.Data[4:8]) - t.Ack = binary.BigEndian.Uint32(t.Data[8:12]) - t.DataOffset = (t.Data[12] & 0xF0) >> 4 t.Flags = binary.BigEndian.Uint16(t.Data[12:14]) & 0x1FF t.Window = binary.BigEndian.Uint16(t.Data[14:16]) t.Checksum = binary.BigEndian.Uint16(t.Data[16:18]) t.Urgent = binary.BigEndian.Uint16(t.Data[18:20]) - - t.Data = t.Data[t.DataOffset*4:] } -// Parse only needed set of fields -func (t *TCPPacket) ParseFast() { +// ParseBasic set of fields +func (t *TCPPacket) ParseBasic() { t.Seq = binary.BigEndian.Uint32(t.Data[4:8]) t.Ack = binary.BigEndian.Uint32(t.Data[8:12]) - t.DataOffset = (t.Data[12] & 0xF0) >> 4 + t.Data = t.Data[t.DataOffset*4:] } +// String output for a TCP Packet func (t *TCPPacket) String() string { return strings.Join([]string{ "Source port: " + strconv.Itoa(int(t.SrcPort)), "Dest port:" + strconv.Itoa(int(t.DestPort)), "Sequence:" + strconv.Itoa(int(t.Seq)), - "Acknowledgement:" + strconv.Itoa(int(t.Ack)), + "Acknowledgment:" + strconv.Itoa(int(t.Ack)), "Header len:" + strconv.Itoa(int(t.DataOffset)), "Flag ns:" + strconv.FormatBool(t.Flags&TCP_NS != 0), diff --git a/replay/replay.go b/replay/replay.go index efb487b..cd2c978 100644 --- a/replay/replay.go +++ b/replay/replay.go @@ -35,13 +35,14 @@ import ( const bufSize = 4096 -// Enable debug logging only if "--verbose" flag passed +// Debug enables logging only if "--verbose" flag passed func Debug(v ...interface{}) { if Settings.Verbose { log.Println(v...) } } +// ParseRequest in []byte returns a http request or an error func ParseRequest(data []byte) (request *http.Request, err error) { buf := bytes.NewBuffer(data) reader := bufio.NewReader(buf) @@ -50,13 +51,13 @@ func ParseRequest(data []byte) (request *http.Request, err error) { return } -// Because its sub-program, Run acts as `main` +// Run acts as `main` function of replay // Replay server listen to UDP traffic from Listeners // Each request processed by RequestFactory func Run() { - listener, err := net.Listen("tcp", Settings.Address()) + listener, err := net.Listen("tcp", Settings.Address) - log.Println("Starting replay server at:", Settings.Address()) + log.Println("Starting replay server at:", Settings.Address) if err != nil { log.Fatal("Can't start:", err) @@ -97,7 +98,7 @@ func handleConnection(conn net.Conn, rf *RequestFactory) error { case io.EOF: read = false case nil: - response = append(response, buf[0:n]...) + response = append(response, buf[:n]...) if n < bufSize { read = false } diff --git a/replay/request_factory.go b/replay/request_factory.go index 1d8e65f..8ebb592 100644 --- a/replay/request_factory.go +++ b/replay/request_factory.go @@ -6,6 +6,8 @@ import ( "net/url" ) +// HttpResponse contains a host, a http request, +// a http response and an error type HttpResponse struct { host *ForwardHost req *http.Request @@ -13,7 +15,7 @@ type HttpResponse struct { err error } -// Class for processing requests +// RequestFactory processes requests // // Basic workflow: // @@ -26,7 +28,7 @@ type RequestFactory struct { c_requests chan *http.Request } -// RequestFactory contstuctor +// NewRequestFactory returns a RequestFactory pointer // One created, it starts listening for incoming requests: requests channel func NewRequestFactory() (factory *RequestFactory) { factory = &RequestFactory{} @@ -38,7 +40,7 @@ func NewRequestFactory() (factory *RequestFactory) { return } -// Disable redirects https://github.com/buger/gor/pull/15 +// customCheckRedirect disables redirects https://github.com/buger/gor/pull/15 func customCheckRedirect(req *http.Request, via []*http.Request) error { if len(via) >= 0 { return errors.New("stopped after 2 redirects") @@ -46,7 +48,7 @@ func customCheckRedirect(req *http.Request, via []*http.Request) error { return nil } -// Forward http request to given host +// sendRequest forwards http request to a given host func (f *RequestFactory) sendRequest(host *ForwardHost, request *http.Request) { client := &http.Client{ CheckRedirect: customCheckRedirect, @@ -71,7 +73,7 @@ func (f *RequestFactory) sendRequest(host *ForwardHost, request *http.Request) { f.c_responses <- &HttpResponse{host, request, resp, err} } -// Handle incoming requests, and they responses +// handleRequests and their responses func (f *RequestFactory) handleRequests() { hosts := Settings.ForwardedHosts() diff --git a/replay/request_stats.go b/replay/request_stats.go index d1bbf32..cf26282 100644 --- a/replay/request_stats.go +++ b/replay/request_stats.go @@ -4,31 +4,31 @@ import ( "time" ) -// Stats stores in context of current timestamp +// RequestStat stores in context of current timestamp type RequestStat struct { timestamp int64 Codes map[int]int // { 200: 10, 404:2, 500:1 } Count int // All requests including errors - Errors int // Rquests with errors (timeout or host not reachable). Not include 50x errors. + Errors int // Requests with errors (timeout or host not reachable). Not include 50x errors. host *ForwardHost } -// Ensure that current stats is actual (for current timestamp) +// Touch ensures that current stats is actual (for current timestamp) func (s *RequestStat) Touch() { if s.timestamp != time.Now().Unix() { s.reset() } } -// Called on request start +// IncReq is called on request start func (s *RequestStat) IncReq() { s.Count++ } -// Called after response +// IncResp is called after response func (s *RequestStat) IncResp(resp *HttpResponse) { s.Touch() @@ -40,11 +40,11 @@ func (s *RequestStat) IncResp(resp *HttpResponse) { s.Codes[resp.resp.StatusCode]++ } -// Updated stats timestamp to current time and reset to zero all stats values +// reset updates stats timestamp to current time and reset to zero all stats values // TODO: Further on reset it should write stats to file func (s *RequestStat) reset() { - if s.timestamp != 0 { - Debug("Host:", s.host.Url, "Requests:", s.Count, "Errors:", s.Errors, "Status codes:", s.Codes) + if s.timestamp != 0 { + Debug("Host:", s.host.Url, "Requests:", s.Count, "Errors:", s.Errors, "Status codes:", s.Codes) } s.timestamp = time.Now().Unix() @@ -54,7 +54,7 @@ func (s *RequestStat) reset() { s.Errors = 0 } -// RequestStat constructor +// NewRequestStats returns a RequestStat pointer func NewRequestStats(host *ForwardHost) (stat *RequestStat) { stat = &RequestStat{host: host} stat.reset() diff --git a/replay/settings.go b/replay/settings.go index 8ac8dfb..3aa3b3c 100644 --- a/replay/settings.go +++ b/replay/settings.go @@ -7,6 +7,7 @@ import ( "strings" ) +// ForwardHost where to forward requests type ForwardHost struct { Url string Limit int @@ -14,15 +15,19 @@ type ForwardHost struct { Stat *RequestStat } +// ReplaySettings ListenerSettings contain all the needed configuration for setting up the replay type ReplaySettings struct { Port int Host string + Address string ForwardAddress string Verbose bool } +var Settings ReplaySettings = ReplaySettings{} + // ForwardedHosts implements forwardAddress syntax support for multiple hosts (coma separated), and rate limiting by specifing "|maxRps" after host name. // // -f "host1,http://host2|10,host3" @@ -50,13 +55,11 @@ func (r *ReplaySettings) ForwardedHosts() (hosts []*ForwardHost) { return } -// Helper to return address with port, e.g.: 127.0.0.1:28020 -func (r *ReplaySettings) Address() string { - return r.Host + ":" + strconv.Itoa(r.Port) +// SetAddress with port, e.g.: 127.0.0.1:28020 +func (r *ReplaySettings) SetAddress() { + r.Address = r.Host + ":" + strconv.Itoa(r.Port) } -var Settings ReplaySettings = ReplaySettings{} - func init() { if len(os.Args) < 2 || os.Args[1] != "replay" { return @@ -66,14 +69,15 @@ func init() { defaultPort = 28020 defaultHost = "0.0.0.0" - defaultAddress = "http://localhost:8080" + defaultForwardAddress = "http://localhost:8080" ) flag.IntVar(&Settings.Port, "p", defaultPort, "specify port number") flag.StringVar(&Settings.Host, "ip", defaultHost, "ip addresses to listen on") - flag.StringVar(&Settings.ForwardAddress, "f", defaultAddress, "http address to forward traffic.\n\tYou can limit requests per second by adding `|num` after address.\n\tIf you have multiple addresses with different limits. For example: http://staging.example.com|100,http://dev.example.com|10") + Settings.SetAddress() + flag.StringVar(&Settings.ForwardAddress, "f", defaultForwardAddress, "http address to forward traffic.\n\tYou can limit requests per second by adding `|num` after address.\n\tIf you have multiple addresses with different limits. For example: http://staging.example.com|100,http://dev.example.com|10") flag.BoolVar(&Settings.Verbose, "verbose", false, "Log requests") } From 578d785c27fe53e888032bd6e6d6894def048795 Mon Sep 17 00:00:00 2001 From: Leonid Bugaev Date: Mon, 30 Sep 2013 11:32:26 +0200 Subject: [PATCH 9/9] Fixed previous merge oops :) --- listener/raw_tcp_listener.go | 26 -------------------------- listener/tcp_packet.go | 4 ++-- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index f657ce8..e9f2c14 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -58,32 +58,6 @@ func (t *RAWTCPListener) listen() { } } -// Deleting messages that came from t.c_del_message channel -func (t *RAWTCPListener) deleteMessage(message *TCPMessage) bool { - idx := -1 - - // Searching for given message in messages buffer - for i, m := range t.messages { - if m.Ack == message.Ack { - idx = i - break - } - } - - if idx == -1 { - return false - } - - // Delete element from array - // Note: that this version for arrays that consist of pointers - // https://code.google.com/p/go-wiki/wiki/SliceTricks - copy(t.messages[idx:], t.messages[idx+1:]) - t.messages[len(t.messages)-1] = nil // Ensure that value will be garbage-collected. - t.messages = t.messages[:len(t.messages)-1] - - return true -} - func (t *RAWTCPListener) readRAWSocket() { conn, e := net.ListenPacket("ip4:tcp", t.addr) defer conn.Close() diff --git a/listener/tcp_packet.go b/listener/tcp_packet.go index 689b0f7..2282782 100644 --- a/listener/tcp_packet.go +++ b/listener/tcp_packet.go @@ -38,9 +38,9 @@ type TCPPacket struct { func ParseTCPPacket(b []byte) (p *TCPPacket) { p = &TCPPacket{Data: b} - p.ParseFast() + p.ParseBasic() - return t + return p } // Parse TCP Packet, inspired by: https://github.com/miekg/pcap/blob/master/packet.go