## Readme Code generator for packetbeat tcp based protocol analyzers. In order to create a new protocol analyzer, run inside your GOPATH where you want to create the protocol analyzer (stand-alone, within packetbeat based project or packetbeat itself): ``` python ${GOPATH}/src/github.com/elastic/beats/packetbeat/scripts/create_tcp_protocol.py ``` Note: If you have multiple go paths use `${GOPATH%%:*}`instead of `${GOPATH}`. This requires [python](https://www.python.org/downloads/) to be installed. ## Tutorial (TODO): ### 1. Implement protocol analyzer for simple echo server based protocol: - client: Send request to echo server. All requests must start with `>` character and end with newline character `\n` - server: Send echo response upon receiving a message. Echo response begins with `<` character. Errors responses begin with `!` character. An error message will be returned for any received request not starting with `>`. - Echo Server sample code: ``` package main /* Echo protocol test server listening on port 3030 First character in message indicates message type: '>': request '<': response '!': error response Sample test run: $ nc localhost 3030 >abc 123456 < 123456 asdfasdf !asdfasdf ^C */ import ( "bufio" "net" "os" ) func main() { bind := ":3030" server, err := net.Listen("tcp", bind) if server == nil { panic("couldn't start listening: " + err.Error()) } for { client, err := server.Accept() if err != nil { panic("failed accepting new client: " + err.Error()) } go echo(client) } } func echo(sock net.Conn) { defer sock.Close() in := bufio.NewReader(sock) for { line, err := in.ReadBytes('\n') if err != nil { os.Stderr.Write([]byte(err.Error())) return } if len(line) == 0 { continue } if line[0] == '>' { line[0] = '<' sock.Write(line) } else { sock.Write([]byte{'!'}) sock.Write(line) } } } ``` ### 2.1 Add protocol analyzer (echo) to packetbeat: Create analyzer skeleton from code generator template. ``` $ cd ${GOPATH}/src/github.com/elastic/beats/packetbeat $ python ${GOPATH}/src/github.com/elastic/beats/packetbeat/scripts/create_tcp_protocol.py ``` Load plugin into packetbeat by running `make update`. Or add `_ "github.com/elastic/beats/packetbeat/protos/echo"` to the import list in `$GOPATH/src/github.com/elastic/beats/packetbeat/include/list.go`. ### 2.2 Standalone beat with protocol analyzer (echo): Use packetbeat as framework to build custom beat (e.g. for testing) with selected protocol plugins only. A protocol plugin can still be added to packetbeat later by copying the final plugin to `$GOPATH/src/github.com/elastic/beats/packetbeat/protos` and importing module in `$GOPATH/src/github.com/elastic/beats/packetbeat/include/list.go`. Create custom beat (e.g. github.com//pb_echo): ``` $ mkdir -p ${GOPATH}/src/github.com//pb_echo $ cd ${GOPATH}/src/github.com//pb_echo ``` Add main.go importing packetbeat + new protocol (to be added to pb_echo/proto) package main ``` import ( "os" "github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/packetbeat/beater" // import supported protocol modules _ "github.com/urso/pb_echo/protos/echo" ) var Name = "pb_echo" // Setups and Runs Packetbeat func main() { if err := beat.Run(Name, "", beater.New); err != nil { os.Exit(1) } } ``` Create protocol analyzer module (use name ‘echo’ for new protocol): ``` $ mkdir proto $ cd proto $ python ${GOPATH}/src/github.com/elastic/beats/packetbeat/scripts/create_tcp_protocol.py ``` ### 3 Implement application layer analyzer Protocol analyzer structure for `echo` module. - `config.go`: protocol analyzer configuration options + validation - `echo.go`: protocol analyzer module keeping track of state in TCP context - `parser.go`: message parser implementation. - `trans.go`: correlate messages into transactions. Simple implementation with support for pipelining already provided. - `pub.go`: create+publish events. Generic event fields are already set Protocol analyzers to receive raw TCP payloads from packetbeat and must combine and parse them into messages `parser.go`. The parser state is stored in `connection`-struct in `echo.go`. Parsed messages are forwarded for merging with previously parsed messages in same direction (for example if responses consist of multiple messages being streamed) and correlation of requests and responses (See `trans.go`). Some simple message correlation with pipelining support is already provided. Once messages have been correlated a transaction event created in `createEvent` (file `pub.go`) is published. Common event fields are already populated by generated code. Do not remove these common event fields. ### 3.1 Add parser Add code to parse message from network stream to `func (*parser) parse(...)`: ``` type message struct { ... failed bool content common.NetString } ... func (p *parser) parse() (*message, error) { // wait for message being complete buf, err := p.buf.CollectUntil([]byte{'\n'}) if err == streambuf.ErrNoMoreBytes { return nil, nil } msg := p.message msg.Size = uint64(p.buf.BufferConsumed()) isRequest := true dir := applayer.NetOriginalDirection if len(buf) > 0 { c := buf[0] isRequest = !(c == '<' || c == '!') if !isRequest { msg.failed = c == '!' dir = applayer.NetReverseDirection } buf = buf[1:] } msg.content = common.NetString(buf) msg.IsRequest = isRequest msg.Direction = dir return msg, nil } ``` If possible you can use third-party libraries for parsing messages. This might require some more changes to the parser struct. ### 3.2 Add additional fields to transaction event ``` func (pub *transPub) createEvent(requ, resp *message) beat.Event { status := common.OK_STATUS if resp.failed { status = common.ERROR_STATUS } // resp_time in milliseconds responseTime := int32(resp.Ts.Sub(requ.Ts).Nanoseconds() / 1e6) src := &common.Endpoint{ IP: requ.Tuple.SrcIP.String(), Port: requ.Tuple.SrcPort, Proc: string(requ.CmdlineTuple.Src), } dst := &common.Endpoint{ IP: requ.Tuple.DstIP.String(), Port: requ.Tuple.DstPort, Proc: string(requ.CmdlineTuple.Dst), } fields := common.MapStr{ "type": "echo", "status": status, "responsetime": responseTime, "bytes_in": requ.Size, "bytes_out": resp.Size, "src": src, "dst": dst, } // add processing notes/errors to event if len(requ.Notes)+len(resp.Notes) > 0 { fields["notes"] = append(requ.Notes, resp.Notes...) } if pub.sendRequest { fields["request"] = requ.content } if pub.sendResponse { fields["response"] = requ.content } return beat.Event{ Timestamp: requ.Ts, Fields: fields, } } ``` ### 4 (TODO) Add protocol analyzer module to config files ### 5 (TODO) Build kibana dashboard ### 6 Tips - Prepare pcap (with tcpdump) for testing protocol analyzer during development - At least add tests using pcaps to system/test.