youtubebeat/vendor/github.com/elastic/beats/packetbeat/scripts/tcp-protocol
2018-11-18 11:08:38 +01:00
..
{protocol} Add generated youtubebeat files 2018-11-18 11:08:38 +01:00
README.md Add generated youtubebeat files 2018-11-18 11:08:38 +01:00

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 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
    <abc
    asdfkjhasf
    !asdfkjhasf
    > 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/<username>/pb_echo
$ cd ${GOPATH}/src/github.com/<username>/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.