diff --git a/listener/listener.go b/listener/listener.go index 9e34d2f..4370dde 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -40,10 +40,11 @@ func Run() { log.Fatal("Connection error", err) } - // Sniffing traffic from given port + // Sniffing traffic from given address listener := RAWTCPListen(Settings.address, Settings.port) for { + // Receiving TCPMessage object m := listener.Receive() if Settings.verbose { diff --git a/listener/raw_tcp_listener.go b/listener/raw_tcp_listener.go index 8e2aa60..48fb87d 100644 --- a/listener/raw_tcp_listener.go +++ b/listener/raw_tcp_listener.go @@ -6,17 +6,23 @@ import ( "net" ) +// Capture traffic from socket using RAW_SOCKET's +// http://en.wikipedia.org/wiki/Raw_socket +// +// RAW_SOCKET allow you listen for traffic on any port (e.g. sniffing) because they operate on IP level. +// Ports is TCP feature, same as flow control, reliable transmission and etc. +// 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 c_packets chan *TCPPacket - c_messages chan *TCPMessage + c_messages chan *TCPMessage // Messages ready to be send to client - c_add_message chan *TCPMessage - c_del_message chan *TCPMessage + c_del_message chan *TCPMessage // Used for notifications about completed or expired messages - addr string - port int + addr string // IP to listen + port int // Port to listen } func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { @@ -24,14 +30,13 @@ func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { listener.c_packets = make(chan *TCPPacket) listener.c_messages = make(chan *TCPMessage) - listener.c_add_message = make(chan *TCPMessage) listener.c_del_message = make(chan *TCPMessage) listener.addr = addr listener.port = port go listener.listen() - go listener.readTCPPackets() + go listener.readRAWSocket() return } @@ -39,21 +44,23 @@ func RAWTCPListen(addr string, port int) (listener *RAWTCPListener) { func (t *RAWTCPListener) listen() { for { select { + // If message ready for deletion it means that its also complete or expired by timeout case message := <-t.c_del_message: t.deleteMessage(message) - Debug("Deleted") t.c_messages <- message + // We need to use channgels to process each packet to avoid data races case packet := <-t.c_packets: t.processTCPPacket(packet) - Debug("Processed") } } } +// 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 @@ -65,14 +72,17 @@ func (t *RAWTCPListener) deleteMessage(message *TCPMessage) bool { 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 // or the zero value of T + 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) readTCPPackets() { +func (t *RAWTCPListener) readRAWSocket() { conn, e := net.ListenPacket("ip4:tcp", t.addr) defer conn.Close() @@ -80,9 +90,10 @@ func (t *RAWTCPListener) readTCPPackets() { log.Fatal(e) } - buf := make([]byte, 1500*2) + buf := make([]byte, 4096) for { + // Note: ReadFrom receive messages without IP header n, _, err := conn.ReadFrom(buf) if err != nil { @@ -90,20 +101,24 @@ func (t *RAWTCPListener) readTCPPackets() { } 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 client should push data to buffer + // 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 @@ -114,10 +129,13 @@ func (t *RAWTCPListener) readTCPPackets() { } } +// Trying to add packet to existing message or creating new message // +// For TCP message unique id is Acknowledgment number (see tcp_packet.go) 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 @@ -126,12 +144,14 @@ func (t *RAWTCPListener) processTCPPacket(packet *TCPPacket) { } if message == nil { + // 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) Debug("Adding message") t.messages = append(t.messages, message) } + // Adding packet to message message.c_packets <- packet } diff --git a/listener/tcp_message.go b/listener/tcp_message.go index 4c4a5b0..b34ec8c 100644 --- a/listener/tcp_message.go +++ b/listener/tcp_message.go @@ -12,12 +12,12 @@ const MSG_EXPIRE = 200 * time.Millisecond // // Each TCP Packet have 2 ids: acknowledgement - 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 OR if we received packet with "fin" flag +// Message is received if we did't receive any packets for 200ms type TCPMessage struct { Ack uint32 // Message ID packets []*TCPPacket - timer *time.Timer + timer *time.Timer // Used for expire check expired bool @@ -32,21 +32,22 @@ func NewTCPMessage(Ack uint32, c_del chan *TCPMessage) (msg *TCPMessage) { msg.c_packets = make(chan *TCPPacket) msg.c_closing = make(chan int) - msg.c_del_message = c_del + msg.c_del_message = c_del // used for notifying that message completed or expired + // Every time we receive packet we reset this timer msg.timer = time.AfterFunc(MSG_EXPIRE, msg.Timeout) - go msg.ListenPackets() + go msg.listen() return } -func (t *TCPMessage) ListenPackets() { +func (t *TCPMessage) listen() { for { select { case <-t.c_closing: close(t.c_packets) - return + return // Stop loop if message completed/expired case packet := <-t.c_packets: t.AddPacket(packet) } @@ -54,8 +55,8 @@ func (t *TCPMessage) ListenPackets() { } func (t *TCPMessage) Timeout() { - t.c_closing <- 1 - t.c_del_message <- t + 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 } // Sort packets in right orders and return message content @@ -77,7 +78,8 @@ func (t *TCPMessage) Bytes() (output []byte) { return } -// Add packet to the message +// 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") @@ -99,5 +101,6 @@ func (t *TCPMessage) AddPacket(packet *TCPPacket) { t.packets = append(t.packets, packet) } + // Reset message timeout timer t.timer.Reset(MSG_EXPIRE) } diff --git a/replay/request_factory.go b/replay/request_factory.go index b60d9d1..82eb2f7 100644 --- a/replay/request_factory.go +++ b/replay/request_factory.go @@ -41,6 +41,7 @@ func NewRequestFactory() (factory *RequestFactory) { func (f *RequestFactory) sendRequest(host *ForwardHost, request *http.Request) { client := &http.Client{} + // Change HOST of original request URL := host.Url + request.URL.Path + "?" + request.URL.RawQuery request.RequestURI = ""