From 1219d029b90ed8a31c5c933fcf191b9f6d2113ff Mon Sep 17 00:00:00 2001
From: Oliver Herms <oliver.herms@exaring.de>
Date: Tue, 17 Apr 2018 11:27:32 +0200
Subject: [PATCH] init

---
 AUTHORS                                      |   13 +
 CONTRIBUTING.md                              |   15 +
 CONTRIBUTORS                                 |    7 +
 main.go                                      |   41 +
 net/prefix.go                                |  104 ++
 net/prefix_test.go                           |  334 ++++
 protocols/bgp/packet/bgp.go                  |  147 ++
 protocols/bgp/packet/decoder.go              |  273 ++++
 protocols/bgp/packet/decoder_test.go         | 1536 ++++++++++++++++++
 protocols/bgp/packet/encoder.go              |   45 +
 protocols/bgp/packet/encoder_test.go         |  130 ++
 protocols/bgp/packet/nlri.go                 |   60 +
 protocols/bgp/packet/nlri_test.go            |  129 ++
 protocols/bgp/packet/path_attribute_flags.go |   46 +
 protocols/bgp/packet/path_attributes.go      |  289 ++++
 protocols/bgp/packet/path_attributes_test.go |  759 +++++++++
 protocols/bgp/server/fsm.go                  |  865 ++++++++++
 protocols/bgp/server/peer.go                 |   36 +
 protocols/bgp/server/server.go               |  119 ++
 protocols/bgp/server/sockopt.go              |   31 +
 protocols/bgp/server/tcplistener.go          |   65 +
 protocols/bgp/server/util.go                 |   33 +
 22 files changed, 5077 insertions(+)
 create mode 100644 AUTHORS
 create mode 100644 CONTRIBUTING.md
 create mode 100644 CONTRIBUTORS
 create mode 100644 main.go
 create mode 100644 net/prefix.go
 create mode 100644 net/prefix_test.go
 create mode 100644 protocols/bgp/packet/bgp.go
 create mode 100644 protocols/bgp/packet/decoder.go
 create mode 100644 protocols/bgp/packet/decoder_test.go
 create mode 100644 protocols/bgp/packet/encoder.go
 create mode 100644 protocols/bgp/packet/encoder_test.go
 create mode 100644 protocols/bgp/packet/nlri.go
 create mode 100644 protocols/bgp/packet/nlri_test.go
 create mode 100644 protocols/bgp/packet/path_attribute_flags.go
 create mode 100644 protocols/bgp/packet/path_attributes.go
 create mode 100644 protocols/bgp/packet/path_attributes_test.go
 create mode 100644 protocols/bgp/server/fsm.go
 create mode 100644 protocols/bgp/server/peer.go
 create mode 100644 protocols/bgp/server/server.go
 create mode 100644 protocols/bgp/server/sockopt.go
 create mode 100644 protocols/bgp/server/tcplistener.go
 create mode 100644 protocols/bgp/server/util.go

diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 00000000..61a22aa1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,13 @@
+# This is the official list of bio-routing‚ authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as one of
+#     Organization's name
+#     Individual's name <submission email address>
+#     Individual's name <submission email address> <email2> <emailN>
+# See CONTRIBUTORS for the meaning of multiple email addresses.
+
+# Please keep the list sorted.
+
+Oliver Herms
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..1b082ebe
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,15 @@
+Want to contribute? Great! First, read this page.
+
+### Code guidelines
+We expect all submissions to be properly formatted using gofmt.
+Golint and govet shall not complain about your code. Gocyclo shall not report 
+complexity above 15. And your code should not lower the testcoverage of our
+codebase.
+
+### Code reviews
+All submissions, including submissions by project members, require review. We
+use Github pull requests for this purpose.
+
+### License
+By sending us your code you agree to release your contribution under the projects
+chosen license: Apche License 2.0 (see LICENSE file).
\ No newline at end of file
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 00000000..d9d12494
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,7 @@
+# This is the official list of people who can contribute
+# (and typically have contributed) code to the bio-rd repository.
+# The AUTHORS file lists the copyright holders; this file
+# lists people.  For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+
+Oliver Herms
diff --git a/main.go b/main.go
new file mode 100644
index 00000000..108c326e
--- /dev/null
+++ b/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"fmt"
+	"net"
+	"sync"
+
+	"github.com/sirupsen/logrus"
+
+	"github.com/taktv6/tbgp/config"
+	"github.com/taktv6/tbgp/server"
+)
+
+func main() {
+	fmt.Printf("This is a BGP speaker\n")
+
+	b := server.NewBgpServer()
+
+	err := b.Start(&config.Global{
+		Listen: true,
+	})
+	if err != nil {
+		logrus.Fatalf("Unable to start BGP server: %v", err)
+	}
+
+	b.AddPeer(config.Peer{
+		AdminEnabled: true,
+		LocalAS:      65200,
+		PeerAS:       65201,
+		PeerAddress:  net.IP([]byte{169, 254, 123, 1}),
+		LocalAddress: net.IP([]byte{169, 254, 123, 0}),
+		HoldTimer:    90,
+		KeepAlive:    30,
+		Passive:      true,
+		RouterID:     b.RouterID(),
+	})
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	wg.Wait()
+}
diff --git a/net/prefix.go b/net/prefix.go
new file mode 100644
index 00000000..72ece5d6
--- /dev/null
+++ b/net/prefix.go
@@ -0,0 +1,104 @@
+package net
+
+import (
+	"fmt"
+	"math"
+	"net"
+	"strconv"
+	"strings"
+
+	"github.com/taktv6/tflow2/convert"
+)
+
+// Prefix represents an IPv4 prefix
+type Prefix struct {
+	addr   uint32
+	pfxlen uint8
+}
+
+// NewPfx creates a new Prefix
+func NewPfx(addr uint32, pfxlen uint8) *Prefix {
+	return &Prefix{
+		addr:   addr,
+		pfxlen: pfxlen,
+	}
+}
+
+// StrToAddr converts an IP address string to it's uint32 representation
+func StrToAddr(x string) (uint32, error) {
+	parts := strings.Split(x, ".")
+	if len(parts) != 4 {
+		return 0, fmt.Errorf("Invalid format")
+	}
+
+	ret := uint32(0)
+	for i := 0; i < 4; i++ {
+		y, err := strconv.Atoi(parts[i])
+		if err != nil {
+			return 0, fmt.Errorf("Unable to convert %q to int: %v", parts[i], err)
+		}
+
+		if y > 255 {
+			return 0, fmt.Errorf("%d is too big for a uint8", y)
+		}
+
+		ret += uint32(math.Pow(256, float64(3-i))) * uint32(y)
+	}
+
+	return ret, nil
+}
+
+// Addr returns the address of the prefix
+func (pfx *Prefix) Addr() uint32 {
+	return pfx.addr
+}
+
+// Pfxlen returns the length of the prefix
+func (pfx *Prefix) Pfxlen() uint8 {
+	return pfx.pfxlen
+}
+
+// String returns a string representation of pfx
+func (pfx *Prefix) String() string {
+	return fmt.Sprintf("%s/%d", net.IP(convert.Uint32Byte(pfx.addr)), pfx.pfxlen)
+}
+
+// Contains checks if x is a subnet of or equal to pfx
+func (pfx *Prefix) Contains(x *Prefix) bool {
+	if x.pfxlen <= pfx.pfxlen {
+		return false
+	}
+
+	mask := (uint32(1) << (32 - pfx.pfxlen))
+	return (pfx.addr & mask) == (x.addr & mask)
+}
+
+// Equal checks if pfx and x are equal
+func (pfx *Prefix) Equal(x *Prefix) bool {
+	return *pfx == *x
+}
+
+// GetSupernet gets the next common supernet of pfx and x
+func (pfx *Prefix) GetSupernet(x *Prefix) *Prefix {
+	maxPfxLen := min(pfx.pfxlen, x.pfxlen) - 1
+	a := pfx.addr >> (32 - maxPfxLen)
+	b := x.addr >> (32 - maxPfxLen)
+
+	for i := 0; a != b; i++ {
+		a = a >> 1
+		b = b >> 1
+		maxPfxLen--
+	}
+
+	return &Prefix{
+		addr:   a << (32 - maxPfxLen),
+		pfxlen: maxPfxLen,
+	}
+}
+
+func min(a uint8, b uint8) uint8 {
+	if a < b {
+		return a
+	}
+	return b
+}
diff --git a/net/prefix_test.go b/net/prefix_test.go
new file mode 100644
index 00000000..45fb9028
--- /dev/null
+++ b/net/prefix_test.go
@@ -0,0 +1,334 @@
+package net
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewPfx(t *testing.T) {
+	p := NewPfx(123, 11)
+	if p.addr != 123 || p.pfxlen != 11 {
+		t.Errorf("NewPfx() failed: Unexpected values")
+	}
+}
+
+func TestAddr(t *testing.T) {
+	tests := []struct {
+		name     string
+		pfx      *Prefix
+		expected uint32
+	}{
+		{
+			name:     "Test 1",
+			pfx:      NewPfx(100, 5),
+			expected: 100,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.pfx.Addr()
+		if res != test.expected {
+			t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected)
+		}
+	}
+}
+
+func TestPfxlen(t *testing.T) {
+	tests := []struct {
+		name     string
+		pfx      *Prefix
+		expected uint8
+	}{
+		{
+			name:     "Test 1",
+			pfx:      NewPfx(100, 5),
+			expected: 5,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.pfx.Pfxlen()
+		if res != test.expected {
+			t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected)
+		}
+	}
+}
+
+func TestGetSupernet(t *testing.T) {
+	tests := []struct {
+		name     string
+		a        *Prefix
+		b        *Prefix
+		expected *Prefix
+	}{
+		{
+			name: "Test 1",
+			a: &Prefix{
+				addr:   167772160, // 10.0.0.0/8
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   191134464, // 11.100.123.0/24
+				pfxlen: 24,
+			},
+			expected: &Prefix{
+				addr:   167772160, // 10.0.0.0/7
+				pfxlen: 7,
+			},
+		},
+		{
+			name: "Test 2",
+			a: &Prefix{
+				addr:   167772160, // 10.0.0.0/8
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   3232235520, // 192.168.0.0/24
+				pfxlen: 24,
+			},
+			expected: &Prefix{
+				addr:   0, // 0.0.0.0/0
+				pfxlen: 0,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		s := test.a.GetSupernet(test.b)
+		assert.Equal(t, s, test.expected)
+	}
+}
+
+func TestContains(t *testing.T) {
+	tests := []struct {
+		name     string
+		a        *Prefix
+		b        *Prefix
+		expected bool
+	}{
+		{
+			name: "Test 1",
+			a: &Prefix{
+				addr:   0,
+				pfxlen: 0,
+			},
+			b: &Prefix{
+				addr:   100,
+				pfxlen: 24,
+			},
+			expected: true,
+		},
+		{
+			name: "Test 2",
+			a: &Prefix{
+				addr:   100,
+				pfxlen: 24,
+			},
+			b: &Prefix{
+				addr:   0,
+				pfxlen: 0,
+			},
+			expected: false,
+		},
+		{
+			name: "Test 3",
+			a: &Prefix{
+				addr:   167772160,
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   167772160,
+				pfxlen: 9,
+			},
+			expected: true,
+		},
+		{
+			name: "Test 4",
+			a: &Prefix{
+				addr:   167772160,
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   174391040,
+				pfxlen: 24,
+			},
+			expected: true,
+		},
+		{
+			name: "Test 5",
+			a: &Prefix{
+				addr:   167772160,
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   184549377,
+				pfxlen: 24,
+			},
+			expected: false,
+		},
+		{
+			name: "Test 6",
+			a: &Prefix{
+				addr:   167772160,
+				pfxlen: 8,
+			},
+			b: &Prefix{
+				addr:   191134464,
+				pfxlen: 24,
+			},
+			expected: false,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.a.Contains(test.b)
+		if res != test.expected {
+			t.Errorf("Unexpected result %v for test %s: %s contains %s\n", res, test.name, test.a.String(), test.b.String())
+		}
+	}
+}
+
+func TestMin(t *testing.T) {
+	tests := []struct {
+		name     string
+		a        uint8
+		b        uint8
+		expected uint8
+	}{
+		{
+			name:     "Min 100 200",
+			a:        100,
+			b:        200,
+			expected: 100,
+		},
+		{
+			name:     "Min 200 100",
+			a:        200,
+			b:        100,
+			expected: 100,
+		},
+		{
+			name:     "Min 111 112",
+			a:        111,
+			b:        112,
+			expected: 111,
+		},
+	}
+
+	for _, test := range tests {
+		res := min(test.a, test.b)
+		if res != test.expected {
+			t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected)
+		}
+	}
+}
+
+func TestEqual(t *testing.T) {
+	tests := []struct {
+		name     string
+		a        *Prefix
+		b        *Prefix
+		expected bool
+	}{
+		{
+			name:     "Equal PFXs",
+			a:        NewPfx(100, 8),
+			b:        NewPfx(100, 8),
+			expected: true,
+		},
+		{
+			name:     "Unequal PFXs",
+			a:        NewPfx(100, 8),
+			b:        NewPfx(200, 8),
+			expected: false,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.a.Equal(test.b)
+		if res != test.expected {
+			t.Errorf("Unexpected result for %q: Got %v Expected %v", test.name, res, test.expected)
+		}
+	}
+}
+
+func TestString(t *testing.T) {
+	tests := []struct {
+		name     string
+		pfx      *Prefix
+		expected string
+	}{
+		{
+			name:     "Test 1",
+			pfx:      NewPfx(167772160, 8), // 10.0.0.0/8
+			expected: "10.0.0.0/8",
+		},
+		{
+			name:     "Test 2",
+			pfx:      NewPfx(167772160, 16), // 10.0.0.0/8
+			expected: "10.0.0.0/16",
+		},
+	}
+
+	for _, test := range tests {
+		res := test.pfx.String()
+		if res != test.expected {
+			t.Errorf("Unexpected result for %q: Got %q Expected %q", test.name, res, test.expected)
+		}
+	}
+}
+
+func TestStrToAddr(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		wantFail bool
+		expected uint32
+	}{
+		{
+			name:     "Invalid address #1",
+			input:    "10.10.10",
+			wantFail: true,
+		},
+		{
+			name:     "Invalid address #2",
+			input:    "",
+			wantFail: true,
+		},
+		{
+			name:     "Invalid address #3",
+			input:    "10.10.10.10.10",
+			wantFail: true,
+		},
+		{
+			name:     "Invalid address #4",
+			input:    "10.256.0.0",
+			wantFail: true,
+		},
+		{
+			name:     "Valid address",
+			input:    "10.0.0.0",
+			wantFail: false,
+			expected: 167772160,
+		},
+	}
+
+	for _, test := range tests {
+		res, err := StrToAddr(test.input)
+		if err != nil {
+			if test.wantFail {
+				continue
+			}
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+			continue
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go
new file mode 100644
index 00000000..46be5aed
--- /dev/null
+++ b/protocols/bgp/packet/bgp.go
@@ -0,0 +1,147 @@
+package packet
+
+const (
+	OctetLen    = 8
+	BGP4Version = 4
+
+	MarkerLen = 16
+	HeaderLen = 19
+	MinLen    = 19
+	MaxLen    = 4096
+
+	OpenMsg         = 1
+	UpdateMsg       = 2
+	NotificationMsg = 3
+	KeepaliveMsg    = 4
+
+	MessageHeaderError      = 1
+	OpenMessageError        = 2
+	UpdateMessageError      = 3
+	HoldTimeExpired         = 4
+	FiniteStateMachineError = 5
+	Cease                   = 6
+
+	// Msg Header Errors
+	ConnectionNotSync = 1
+	BadMessageLength  = 2
+	BadMessageType    = 3
+
+	// Open Msg Errors
+	UnsupportedVersionNumber     = 1
+	BadPeerAS                    = 2
+	BadBGPIdentifier             = 3
+	UnsupportedOptionalParameter = 4
+	DeprecatedOpenMsgError5      = 5
+	UnacceptableHoldTime         = 6
+
+	// Update Msg Errors
+	MalformedAttributeList    = 1
+	UnrecognizedWellKnownAttr = 2
+	MissingWellKnonAttr       = 3
+	AttrFlagsError            = 4
+	AttrLengthError           = 5
+	InvalidOriginAttr         = 6
+	DeprecatedUpdateMsgError7 = 7
+	InvalidNextHopAttr        = 8
+	OptionalAttrError         = 9
+	InvalidNetworkField       = 10
+	MalformedASPath           = 11
+
+	// Attribute Type Codes
+	OriginAttr     = 1
+	ASPathAttr     = 2
+	NextHopAttr    = 3
+	MEDAttr        = 4
+	LocalPrefAttr  = 5
+	AtomicAggrAttr = 6
+	AggregatorAttr = 7
+
+	// ORIGIN values
+	IGP        = 0
+	EGP        = 1
+	INCOMPLETE = 2
+
+	// ASPath Segment Types
+	ASSet      = 1
+	ASSequence = 2
+
+	// NOTIFICATION Cease error SubCodes (RFC4486)
+	MaxPrefReached                = 1
+	AdminShut                     = 2
+	PeerDeconfigured              = 3
+	AdminReset                    = 4
+	ConnectionRejected            = 5
+	OtherConfigChange             = 8
+	ConnectionCollisionResolution = 7
+	OutOfResoutces                = 8
+)
+
+type BGPError struct {
+	ErrorCode    uint8
+	ErrorSubCode uint8
+	ErrorStr     string
+}
+
+func (b BGPError) Error() string {
+	return b.ErrorStr
+}
+
+type BGPMessage struct {
+	Header *BGPHeader
+	Body   interface{}
+}
+
+type BGPHeader struct {
+	Length uint16
+	Type   uint8
+}
+
+type BGPOpen struct {
+	Version       uint8
+	AS            uint16
+	HoldTime      uint16
+	BGPIdentifier uint32
+	OptParmLen    uint8
+}
+
+type BGPNotification struct {
+	ErrorCode    uint8
+	ErrorSubcode uint8
+}
+
+type BGPUpdate struct {
+	WithdrawnRoutesLen uint16
+	WithdrawnRoutes    *NLRI
+	TotalPathAttrLen   uint16
+	PathAttributes     *PathAttribute
+	NLRI               *NLRI
+}
+
+type PathAttribute struct {
+	Length         uint16
+	Optional       bool
+	Transitive     bool
+	Partial        bool
+	ExtendedLength bool
+	TypeCode       uint8
+	Value          interface{}
+	Next           *PathAttribute
+}
+
+type NLRI struct {
+	IP     interface{}
+	Pfxlen uint8
+	Next   *NLRI
+}
+
+type ASPath []ASPathSegment
+type ASPathSegment struct {
+	Type  uint8
+	Count uint8
+	ASNs  []uint32
+}
+
+type Aggretator struct {
+	Addr [4]byte
+	ASN  uint16
+}
diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go
new file mode 100644
index 00000000..8ba824ec
--- /dev/null
+++ b/protocols/bgp/packet/decoder.go
@@ -0,0 +1,273 @@
+package packet
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"net"
+
+	"github.com/taktv6/tflow2/convert"
+)
+
+// Decode decodes a BGP message
+func Decode(buf *bytes.Buffer) (*BGPMessage, error) {
+	hdr, err := decodeHeader(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to decode header: %v", err)
+	}
+
+	body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to decode message: %v", err)
+	}
+
+	return &BGPMessage{
+		Header: hdr,
+		Body:   body,
+	}, nil
+}
+
+func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, error) {
+	switch msgType {
+	case OpenMsg:
+		return decodeOpenMsg(buf)
+	case UpdateMsg:
+		return decodeUpdateMsg(buf, l)
+	case KeepaliveMsg:
+		return nil, nil // Nothing to decode in Keepalive message
+	case NotificationMsg:
+		return decodeNotificationMsg(buf)
+	}
+	return nil, fmt.Errorf("Unknown message type: %d", msgType)
+}
+
+func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) {
+	msg := &BGPUpdate{}
+
+	err := decode(buf, []interface{}{&msg.WithdrawnRoutesLen})
+	if err != nil {
+		return msg, err
+	}
+
+	msg.WithdrawnRoutes, err = decodeNLRIs(buf, uint16(msg.WithdrawnRoutesLen))
+	if err != nil {
+		return msg, err
+	}
+
+	err = decode(buf, []interface{}{&msg.TotalPathAttrLen})
+	if err != nil {
+		return msg, err
+	}
+
+	msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen)
+	if err != nil {
+		return msg, err
+	}
+
+	nlriLen := uint16(l) - 4 - uint16(msg.TotalPathAttrLen) - uint16(msg.WithdrawnRoutesLen)
+	if nlriLen > 0 {
+		msg.NLRI, err = decodeNLRIs(buf, nlriLen)
+		if err != nil {
+			return msg, err
+		}
+	}
+
+	return msg, nil
+}
+
+func decodeNotificationMsg(buf *bytes.Buffer) (*BGPNotification, error) {
+	msg := &BGPNotification{}
+
+	fields := []interface{}{
+		&msg.ErrorCode,
+		&msg.ErrorSubcode,
+	}
+
+	err := decode(buf, fields)
+	if err != nil {
+		return msg, err
+	}
+
+	if msg.ErrorCode > Cease {
+		return msg, fmt.Errorf("Invalid error code: %d", msg.ErrorSubcode)
+	}
+
+	switch msg.ErrorCode {
+	case MessageHeaderError:
+		if msg.ErrorSubcode > BadMessageType || msg.ErrorSubcode == 0 {
+			return invalidErrCode(msg)
+		}
+	case OpenMessageError:
+		if msg.ErrorSubcode > UnacceptableHoldTime || msg.ErrorSubcode == 0 || msg.ErrorSubcode == DeprecatedOpenMsgError5 {
+			return invalidErrCode(msg)
+		}
+	case UpdateMessageError:
+		if msg.ErrorSubcode > MalformedASPath || msg.ErrorSubcode == 0 || msg.ErrorSubcode == DeprecatedUpdateMsgError7 {
+			return invalidErrCode(msg)
+		}
+	case HoldTimeExpired:
+		if msg.ErrorSubcode != 0 {
+			return invalidErrCode(msg)
+		}
+	case FiniteStateMachineError:
+		if msg.ErrorSubcode != 0 {
+			return invalidErrCode(msg)
+		}
+	case Cease:
+		if msg.ErrorSubcode != 0 {
+			return invalidErrCode(msg)
+		}
+	default:
+		return invalidErrCode(msg)
+	}
+
+	return msg, nil
+}
+
+func invalidErrCode(n *BGPNotification) (*BGPNotification, error) {
+	return n, fmt.Errorf("Invalid error sub code: %d/%d", n.ErrorCode, n.ErrorSubcode)
+}
+
+func decodeOpenMsg(buf *bytes.Buffer) (*BGPOpen, error) {
+	msg, err := _decodeOpenMsg(buf)
+	return msg.(*BGPOpen), err
+}
+
+func _decodeOpenMsg(buf *bytes.Buffer) (interface{}, error) {
+	msg := &BGPOpen{}
+
+	fields := []interface{}{
+		&msg.Version,
+		&msg.AS,
+		&msg.HoldTime,
+		&msg.BGPIdentifier,
+		&msg.OptParmLen,
+	}
+
+	err := decode(buf, fields)
+	if err != nil {
+		return msg, err
+	}
+
+	err = validateOpen(msg)
+	if err != nil {
+		return nil, err
+	}
+
+	return msg, nil
+}
+
+func validateOpen(msg *BGPOpen) error {
+	if msg.Version != BGP4Version {
+		return BGPError{
+			ErrorCode:    OpenMessageError,
+			ErrorSubCode: UnsupportedVersionNumber,
+			ErrorStr:     fmt.Sprintf("Unsupported version number"),
+		}
+	}
+	if !isValidIdentifier(msg.BGPIdentifier) {
+		return BGPError{
+			ErrorCode:    OpenMessageError,
+			ErrorSubCode: BadBGPIdentifier,
+			ErrorStr:     fmt.Sprintf("Invalid BGP identifier"),
+		}
+	}
+
+	return nil
+}
+
+func isValidIdentifier(id uint32) bool {
+	addr := net.IP(convert.Uint32Byte(id))
+	if addr.IsLoopback() {
+		return false
+	}
+
+	if addr.IsMulticast() {
+		return false
+	}
+
+	if addr[0] == 0 {
+		return false
+	}
+
+	if addr[0] == 255 && addr[1] == 255 && addr[2] == 255 && addr[3] == 255 {
+		return false
+	}
+
+	return true
+}
+
+func decodeHeader(buf *bytes.Buffer) (*BGPHeader, error) {
+	hdr := &BGPHeader{}
+
+	marker := make([]byte, MarkerLen)
+	n, err := buf.Read(marker)
+	if err != nil {
+		return hdr, BGPError{
+			ErrorCode:    Cease,
+			ErrorSubCode: 0,
+			ErrorStr:     fmt.Sprintf("Failed to read from buffer: %v", err.Error()),
+		}
+	}
+
+	if n != MarkerLen {
+		return hdr, BGPError{
+			ErrorCode:    Cease,
+			ErrorSubCode: 0,
+			ErrorStr:     fmt.Sprintf("Unable to read marker"),
+		}
+	}
+
+	for i := range marker {
+		if marker[i] != 255 {
+			return nil, BGPError{
+				ErrorCode:    MessageHeaderError,
+				ErrorSubCode: ConnectionNotSync,
+				ErrorStr:     fmt.Sprintf("Invalid marker: %v", marker),
+			}
+		}
+	}
+
+	fields := []interface{}{
+		&hdr.Length,
+		&hdr.Type,
+	}
+
+	err = decode(buf, fields)
+	if err != nil {
+		return hdr, BGPError{
+			ErrorCode:    Cease,
+			ErrorSubCode: 0,
+			ErrorStr:     fmt.Sprintf("%v", err.Error()),
+		}
+	}
+
+	if hdr.Length < MinLen || hdr.Length > MaxLen {
+		return hdr, BGPError{
+			ErrorCode:    MessageHeaderError,
+			ErrorSubCode: BadMessageLength,
+			ErrorStr:     fmt.Sprintf("Invalid length in BGP header: %v", hdr.Length),
+		}
+	}
+
+	if hdr.Type > KeepaliveMsg || hdr.Type == 0 {
+		return hdr, BGPError{
+			ErrorCode:    MessageHeaderError,
+			ErrorSubCode: BadMessageType,
+			ErrorStr:     fmt.Sprintf("Invalid message type: %d", hdr.Type),
+		}
+	}
+
+	return hdr, nil
+}
+
+func decode(buf *bytes.Buffer, fields []interface{}) error {
+	var err error
+	for _, field := range fields {
+		err = binary.Read(buf, binary.BigEndian, field)
+		if err != nil {
+			return fmt.Errorf("Unable to read from buffer: %v", err)
+		}
+	}
+	return nil
+}
diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go
new file mode 100644
index 00000000..ddb20a2e
--- /dev/null
+++ b/protocols/bgp/packet/decoder_test.go
@@ -0,0 +1,1536 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/taktv6/tflow2/convert"
+)
+
+type test struct {
+	testNum  int
+	input    []byte
+	wantFail bool
+	expected interface{}
+}
+
+type decodeFunc func(*bytes.Buffer) (interface{}, error)
+
+func BenchmarkDecodeUpdateMsg(b *testing.B) {
+	input := []byte{0, 5, 8, 10, 16, 192, 168,
+		0, 53, // Total Path Attribute Length
+
+		255,  // Attribute flags
+		1,    // Attribute Type code (ORIGIN)
+		0, 1, // Length
+		2, // INCOMPLETE
+
+		0,      // Attribute flags
+		2,      // Attribute Type code (AS Path)
+		12,     // Length
+		2,      // Type = AS_SEQUENCE
+		2,      // Path Segement Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+		1,      // Type = AS_SET
+		2,      // Path Segement Length
+		59, 65, // AS15169
+		12, 248, // AS3320
+
+		0,              // Attribute flags
+		3,              // Attribute Type code (Next Hop)
+		4,              // Length
+		10, 11, 12, 13, // Next Hop
+
+		0,          // Attribute flags
+		4,          // Attribute Type code (MED)
+		4,          // Length
+		0, 0, 1, 0, // MED 256
+
+		0,          // Attribute flags
+		5,          // Attribute Type code (Local Pref)
+		4,          // Length
+		0, 0, 1, 0, // Local Pref 256
+
+		0, // Attribute flags
+		6, // Attribute Type code (Atomic Aggregate)
+		0, // Length
+
+		0,    // Attribute flags
+		7,    // Attribute Type code (Atomic Aggregate)
+		6,    // Length
+		1, 2, // ASN
+		10, 11, 12, 13, // Address
+
+		8, 11, // 11.0.0.0/8
+	}
+
+	for i := 0; i < b.N; i++ {
+		buf := bytes.NewBuffer(input)
+		_, err := decodeUpdateMsg(buf, uint16(len(input)))
+		if err != nil {
+			fmt.Printf("decodeUpdateMsg failed: %v\n", err)
+		}
+		//buf.Next(1)
+	}
+}
+
+func TestDecode(t *testing.T) {
+	tests := []test{
+		{
+			// Proper packet
+			testNum: 1,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 19, // Length
+				4, // Type = Keepalive
+
+			},
+			wantFail: false,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 19,
+					Type:   4,
+				},
+			},
+		},
+		{
+			// Invalid marker
+			testNum: 2,
+			input: []byte{
+				1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, // Marker
+				0, 19, // Length
+				4, // Type = Keepalive
+
+			},
+			wantFail: true,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 19,
+					Type:   4,
+				},
+			},
+		},
+		{
+			// Proper NOTIFICATION packet
+			testNum: 3,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 21, // Length
+				3,    // Type = Notification
+				1, 1, // Message Header Error, Connection Not Synchronized.
+			},
+			wantFail: false,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 21,
+					Type:   3,
+				},
+				Body: &BGPNotification{
+					ErrorCode:    1,
+					ErrorSubcode: 1,
+				},
+			},
+		},
+		{
+			// Proper OPEN packet
+			testNum: 4,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 29, // Length
+				1,      // Type = Open
+				4,      // Version
+				0, 200, //ASN,
+				0, 15, // Holdtime
+				10, 20, 30, 40, // BGP Identifier
+				0, // Opt Parm Len
+			},
+			wantFail: false,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 29,
+					Type:   1,
+				},
+				Body: &BGPOpen{
+					Version:       4,
+					AS:            200,
+					HoldTime:      15,
+					BGPIdentifier: uint32(169090600),
+					OptParmLen:    0,
+				},
+			},
+		},
+		{
+			// Incomplete OPEN packet
+			testNum: 5,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 28, // Length
+				1,      // Type = Open
+				4,      // Version
+				0, 200, //ASN,
+				0, 15, // Holdtime
+				0, 0, 0, 100, // BGP Identifier
+			},
+			wantFail: true,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 28,
+					Type:   1,
+				},
+				Body: &BGPOpen{
+					Version:       4,
+					AS:            200,
+					HoldTime:      15,
+					BGPIdentifier: uint32(100),
+				},
+			},
+		},
+		{
+			testNum: 6,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 28, // Length
+				2,                               // Type = Update
+				0, 5, 8, 10, 16, 192, 168, 0, 0, // 2 withdraws
+			},
+			wantFail: false,
+			expected: &BGPMessage{
+				Header: &BGPHeader{
+					Length: 28,
+					Type:   2,
+				},
+				Body: &BGPUpdate{
+					WithdrawnRoutesLen: 5,
+					WithdrawnRoutes: &NLRI{
+						IP:     [4]byte{10, 0, 0, 0},
+						Pfxlen: 8,
+						Next: &NLRI{
+							IP:     [4]byte{192, 168, 0, 0},
+							Pfxlen: 16,
+						},
+					},
+				},
+			},
+		},
+		{
+			testNum: 7,
+			input: []byte{
+				255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker
+				0, 28, // Length
+				5,                               // Type = Invalid
+				0, 5, 8, 10, 16, 192, 168, 0, 0, // Some more stuff
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		msg, err := Decode(buf)
+
+		if err != nil && !test.wantFail {
+			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
+			continue
+		}
+
+		if err == nil && test.wantFail {
+			t.Errorf("Expected error did not happen in test %d", test.testNum)
+			continue
+		}
+
+		if err != nil && test.wantFail {
+			continue
+		}
+
+		if msg == nil {
+			t.Errorf("Unexpected nil result in test %d. Expected: %v", test.testNum, test.expected)
+			continue
+		}
+
+		assert.Equal(t, test.expected, msg)
+	}
+}
+
+func TestDecodeNotificationMsg(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected interface{}
+	}{
+		{
+			name:     "Invalid error code",
+			input:    []byte{0, 0},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid error code #2",
+			input:    []byte{7, 0},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Header)",
+			input:    []byte{1, 0},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Header) #2",
+			input:    []byte{1, 4},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Open)",
+			input:    []byte{2, 0},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Open) #2",
+			input:    []byte{2, 7},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Open) #3",
+			input:    []byte{2, 5},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Update)",
+			input:    []byte{3, 0},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Update) #2",
+			input:    []byte{3, 12},
+			wantFail: true,
+		},
+		{
+			name:     "Invalid ErrSubCode (Update) #3",
+			input:    []byte{3, 7},
+			wantFail: true,
+		},
+		{
+			name:     "Valid Notification",
+			input:    []byte{2, 2},
+			wantFail: false,
+			expected: &BGPNotification{
+				ErrorCode:    2,
+				ErrorSubcode: 2,
+			},
+		},
+		{
+			name:     "Empty input",
+			input:    []byte{},
+			wantFail: true,
+		},
+		{
+			name:     "Hold Timer Expired",
+			input:    []byte{4, 0},
+			wantFail: false,
+			expected: &BGPNotification{
+				ErrorCode:    4,
+				ErrorSubcode: 0,
+			},
+		},
+		{
+			name:     "Hold Timer Expired (invalid subcode)",
+			input:    []byte{4, 1},
+			wantFail: true,
+		},
+		{
+			name:     "FSM Error",
+			input:    []byte{5, 0},
+			wantFail: false,
+			expected: &BGPNotification{
+				ErrorCode:    5,
+				ErrorSubcode: 0,
+			},
+		},
+		{
+			name:     "FSM Error (invalid subcode)",
+			input:    []byte{5, 1},
+			wantFail: true,
+		},
+		{
+			name:     "Cease",
+			input:    []byte{6, 0},
+			wantFail: false,
+			expected: &BGPNotification{
+				ErrorCode:    6,
+				ErrorSubcode: 0,
+			},
+		},
+		{
+			name:     "Cease (invalid subcode)",
+			input:    []byte{6, 1},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		res, err := decodeNotificationMsg(bytes.NewBuffer(test.input))
+
+		if test.wantFail {
+			if err != nil {
+				continue
+			}
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestDecodeUpdateMsg(t *testing.T) {
+	tests := []struct {
+		testNum        int
+		input          []byte
+		explicitLength uint16
+		wantFail       bool
+		expected       interface{}
+	}{
+		{
+			// 2 withdraws only, valid update
+			testNum:  1,
+			input:    []byte{0, 5, 8, 10, 16, 192, 168, 0, 0},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with one path attribute (ORIGIN), valid update
+			testNum: 2,
+			input: []byte{
+				0, 5, // Withdrawn Routes Length
+				8, 10, // 10.0.0.0/8
+				16, 192, 168, // 192.168.0.0/16
+				0, 5, // Total Path Attribute Length
+				255,  // Attribute flags
+				1,    // Attribute Type code
+				0, 1, // Length
+				2, // INCOMPLETE
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 5,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+				},
+			},
+		},
+		{
+			// 2 withdraws with two path attributes (ORIGIN + ASPath), valid update
+			testNum: 3,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 14, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				6,      // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 14,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         6,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with two path attributes (ORIGIN + ASPath), invalid AS Path segment type
+			testNum: 4,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 13, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0, // Attribute flags
+				2, // Attribute Type code (AS Path)
+				6, // Length
+				1, // Type = AS_SET
+				0, // Path Segement Length
+			},
+			wantFail: true,
+		},
+		{
+			// 2 withdraws with two path attributes (ORIGIN + ASPath), invalid AS Path segment member count
+			testNum: 5,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 13, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				6,      // Length
+				3,      // Type = INVALID
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+			},
+			wantFail: true,
+		},
+		{
+			// 2 withdraws with two path attributes (ORIGIN + ASPath), valid update
+			testNum: 6,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 20, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 20,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with 3 path attributes (ORIGIN + ASPath, NH), valid update
+			testNum: 7,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 27, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 27,
+				PathAttributes: &PathAttribute{
+
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+						Next: &PathAttribute{
+							Optional:       false,
+							Transitive:     false,
+							Partial:        false,
+							ExtendedLength: false,
+							Length:         4,
+							TypeCode:       3,
+							Value:          [4]byte{10, 11, 12, 13},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with 4 path attributes (ORIGIN + ASPath, NH, MED), valid update
+			testNum: 8,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 34, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (Next Hop)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 34,
+				PathAttributes: &PathAttribute{
+
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+						Next: &PathAttribute{
+							Optional:       false,
+							Transitive:     false,
+							Partial:        false,
+							ExtendedLength: false,
+							Length:         4,
+							TypeCode:       3,
+							Value:          [4]byte{10, 11, 12, 13},
+							Next: &PathAttribute{
+								Optional:       false,
+								Transitive:     false,
+								Partial:        false,
+								ExtendedLength: false,
+								Length:         4,
+								TypeCode:       4,
+								Value:          uint32(256),
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with 4 path attributes (ORIGIN + ASPath, NH, MED, Local Pref), valid update
+			testNum: 9,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 41, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 41,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+						Next: &PathAttribute{
+							Optional:       false,
+							Transitive:     false,
+							Partial:        false,
+							ExtendedLength: false,
+							Length:         4,
+							TypeCode:       3,
+							Value:          [4]byte{10, 11, 12, 13},
+							Next: &PathAttribute{
+								Optional:       false,
+								Transitive:     false,
+								Partial:        false,
+								ExtendedLength: false,
+								Length:         4,
+								TypeCode:       4,
+								Value:          uint32(256),
+								Next: &PathAttribute{
+									Optional:       false,
+									Transitive:     false,
+									Partial:        false,
+									ExtendedLength: false,
+									Length:         4,
+									TypeCode:       5,
+									Value:          uint32(256),
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with 6 path attributes (ORIGIN, ASPath, NH, MED, Local Pref, Atomi Aggregate), valid update
+			testNum: 9,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 44, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				0, // Attribute flags
+				6, // Attribute Type code (Atomic Aggregate)
+				0, // Length
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 44,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+						Next: &PathAttribute{
+							Optional:       false,
+							Transitive:     false,
+							Partial:        false,
+							ExtendedLength: false,
+							Length:         4,
+							TypeCode:       3,
+							Value:          [4]byte{10, 11, 12, 13},
+							Next: &PathAttribute{
+								Optional:       false,
+								Transitive:     false,
+								Partial:        false,
+								ExtendedLength: false,
+								Length:         4,
+								TypeCode:       4,
+								Value:          uint32(256),
+								Next: &PathAttribute{
+									Optional:       false,
+									Transitive:     false,
+									Partial:        false,
+									ExtendedLength: false,
+									Length:         4,
+									TypeCode:       5,
+									Value:          uint32(256),
+									Next: &PathAttribute{
+										Optional:       false,
+										Transitive:     false,
+										Partial:        false,
+										ExtendedLength: false,
+										Length:         0,
+										TypeCode:       6,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			// 2 withdraws with 7 path attributes (ORIGIN, ASPath, NH, MED, Local Pref, Atomic Aggregate), valid update
+			testNum: 10,
+			input: []byte{0, 5, 8, 10, 16, 192, 168,
+				0, 53, // Total Path Attribute Length
+
+				255,  // Attribute flags
+				1,    // Attribute Type code (ORIGIN)
+				0, 1, // Length
+				2, // INCOMPLETE
+
+				0,      // Attribute flags
+				2,      // Attribute Type code (AS Path)
+				12,     // Length
+				2,      // Type = AS_SEQUENCE
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+				1,      // Type = AS_SET
+				2,      // Path Segement Length
+				59, 65, // AS15169
+				12, 248, // AS3320
+
+				0,              // Attribute flags
+				3,              // Attribute Type code (Next Hop)
+				4,              // Length
+				10, 11, 12, 13, // Next Hop
+
+				0,          // Attribute flags
+				4,          // Attribute Type code (MED)
+				4,          // Length
+				0, 0, 1, 0, // MED 256
+
+				0,          // Attribute flags
+				5,          // Attribute Type code (Local Pref)
+				4,          // Length
+				0, 0, 1, 0, // Local Pref 256
+
+				0, // Attribute flags
+				6, // Attribute Type code (Atomic Aggregate)
+				0, // Length
+
+				0,    // Attribute flags
+				7,    // Attribute Type code (Atomic Aggregate)
+				6,    // Length
+				1, 2, // ASN
+				10, 11, 12, 13, // Address
+
+				8, 11, // 11.0.0.0/8
+			},
+			wantFail: false,
+			expected: &BGPUpdate{
+				WithdrawnRoutesLen: 5,
+				WithdrawnRoutes: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{192, 168, 0, 0},
+						Pfxlen: 16,
+					},
+				},
+				TotalPathAttrLen: 53,
+				PathAttributes: &PathAttribute{
+					Optional:       true,
+					Transitive:     true,
+					Partial:        true,
+					ExtendedLength: true,
+					Length:         1,
+					TypeCode:       1,
+					Value:          uint8(2),
+					Next: &PathAttribute{
+						Optional:       false,
+						Transitive:     false,
+						Partial:        false,
+						ExtendedLength: false,
+						Length:         12,
+						TypeCode:       2,
+						Value: ASPath{
+							{
+								Type:  2,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+							{
+								Type:  1,
+								Count: 2,
+								ASNs: []uint32{
+									15169,
+									3320,
+								},
+							},
+						},
+						Next: &PathAttribute{
+							Optional:       false,
+							Transitive:     false,
+							Partial:        false,
+							ExtendedLength: false,
+							Length:         4,
+							TypeCode:       3,
+							Value:          [4]byte{10, 11, 12, 13},
+							Next: &PathAttribute{
+								Optional:       false,
+								Transitive:     false,
+								Partial:        false,
+								ExtendedLength: false,
+								Length:         4,
+								TypeCode:       4,
+								Value:          uint32(256),
+								Next: &PathAttribute{
+									Optional:       false,
+									Transitive:     false,
+									Partial:        false,
+									ExtendedLength: false,
+									Length:         4,
+									TypeCode:       5,
+									Value:          uint32(256),
+									Next: &PathAttribute{
+										Optional:       false,
+										Transitive:     false,
+										Partial:        false,
+										ExtendedLength: false,
+										Length:         0,
+										TypeCode:       6,
+										Next: &PathAttribute{
+											Optional:       false,
+											Transitive:     false,
+											Partial:        false,
+											ExtendedLength: false,
+											Length:         6,
+											TypeCode:       7,
+											Value: Aggretator{
+												ASN:  uint16(258),
+												Addr: [4]byte{10, 11, 12, 13},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				NLRI: &NLRI{
+					Pfxlen: 8,
+					IP:     [4]byte{11, 0, 0, 0},
+				},
+			},
+		},
+		{
+			testNum: 11, // Incomplete Withdraw
+			input: []byte{
+				0, 5, // Length
+			},
+			wantFail: true,
+		},
+		{
+			testNum:  12, // Empty buffer
+			input:    []byte{},
+			wantFail: true,
+		},
+		{
+			testNum: 13,
+			input: []byte{
+				0, 0, // No Withdraws
+				0, 5, // Total Path Attributes Length
+			},
+			wantFail: true,
+		},
+		{
+			testNum: 14,
+			input: []byte{
+				0, 0, // No Withdraws
+				0, 0, // Total Path Attributes Length
+				24, // Incomplete NLRI
+			},
+			wantFail: true,
+		},
+		{
+			testNum: 15, // Cut at Total Path Attributes Length
+			input: []byte{
+				0, 0, // No Withdraws
+			},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		l := test.explicitLength
+		if l == 0 {
+			l = uint16(len(test.input))
+		}
+		msg, err := decodeUpdateMsg(buf, l)
+
+		if err != nil && !test.wantFail {
+			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
+			continue
+		}
+
+		if err == nil && test.wantFail {
+			t.Errorf("Expected error did not happen in test %d", test.testNum)
+			continue
+		}
+
+		if err != nil && test.wantFail {
+			continue
+		}
+
+		assert.Equal(t, test.expected, msg)
+	}
+}
+
+func TestDecodeMsgBody(t *testing.T) {
+	tests := []struct {
+		name     string
+		buffer   *bytes.Buffer
+		msgType  uint8
+		length   uint16
+		wantFail bool
+		expected interface{}
+	}{
+		{
+			name:     "Unknown msgType",
+			msgType:  5,
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		res, err := decodeMsgBody(test.buffer, test.msgType, test.length)
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error dit not happen in test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected error in test %q: %v", test.name, err)
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestDecodeOpenMsg(t *testing.T) {
+	tests := []test{
+		{
+			// Valid message
+			testNum:  1,
+			input:    []byte{4, 1, 1, 0, 15, 10, 20, 30, 40, 0},
+			wantFail: false,
+			expected: &BGPOpen{
+				Version:       4,
+				AS:            257,
+				HoldTime:      15,
+				BGPIdentifier: 169090600,
+				OptParmLen:    0,
+			},
+		},
+		{
+			// Invalid Version
+			testNum:  2,
+			input:    []byte{3, 1, 1, 0, 15, 10, 10, 10, 11, 0},
+			wantFail: true,
+		},
+	}
+
+	genericTest(_decodeOpenMsg, tests, t)
+}
+
+func TestDecodeHeader(t *testing.T) {
+	tests := []test{
+		{
+			// Valid header
+			testNum:  1,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, KeepaliveMsg},
+			wantFail: false,
+			expected: &BGPHeader{
+				Length: 19,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Invalid length too short
+			testNum:  2,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 18, KeepaliveMsg},
+			wantFail: true,
+			expected: &BGPHeader{
+				Length: 18,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Invalid length too long
+			testNum:  3,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 16, 1, KeepaliveMsg},
+			wantFail: true,
+			expected: &BGPHeader{
+				Length: 18,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Invalid message type 5
+			testNum:  4,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 5},
+			wantFail: true,
+			expected: &BGPHeader{
+				Length: 19,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Invalid message type 0
+			testNum:  5,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 0},
+			wantFail: true,
+			expected: &BGPHeader{
+				Length: 19,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Invalid marker
+			testNum:  6,
+			input:    []byte{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 19, KeepaliveMsg},
+			wantFail: true,
+			expected: &BGPHeader{
+				Length: 19,
+				Type:   KeepaliveMsg,
+			},
+		},
+		{
+			// Incomplete Marker
+			testNum:  7,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
+			wantFail: true,
+		},
+		{
+			// Incomplete Header
+			testNum:  8,
+			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19},
+			wantFail: true,
+		},
+		{
+			// Empty input
+			testNum:  9,
+			input:    []byte{},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		res, err := decodeHeader(buf)
+
+		if err != nil {
+			if test.wantFail {
+				continue
+			}
+			t.Errorf("Unexpected failure for test %d: %v", test.testNum, err)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success fo test %d", test.testNum)
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func genericTest(f decodeFunc, tests []test, t *testing.T) {
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		msg, err := f(buf)
+
+		if err != nil && !test.wantFail {
+			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
+			continue
+		}
+
+		if err == nil && test.wantFail {
+			t.Errorf("Expected error did not happen in test %d", test.testNum)
+			continue
+		}
+
+		if err != nil && test.wantFail {
+			continue
+		}
+
+		if msg == nil {
+			t.Errorf("Unexpected nil result in test %d. Expected: %v", test.testNum, test.expected)
+			continue
+		}
+
+		assert.Equal(t, test.expected, msg)
+	}
+}
+
+func TestIsValidIdentifier(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    uint32
+		expected bool
+	}{
+		{
+			name:     "Valid #1",
+			input:    convert.Uint32b([]byte{8, 8, 8, 8}),
+			expected: true,
+		},
+		{
+			name:     "Multicast",
+			input:    convert.Uint32b([]byte{239, 8, 8, 8}),
+			expected: false,
+		},
+		{
+			name:     "Loopback",
+			input:    convert.Uint32b([]byte{127, 8, 8, 8}),
+			expected: false,
+		},
+		{
+			name:     "First byte 0",
+			input:    convert.Uint32b([]byte{0, 8, 8, 8}),
+			expected: false,
+		},
+		{
+			name:     "All bytes 255",
+			input:    convert.Uint32b([]byte{255, 255, 255, 255}),
+			expected: false,
+		},
+	}
+
+	for _, test := range tests {
+		res := isValidIdentifier(test.input)
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestValidateOpenMessage(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *BGPOpen
+		wantFail bool
+	}{
+		{
+			name: "Valid #1",
+			input: &BGPOpen{
+				Version:       4,
+				BGPIdentifier: convert.Uint32b([]byte{8, 8, 8, 8}),
+			},
+			wantFail: false,
+		},
+		{
+			name: "Invalid Identifier",
+			input: &BGPOpen{
+				Version:       4,
+				BGPIdentifier: convert.Uint32b([]byte{0, 8, 8, 8}),
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		res := validateOpen(test.input)
+
+		if res != nil {
+			if test.wantFail {
+				continue
+			}
+			t.Errorf("Unexpected failure for test %q: %v", test.name, res)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+		}
+	}
+}
diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go
new file mode 100644
index 00000000..e7c98c6f
--- /dev/null
+++ b/protocols/bgp/packet/encoder.go
@@ -0,0 +1,45 @@
+package packet
+
+import (
+	"bytes"
+
+	"github.com/taktv6/tflow2/convert"
+)
+
+func SerializeKeepaliveMsg() []byte {
+	keepaliveLen := uint16(19)
+	buf := bytes.NewBuffer(make([]byte, 0, keepaliveLen))
+	serializeHeader(buf, keepaliveLen, KeepaliveMsg)
+
+	return buf.Bytes()
+}
+
+func SerializeNotificationMsg(msg *BGPNotification) []byte {
+	notificationLen := uint16(21)
+	buf := bytes.NewBuffer(make([]byte, 0, notificationLen))
+	serializeHeader(buf, notificationLen, NotificationMsg)
+	buf.WriteByte(msg.ErrorCode)
+	buf.WriteByte(msg.ErrorSubcode)
+
+	return buf.Bytes()
+}
+
+func SerializeOpenMsg(msg *BGPOpen) []byte {
+	openLen := uint16(29)
+	buf := bytes.NewBuffer(make([]byte, 0, openLen))
+	serializeHeader(buf, openLen, OpenMsg)
+
+	buf.WriteByte(msg.Version)
+	buf.Write(convert.Uint16Byte(msg.AS))
+	buf.Write(convert.Uint16Byte(msg.HoldTime))
+	buf.Write(convert.Uint32Byte(msg.BGPIdentifier))
+	buf.WriteByte(uint8(0))
+
+	return buf.Bytes()
+}
+
+func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) {
+	buf.Write([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
+	buf.Write(convert.Uint16Byte(length))
+	buf.WriteByte(typ)
+}
diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go
new file mode 100644
index 00000000..eccfdfdc
--- /dev/null
+++ b/protocols/bgp/packet/encoder_test.go
@@ -0,0 +1,130 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/taktv6/tflow2/convert"
+)
+
+func TestSerializeKeepaliveMsg(t *testing.T) {
+	expected := []byte{
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0x00, 0x13, 0x04,
+	}
+	res := SerializeKeepaliveMsg()
+
+	assert.Equal(t, expected, res)
+}
+
+func TestSerializeNotificationMsg(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *BGPNotification
+		expected []byte
+	}{
+		{
+			name: "Valid #1",
+			input: &BGPNotification{
+				ErrorCode:    10,
+				ErrorSubcode: 5,
+			},
+			expected: []byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0x00, 0x15, // Length
+				0x03, // Type
+				0x0a, // Error Code
+				0x05, // Error Subcode
+			},
+		},
+		{
+			name: "Valid #2",
+			input: &BGPNotification{
+				ErrorCode:    11,
+				ErrorSubcode: 6,
+			},
+			expected: []byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0x00, 0x15, // Length
+				0x03, // Type
+				0x0b, // Error Code
+				0x06, // Error Subcode
+			},
+		},
+	}
+
+	for _, test := range tests {
+		res := SerializeNotificationMsg(test.input)
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestSerializeOpenMsg(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *BGPOpen
+		expected []byte
+	}{
+		{
+			name: "Valid #1",
+			input: &BGPOpen{
+				Version:       4,
+				AS:            15169,
+				HoldTime:      120,
+				BGPIdentifier: convert.Uint32([]byte{100, 111, 120, 130}),
+				OptParmLen:    0,
+			},
+			expected: []byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0x00, 0x1d, // Length
+				0x01,       // Type
+				0x04,       // Version
+				0x3b, 0x41, // ASN
+				0x00, 0x78, // Holdtime
+				130, 120, 111, 100, // BGP Identifier
+				0x00, // Opt. Param Length
+			},
+		},
+	}
+
+	for _, test := range tests {
+		res := SerializeOpenMsg(test.input)
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestSerializeHeader(t *testing.T) {
+	tests := []struct {
+		name     string
+		length   uint16
+		typ      uint8
+		expected []byte
+	}{
+		{
+			name:   "Valid #1",
+			length: 10,
+			typ:    5,
+			expected: []byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0x00, 0x0a, 0x05,
+			},
+		},
+		{
+			name:   "Valid #12",
+			length: 256,
+			typ:    255,
+			expected: []byte{
+				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+				0x01, 0x00, 0xff,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer([]byte{})
+		serializeHeader(buf, test.length, test.typ)
+
+		assert.Equal(t, test.expected, buf.Bytes())
+	}
+}
diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go
new file mode 100644
index 00000000..1f478fc1
--- /dev/null
+++ b/protocols/bgp/packet/nlri.go
@@ -0,0 +1,60 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+	"net"
+)
+
+func decodeNLRIs(buf *bytes.Buffer, length uint16) (*NLRI, error) {
+	var ret *NLRI
+	var eol *NLRI
+	var nlri *NLRI
+	var err error
+	var consumed uint8
+	p := uint16(0)
+
+	for p < length {
+		nlri, consumed, err = decodeNLRI(buf)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode NLRI: %v", err)
+		}
+		p += uint16(consumed)
+
+		if ret == nil {
+			ret = nlri
+			eol = nlri
+			continue
+		}
+
+		eol.Next = nlri
+		eol = nlri
+	}
+
+	return ret, nil
+}
+
+func decodeNLRI(buf *bytes.Buffer) (*NLRI, uint8, error) {
+	var addr [4]byte
+	nlri := &NLRI{}
+
+	err := decode(buf, []interface{}{&nlri.Pfxlen})
+	if err != nil {
+		return nil, 0, err
+	}
+
+	toCopy := uint8(math.Ceil(float64(nlri.Pfxlen) / float64(OctetLen)))
+	for i := uint8(0); i < net.IPv4len%OctetLen; i++ {
+		if i < toCopy {
+			err := decode(buf, []interface{}{&addr[i]})
+			if err != nil {
+				return nil, 0, err
+			}
+		} else {
+			addr[i] = 0
+		}
+	}
+	nlri.IP = addr
+	return nlri, toCopy + 1, nil
+}
diff --git a/protocols/bgp/packet/nlri_test.go b/protocols/bgp/packet/nlri_test.go
new file mode 100644
index 00000000..e7251e41
--- /dev/null
+++ b/protocols/bgp/packet/nlri_test.go
@@ -0,0 +1,129 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeNLRIs(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *NLRI
+	}{
+		{
+			name: "Valid NRLI #1",
+			input: []byte{
+				24, 192, 168, 0,
+				8, 10,
+				17, 172, 16, 0,
+			},
+			wantFail: false,
+			expected: &NLRI{
+				IP:     [4]byte{192, 168, 0, 0},
+				Pfxlen: 24,
+				Next: &NLRI{
+					IP:     [4]byte{10, 0, 0, 0},
+					Pfxlen: 8,
+					Next: &NLRI{
+						IP:     [4]byte{172, 16, 0, 0},
+						Pfxlen: 17,
+					},
+				},
+			},
+		},
+		{
+			name: "Invalid NRLI #1",
+			input: []byte{
+				24, 192, 168, 0,
+				8, 10,
+				17, 172, 16,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		res, err := decodeNLRIs(buf, uint16(len(test.input)))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestDecodeNLRI(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *NLRI
+	}{
+		{
+			name: "Valid NRLI #1",
+			input: []byte{
+				24, 192, 168, 0,
+			},
+			wantFail: false,
+			expected: &NLRI{
+				IP:     [4]byte{192, 168, 0, 0},
+				Pfxlen: 24,
+			},
+		},
+		{
+			name: "Valid NRLI #2",
+			input: []byte{
+				25, 192, 168, 0, 128,
+			},
+			wantFail: false,
+			expected: &NLRI{
+				IP:     [4]byte{192, 168, 0, 128},
+				Pfxlen: 25,
+			},
+		},
+		{
+			name: "Incomplete NLRI #1",
+			input: []byte{
+				25, 192, 168, 0,
+			},
+			wantFail: true,
+		},
+		{
+			name: "Incomplete NLRI #2",
+			input: []byte{
+				25,
+			},
+			wantFail: true,
+		},
+		{
+			name:     "Empty input",
+			input:    []byte{},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		res, _, err := decodeNLRI(buf)
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
diff --git a/protocols/bgp/packet/path_attribute_flags.go b/protocols/bgp/packet/path_attribute_flags.go
new file mode 100644
index 00000000..8beca7ac
--- /dev/null
+++ b/protocols/bgp/packet/path_attribute_flags.go
@@ -0,0 +1,46 @@
+package packet
+
+import "bytes"
+
+func decodePathAttrFlags(buf *bytes.Buffer, pa *PathAttribute) error {
+	flags := uint8(0)
+	err := decode(buf, []interface{}{&flags})
+	if err != nil {
+		return err
+	}
+
+	pa.Optional = isOptional(flags)
+	pa.Transitive = isTransitive(flags)
+	pa.Partial = isPartial(flags)
+	pa.ExtendedLength = isExtendedLength(flags)
+
+	return nil
+}
+
+func isOptional(x uint8) bool {
+	if x&128 == 128 {
+		return true
+	}
+	return false
+}
+
+func isTransitive(x uint8) bool {
+	if x&64 == 64 {
+		return true
+	}
+	return false
+}
+
+func isPartial(x uint8) bool {
+	if x&32 == 32 {
+		return true
+	}
+	return false
+}
+
+func isExtendedLength(x uint8) bool {
+	if x&16 == 16 {
+		return true
+	}
+	return false
+}
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
new file mode 100644
index 00000000..b00f1599
--- /dev/null
+++ b/protocols/bgp/packet/path_attributes.go
@@ -0,0 +1,289 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) {
+	var ret *PathAttribute
+	var eol *PathAttribute
+	var pa *PathAttribute
+	var err error
+	var consumed uint16
+
+	p := uint16(0)
+	for p < tpal {
+		pa, consumed, err = decodePathAttr(buf)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode path attr: %v", err)
+		}
+		p += consumed
+
+		if ret == nil {
+			ret = pa
+			eol = pa
+		} else {
+			eol.Next = pa
+			eol = pa
+		}
+	}
+
+	return ret, nil
+}
+
+func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err error) {
+	pa = &PathAttribute{}
+
+	err = decodePathAttrFlags(buf, pa)
+	if err != nil {
+		return nil, consumed, fmt.Errorf("Unable to get path attribute flags: %v", err)
+	}
+	consumed++
+
+	err = decode(buf, []interface{}{&pa.TypeCode})
+	if err != nil {
+		return nil, consumed, err
+	}
+	consumed++
+
+	n, err := pa.setLength(buf)
+	if err != nil {
+		return nil, consumed, err
+	}
+	consumed += uint16(n)
+
+	switch pa.TypeCode {
+	case OriginAttr:
+		if err := pa.decodeOrigin(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode Origin: %v", err)
+		}
+	case ASPathAttr:
+		if err := pa.decodeASPath(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode AS Path: %v", err)
+		}
+	case NextHopAttr:
+		if err := pa.decodeNextHop(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode Next-Hop: %v", err)
+		}
+	case MEDAttr:
+		if err := pa.decodeMED(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode MED: %v", err)
+		}
+	case LocalPrefAttr:
+		if err := pa.decodeLocalPref(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode local pref: %v", err)
+		}
+	case AggregatorAttr:
+		if err := pa.decodeAggregator(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode Aggregator: %v", err)
+		}
+	case AtomicAggrAttr:
+		// Nothing to do for 0 octet long attribute
+	default:
+		return nil, consumed, fmt.Errorf("Invalid Attribute Type Code: %v", pa.TypeCode)
+	}
+
+	return pa, consumed + pa.Length, nil
+}
+
+func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error {
+	origin := uint8(0)
+
+	p := uint16(0)
+	err := decode(buf, []interface{}{&origin})
+	if err != nil {
+		return fmt.Errorf("Unable to decode: %v", err)
+	}
+
+	pa.Value = origin
+	p++
+
+	return dumpNBytes(buf, pa.Length-p)
+}
+
+func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error {
+	pa.Value = make(ASPath, 0)
+
+	p := uint16(0)
+	for p < pa.Length {
+		segment := ASPathSegment{
+			ASNs: make([]uint32, 0),
+		}
+
+		err := decode(buf, []interface{}{&segment.Type, &segment.Count})
+		if err != nil {
+			return err
+		}
+		p += 2
+
+		if segment.Type != ASSet && segment.Type != ASSequence {
+			return fmt.Errorf("Invalid AS Path segment type: %d", segment.Type)
+		}
+
+		if segment.Count == 0 {
+			return fmt.Errorf("Invalid AS Path segment length: %d", segment.Count)
+		}
+
+		for i := uint8(0); i < segment.Count; i++ {
+			asn := uint16(0)
+
+			err := decode(buf, []interface{}{&asn})
+			if err != nil {
+				return err
+			}
+			p += 2
+
+			segment.ASNs = append(segment.ASNs, uint32(asn))
+		}
+		pa.Value = append(pa.Value.(ASPath), segment)
+	}
+
+	return nil
+}
+
+func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error {
+	addr := [4]byte{}
+
+	p := uint16(0)
+	n, err := buf.Read(addr[:])
+	if err != nil {
+		return err
+	}
+	if n != 4 {
+		return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n)
+	}
+
+	pa.Value = addr
+	p += 4
+
+	return dumpNBytes(buf, pa.Length-p)
+}
+
+func (pa *PathAttribute) decodeMED(buf *bytes.Buffer) error {
+	med, err := pa.decodeUint32(buf)
+	if err != nil {
+		return fmt.Errorf("Unable to recode local pref: %v", err)
+	}
+
+	pa.Value = uint32(med)
+	return nil
+}
+
+func (pa *PathAttribute) decodeLocalPref(buf *bytes.Buffer) error {
+	lpref, err := pa.decodeUint32(buf)
+	if err != nil {
+		return fmt.Errorf("Unable to recode local pref: %v", err)
+	}
+
+	pa.Value = uint32(lpref)
+	return nil
+}
+
+func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error {
+	aggr := Aggretator{}
+
+	p := uint16(0)
+	err := decode(buf, []interface{}{&aggr.ASN})
+	if err != nil {
+		return err
+	}
+	p += 2
+
+	n, err := buf.Read(aggr.Addr[:])
+	if err != nil {
+		return err
+	}
+	if n != 4 {
+		return fmt.Errorf("Unable to read aggregator IP: buf.Read read %d bytes", n)
+	}
+	p += 4
+
+	pa.Value = aggr
+	return dumpNBytes(buf, pa.Length-p)
+}
+
+func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) {
+	bytesRead := 0
+	if pa.ExtendedLength {
+		err := decode(buf, []interface{}{&pa.Length})
+		if err != nil {
+			return 0, err
+		}
+		bytesRead = 2
+	} else {
+		x := uint8(0)
+		err := decode(buf, []interface{}{&x})
+		if err != nil {
+			return 0, err
+		}
+		pa.Length = uint16(x)
+		bytesRead = 1
+	}
+	return bytesRead, nil
+}
+
+func (pa *PathAttribute) decodeUint32(buf *bytes.Buffer) (uint32, error) {
+	var v uint32
+
+	p := uint16(0)
+	err := decode(buf, []interface{}{&v})
+	if err != nil {
+		return 0, err
+	}
+
+	p += 4
+	err = dumpNBytes(buf, pa.Length-p)
+	if err != nil {
+		return 0, fmt.Errorf("dumpNBytes failed: %v", err)
+	}
+
+	return v, nil
+}
+
+func (pa *PathAttribute) ASPathString() (ret string) {
+	for _, p := range *pa.Value.(*ASPath) {
+		if p.Type == ASSet {
+			ret += " ("
+		}
+		n := len(p.ASNs)
+		for i, asn := range p.ASNs {
+			if i < n-1 {
+				ret += fmt.Sprintf("%d ", asn)
+				continue
+			}
+			ret += fmt.Sprintf("%d", asn)
+		}
+		if p.Type == ASSet {
+			ret += ")"
+		}
+	}
+
+	return
+}
+
+func (pa *PathAttribute) ASPathLen() (ret uint16) {
+	for _, p := range *pa.Value.(*ASPath) {
+		if p.Type == ASSet {
+			ret++
+			continue
+		}
+		ret += uint16(len(p.ASNs))
+	}
+
+	return
+}
+
+// dumpNBytes is used to dump n bytes of buf. This is useful in case an path attributes
+// length doesn't match a fixed length's attributes length (e.g. ORIGIN is always an octet)
+func dumpNBytes(buf *bytes.Buffer, n uint16) error {
+	if n <= 0 {
+		return nil
+	}
+	dump := make([]byte, n)
+	err := decode(buf, []interface{}{&dump})
+	if err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
new file mode 100644
index 00000000..ee4d0fc3
--- /dev/null
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -0,0 +1,759 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodePathAttrs(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *PathAttribute
+	}{
+		{
+			name: "Valid attribute set",
+			input: []byte{
+				0,              // Attr. Flags
+				1,              // Attr. Type Code
+				1,              // Attr. Length
+				1,              // EGP
+				0,              // Attr. Flags
+				3,              // Next Hop
+				4,              // Attr. Length
+				10, 20, 30, 40, // IP-Address
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				TypeCode: 1,
+				Length:   1,
+				Value:    uint8(1),
+				Next: &PathAttribute{
+					TypeCode: 3,
+					Length:   4,
+					Value:    [4]byte{10, 20, 30, 40},
+				},
+			},
+		},
+		{
+			name: "Incomplete data",
+			input: []byte{
+				0, // Attr. Flags
+				1, // Attr. Type Code
+				1, // Attr. Length
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestDecodePathAttr(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *PathAttribute
+	}{
+		{
+			name: "Valid origin",
+			input: []byte{
+				0, // Attr. Flags
+				1, // Attr. Type Code
+				1, // Attr. Length
+				1, // EGP
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length:         1,
+				Optional:       false,
+				Transitive:     false,
+				Partial:        false,
+				ExtendedLength: false,
+				TypeCode:       OriginAttr,
+				Value:          uint8(1),
+			},
+		},
+		{
+			name: "Missing TypeCode",
+			input: []byte{
+				0, // Attr. Flags
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing Length",
+			input: []byte{
+				0, // Attr. Flags
+				1, // Attr. Type Code
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing Value ORIGIN",
+			input: []byte{
+				0, // Attr. Flags
+				1, // Attr. Type Code
+				1, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing value AS_PATH",
+			input: []byte{
+				0, // Attr. Flags
+				2, // Attr. Type Code
+				8, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing value NextHop",
+			input: []byte{
+				0, // Attr. Flags
+				3, // Attr. Type Code
+				4, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing value MED",
+			input: []byte{
+				0, // Attr. Flags
+				4, // Attr. Type Code
+				4, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing value LocalPref",
+			input: []byte{
+				0, // Attr. Flags
+				5, // Attr. Type Code
+				4, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing value AGGREGATOR",
+			input: []byte{
+				0, // Attr. Flags
+				7, // Attr. Type Code
+				4, // Attr. Length
+			},
+			wantFail: true,
+		},
+		{
+			name: "Not supported attribute",
+			input: []byte{
+				0,   // Attr. Flags
+				111, // Attr. Type Code
+				4,   // Attr. Length
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		res, _, err := decodePathAttr(bytes.NewBuffer(test.input))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestDecodeOrigin(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		wantFail bool
+		expected *PathAttribute
+	}{
+		{
+			name: "Test #1",
+			input: []byte{
+				0, // Origin: IGP
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Value:  uint8(IGP),
+				Length: 1,
+			},
+		},
+		{
+			name: "Test #2",
+			input: []byte{
+				1, // Origin: EGP
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Value:  uint8(EGP),
+				Length: 1,
+			},
+		},
+		{
+			name: "Test #3",
+			input: []byte{
+				2, // Origin: INCOMPLETE
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Value:  uint8(INCOMPLETE),
+				Length: 1,
+			},
+		},
+		{
+			name:     "Test #4",
+			input:    []byte{},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		pa := &PathAttribute{
+			Length: uint16(len(test.input)),
+		}
+		err := pa.decodeOrigin(bytes.NewBuffer(test.input))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		if err != nil {
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestDecodeASPath(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name: "Test #1",
+			input: []byte{
+				2, // AS_SEQUENCE
+				4, // Path Length
+				0, 100, 0, 200, 0, 222, 0, 240,
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 10,
+				Value: ASPath{
+					ASPathSegment{
+						Type:  2,
+						Count: 4,
+						ASNs: []uint32{
+							100, 200, 222, 240,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "Test #2",
+			input: []byte{
+				1, // AS_SEQUENCE
+				3, // Path Length
+				0, 100, 0, 222, 0, 240,
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 8,
+				Value: ASPath{
+					ASPathSegment{
+						Type:  1,
+						Count: 3,
+						ASNs: []uint32{
+							100, 222, 240,
+						},
+					},
+				},
+			},
+		},
+		{
+			name:           "Empty input",
+			input:          []byte{},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+		{
+			name: "Incomplete AS_PATH",
+			input: []byte{
+				1, // AS_SEQUENCE
+				3, // Path Length
+				0, 100, 0, 222,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength != 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		err := pa.decodeASPath(bytes.NewBuffer(test.input))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		if err != nil {
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestDecodeNextHop(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name: "Test #1",
+			input: []byte{
+				10, 20, 30, 40,
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 4,
+				Value: [4]byte{
+					10, 20, 30, 40,
+				},
+			},
+		},
+		{
+			name:           "Test #2",
+			input:          []byte{},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+		{
+			name:           "Incomplete IP-Address",
+			input:          []byte{10, 20, 30},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength != 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		err := pa.decodeNextHop(bytes.NewBuffer(test.input))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		if err != nil {
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestDecodeMED(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name: "Test #1",
+			input: []byte{
+				0, 0, 3, 232,
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 4,
+				Value:  uint32(1000),
+			},
+		},
+		{
+			name:           "Test #2",
+			input:          []byte{},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength != 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		err := pa.decodeMED(bytes.NewBuffer(test.input))
+
+		if test.wantFail && err == nil {
+			t.Errorf("Expected error did not happen for test %q", test.name)
+		}
+
+		if !test.wantFail && err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+		}
+
+		if err != nil {
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestDecodeLocalPref(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name: "Test #1",
+			input: []byte{
+				0, 0, 3, 232,
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 4,
+				Value:  uint32(1000),
+			},
+		},
+		{
+			name:           "Test #2",
+			input:          []byte{},
+			explicitLength: 5,
+			wantFail:       true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength != 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		err := pa.decodeLocalPref(bytes.NewBuffer(test.input))
+
+		if test.wantFail {
+			if err != nil {
+				continue
+			}
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestDecodeAggregator(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name: "Valid aggregator",
+			input: []byte{
+				0, 222, // ASN
+				10, 20, 30, 40, // Aggregator IP
+			},
+			wantFail: false,
+			expected: &PathAttribute{
+				Length: 6,
+				Value: Aggretator{
+					ASN:  222,
+					Addr: [4]byte{10, 20, 30, 40},
+				},
+			},
+		},
+		{
+			name: "Incomplete Address",
+			input: []byte{
+				0, 222, // ASN
+				10, 20, // Aggregator IP
+			},
+			wantFail: true,
+		},
+		{
+			name: "Missing Address",
+			input: []byte{
+				0, 222, // ASN
+			},
+			wantFail: true,
+		},
+		{
+			name:     "Empty input",
+			input:    []byte{},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength != 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		err := pa.decodeAggregator(bytes.NewBuffer(test.input))
+
+		if test.wantFail {
+			if err != nil {
+				continue
+			}
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+	}
+}
+
+func TestSetLength(t *testing.T) {
+	tests := []struct {
+		name             string
+		input            []byte
+		ExtendedLength   bool
+		wantFail         bool
+		expected         *PathAttribute
+		expectedConsumed int
+	}{
+		{
+			name:           "Valid input",
+			ExtendedLength: false,
+			input:          []byte{22},
+			expected: &PathAttribute{
+				ExtendedLength: false,
+				Length:         22,
+			},
+			expectedConsumed: 1,
+		},
+		{
+			name:           "Valid input (extended)",
+			ExtendedLength: true,
+			input:          []byte{1, 1},
+			expected: &PathAttribute{
+				ExtendedLength: true,
+				Length:         257,
+			},
+			expectedConsumed: 2,
+		},
+		{
+			name:           "Invalid input",
+			ExtendedLength: true,
+			input:          []byte{},
+			wantFail:       true,
+		},
+		{
+			name:           "Invalid input (extended)",
+			ExtendedLength: true,
+			input:          []byte{1},
+			wantFail:       true,
+		},
+	}
+
+	for _, test := range tests {
+		pa := &PathAttribute{
+			ExtendedLength: test.ExtendedLength,
+		}
+		consumed, err := pa.setLength(bytes.NewBuffer(test.input))
+
+		if test.wantFail {
+			if err != nil {
+				continue
+			}
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, pa)
+		assert.Equal(t, test.expectedConsumed, consumed)
+	}
+}
+
+func TestDecodeUint32(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       uint32
+	}{
+		{
+			name:     "Valid input",
+			input:    []byte{0, 0, 1, 1},
+			expected: 257,
+		},
+		{
+			name:     "Valid input with additional crap",
+			input:    []byte{0, 0, 1, 1, 200},
+			expected: 257,
+		},
+		{
+			name:           "Valid input with additional crap and invalid length",
+			input:          []byte{0, 0, 1, 1, 200},
+			explicitLength: 8,
+			wantFail:       true,
+		},
+		{
+			name:     "Invalid input",
+			input:    []byte{0, 0, 1},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		l := uint16(len(test.input))
+		if test.explicitLength > 0 {
+			l = test.explicitLength
+		}
+		pa := &PathAttribute{
+			Length: l,
+		}
+		res, err := pa.decodeUint32(bytes.NewBuffer(test.input))
+
+		if test.wantFail {
+			if err != nil {
+				continue
+			}
+			t.Errorf("Expected error did not happen for test %q", test.name)
+			continue
+		}
+
+		if err != nil {
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestASPathString(t *testing.T) {
+	tests := []struct {
+		name     string
+		pa       *PathAttribute
+		expected string
+	}{
+		{
+			name: "Test #1",
+			pa: &PathAttribute{
+				Value: &ASPath{
+					{
+						Type: ASSequence,
+						ASNs: []uint32{10, 20, 30},
+					},
+				},
+			},
+			expected: "10 20 30",
+		},
+		{
+			name: "Test #2",
+			pa: &PathAttribute{
+				Value: &ASPath{
+					{
+						Type: ASSequence,
+						ASNs: []uint32{10, 20, 30},
+					},
+					{
+						Type: ASSet,
+						ASNs: []uint32{200, 300},
+					},
+				},
+			},
+			expected: "10 20 30 (200 300)",
+		},
+	}
+
+	for _, test := range tests {
+		res := test.pa.ASPathString()
+		assert.Equal(t, test.expected, res)
+	}
+}
diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go
new file mode 100644
index 00000000..6b49d604
--- /dev/null
+++ b/protocols/bgp/server/fsm.go
@@ -0,0 +1,865 @@
+package server
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+	"net"
+	"time"
+
+	"github.com/taktv6/tbgp/rt"
+
+	log "github.com/sirupsen/logrus"
+	"github.com/taktv6/tbgp/config"
+	tnet "github.com/taktv6/tbgp/net"
+	"github.com/taktv6/tbgp/packet"
+	"github.com/taktv6/tflow2/convert"
+	tomb "gopkg.in/tomb.v2"
+)
+
+const (
+	// Administrative events
+	ManualStart                               = 1
+	ManualStop                                = 2
+	AutomaticStart                            = 3
+	ManualStartWithPassiveTcpEstablishment    = 4
+	AutomaticStartWithPassiveTcpEstablishment = 5
+	AutomaticStop                             = 8
+
+	// Timer events
+	ConnectRetryTimerExpires = 9
+	HoldTimerExpires         = 10
+	KeepaliveTimerExpires    = 11
+)
+
+const (
+	Cease       = 0
+	Idle        = 1
+	Connect     = 2
+	Active      = 3
+	OpenSent    = 4
+	OpenConfirm = 5
+	Established = 6
+)
+
+type FSM struct {
+	t           tomb.Tomb
+	stateReason string
+	state       int
+	lastState   int
+	eventCh     chan int
+
+	con         *net.TCPConn
+	con2        *net.TCPConn
+	conCh       chan *net.TCPConn
+	conErrCh    chan error
+	initiateCon chan struct{}
+	passive     bool
+
+	local  net.IP
+	remote net.IP
+
+	localASN  uint16
+	remoteASN uint16
+
+	neighborID uint32
+	routerID   uint32
+
+	delayOpen      bool
+	delayOpenTime  time.Duration
+	delayOpenTimer *time.Timer
+
+	connectRetryTime    time.Duration
+	connectRetryTimer   *time.Timer
+	connectRetryCounter int
+
+	holdTimeConfigured time.Duration
+	holdTime           time.Duration
+	holdTimer          *time.Timer
+
+	keepaliveTime  time.Duration
+	keepaliveTimer *time.Timer
+
+	msgRecvCh     chan msgRecvMsg
+	msgRecvFailCh chan msgRecvErr
+	stopMsgRecvCh chan struct{}
+
+	adjRibIn  *rt.LPM
+	adjRibOut *rt.LPM
+}
+
+type msgRecvMsg struct {
+	msg []byte
+	con *net.TCPConn
+}
+
+type msgRecvErr struct {
+	err error
+	con *net.TCPConn
+}
+
+func NewFSM(c config.Peer) *FSM {
+	fsm := &FSM{
+		state:             Idle,
+		passive:           true,
+		connectRetryTime:  5,
+		connectRetryTimer: time.NewTimer(time.Second * time.Duration(20)),
+
+		msgRecvCh:     make(chan msgRecvMsg),
+		msgRecvFailCh: make(chan msgRecvErr),
+		stopMsgRecvCh: make(chan struct{}),
+
+		holdTimeConfigured: time.Duration(c.HoldTimer),
+		holdTimer:          time.NewTimer(0),
+
+		keepaliveTime:  time.Duration(c.KeepAlive),
+		keepaliveTimer: time.NewTimer(0),
+
+		routerID: c.RouterID,
+		remote:   c.PeerAddress,
+		local:    c.LocalAddress,
+		localASN: uint16(c.LocalAS),
+		eventCh:  make(chan int),
+		conCh:    make(chan *net.TCPConn),
+		conErrCh: make(chan error), initiateCon: make(chan struct{}),
+	}
+	return fsm
+}
+
+func (fsm *FSM) disconnect() {
+	if fsm.con != nil {
+		fsm.con.Close()
+		fsm.con = nil
+	}
+	if fsm.con2 != nil {
+		fsm.con2.Close()
+		fsm.con2 = nil
+	}
+}
+
+func (fsm *FSM) changeState(new int, reason string) int {
+	state := map[int]string{
+		Cease:       "Cease",
+		Idle:        "Idle",
+		Connect:     "Connect",
+		Active:      "Active",
+		OpenSent:    "OpenSent",
+		OpenConfirm: "OpenConfirm",
+		Established: "Established",
+	}
+
+	log.WithFields(log.Fields{
+		"peer":       fsm.remote.String(),
+		"last_state": state[fsm.state],
+		"new_state":  state[new],
+		"reason":     reason,
+	}).Info("FSM: Neighbor state change")
+
+	fsm.lastState = fsm.state
+	fsm.state = new
+	fsm.stateReason = reason
+
+	return fsm.state
+}
+
+func (fsm *FSM) activate() {
+	fsm.eventCh <- ManualStart
+}
+
+func (fsm *FSM) Stop() error {
+	fsm.eventCh <- ManualStop
+	fsm.t.Kill(nil)
+	return fsm.t.Wait()
+}
+
+func (fsm *FSM) start() {
+	fsm.t.Go(fsm.main)
+	fsm.t.Go(fsm.tcpConnector)
+	return
+}
+
+func (fsm *FSM) main() error {
+	next := fsm.idle()
+	for {
+		switch next {
+		case Cease:
+			fsm.t.Kill(fmt.Errorf("FSM is being stopped"))
+			return nil
+		case Idle:
+			next = fsm.idle()
+		case Connect:
+			next = fsm.connect()
+		case Active:
+			next = fsm.active()
+		case OpenSent:
+			next = fsm.openSent()
+		case OpenConfirm:
+			next = fsm.openConfirm()
+		case Established:
+			next = fsm.established()
+		}
+	}
+}
+
+func (fsm *FSM) idle() int {
+	fsm.adjRibIn = nil
+	fsm.adjRibOut = nil
+	for {
+		select {
+		case c := <-fsm.conCh:
+			c.Close()
+			continue
+		case e := <-fsm.eventCh:
+			reason := ""
+			switch e {
+			case ManualStart:
+				reason = "Received ManualStart event %d for %s peer"
+			case AutomaticStart:
+				reason = "Received AutomaticStart event %d for %s peer"
+			default:
+				continue
+			}
+
+			fsm.connectRetryCounter = 0
+			fsm.startConnectRetryTimer()
+			if fsm.passive {
+				return fsm.changeState(Active, fmt.Sprintf(reason, "passive"))
+			}
+			fsm.tcpConnect()
+			return fsm.changeState(Connect, fmt.Sprintf(reason, "active"))
+		}
+
+	}
+}
+
+func (fsm *FSM) tcpConnector() error {
+	for {
+		select {
+		case <-fsm.initiateCon:
+			c, err := net.DialTCP("tcp", &net.TCPAddr{IP: fsm.local}, &net.TCPAddr{IP: fsm.remote, Port: BGPPORT})
+			if err != nil {
+				select {
+				case fsm.conErrCh <- err:
+					continue
+				case <-time.NewTimer(time.Second * 30).C:
+					continue
+				}
+			}
+
+			select {
+			case fsm.conCh <- c:
+				continue
+			case <-time.NewTimer(time.Second * 30).C:
+				c.Close()
+				continue
+			}
+		case <-fsm.t.Dying():
+			return nil
+		}
+	}
+}
+
+func (fsm *FSM) tcpConnect() {
+	fsm.initiateCon <- struct{}{}
+}
+
+// connect state waits for a TCP connection to be fully established. Either the active or passive one.
+func (fsm *FSM) connect() int {
+	for {
+		select {
+		case e := <-fsm.eventCh:
+			if e == ManualStop {
+				fsm.connectRetryCounter = 0
+				stopTimer(fsm.connectRetryTimer)
+				return fsm.changeState(Idle, "Manual stop event")
+			}
+			continue
+		case <-fsm.connectRetryTimer.C:
+			fsm.resetConnectRetryTimer()
+			fsm.tcpConnect()
+			continue
+		case c := <-fsm.conCh:
+			fsm.con = c
+			stopTimer(fsm.connectRetryTimer)
+			return fsm.connectSendOpen()
+		}
+	}
+}
+
+func (fsm *FSM) connectSendOpen() int {
+	err := fsm.sendOpen(fsm.con)
+	if err != nil {
+		stopTimer(fsm.connectRetryTimer)
+		return fsm.changeState(Idle, fmt.Sprintf("Sending OPEN message failed: %v", err))
+	}
+	fsm.holdTimer = time.NewTimer(time.Minute * 4)
+	return fsm.changeState(OpenSent, "Sent OPEN message")
+}
+
+// in the active state we wait for a passive TCP connection to be established
+func (fsm *FSM) active() int {
+	for {
+		select {
+		case e := <-fsm.eventCh:
+			if e == ManualStop {
+				fsm.disconnect()
+				fsm.connectRetryCounter = 0
+				stopTimer(fsm.connectRetryTimer)
+				return fsm.changeState(Active, "Manual stop event")
+			}
+			continue
+		case <-fsm.connectRetryTimer.C:
+			fsm.resetConnectRetryTimer()
+			fsm.tcpConnect()
+			return fsm.changeState(Connect, "Connect retry timer expired")
+		case c := <-fsm.conCh:
+			fsm.con = c
+			stopTimer(fsm.connectRetryTimer)
+			return fsm.activeSendOpen()
+		}
+	}
+}
+
+func (fsm *FSM) activeSendOpen() int {
+	err := fsm.sendOpen(fsm.con)
+	if err != nil {
+		fsm.resetConnectRetryTimer()
+		fsm.connectRetryCounter++
+		return fsm.changeState(Idle, fmt.Sprintf("Sending OPEN message failed: %v", err))
+	}
+	fsm.holdTimer = time.NewTimer(time.Minute * 4)
+	return fsm.changeState(OpenSent, "Sent OPEN message")
+}
+
+func (fsm *FSM) msgReceiver(c *net.TCPConn) error {
+	for {
+		msg, err := recvMsg(c)
+		if err != nil {
+			fsm.msgRecvFailCh <- msgRecvErr{err: err, con: c}
+			return nil
+
+			/*select {
+			case fsm.msgRecvFailCh <- msgRecvErr{err: err, con: c}:
+				continue
+			case <-time.NewTimer(time.Second * 60).C:
+				return nil
+			}*/
+		}
+		fsm.msgRecvCh <- msgRecvMsg{msg: msg, con: c}
+
+		select {
+		case <-fsm.stopMsgRecvCh:
+			return nil
+		default:
+			continue
+		}
+	}
+}
+
+func (fsm *FSM) openSent() int {
+	go fsm.msgReceiver(fsm.con)
+
+	for {
+		select {
+		case e := <-fsm.eventCh:
+			if e == ManualStop {
+				sendNotification(fsm.con, packet.Cease, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter = 0
+				return fsm.changeState(Idle, "Manual stop event")
+			}
+			continue
+		case <-fsm.holdTimer.C:
+			sendNotification(fsm.con, packet.HoldTimeExpired, 0)
+			stopTimer(fsm.connectRetryTimer)
+			fsm.disconnect()
+			fsm.connectRetryCounter++
+			return fsm.changeState(Idle, "Holdtimer expired")
+		case c := <-fsm.conCh: // 2nd connection coming in. Collision!
+			if fsm.con2 != nil {
+				log.WithFields(log.Fields{
+					"peer":  fsm.remote.String(),
+					"local": fsm.local.String(),
+				}).Warningf("Received third connection from peer. Dropping new connection")
+				c.Close()
+				continue
+			}
+
+			err := fsm.sendOpen(c) // FIXME: Not sure if this is standard compliant
+			if err != nil {
+				c.Close()
+				continue
+			}
+			fsm.con2 = c
+			go fsm.msgReceiver(c)
+			continue
+		case recvMsg := <-fsm.msgRecvCh:
+			msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg))
+			if err != nil {
+				switch bgperr := err.(type) {
+				case packet.BGPError:
+					sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode)
+					sendNotification(fsm.con2, bgperr.ErrorCode, bgperr.ErrorSubCode)
+				}
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, fmt.Sprintf("Failed to decode BGP message: %v", err))
+			}
+			switch msg.Header.Type {
+			case packet.NotificationMsg:
+				nMsg := msg.Body.(*packet.BGPNotification)
+				if nMsg.ErrorCode == packet.UnsupportedVersionNumber {
+					stopTimer(fsm.connectRetryTimer)
+					fsm.disconnect()
+					return fsm.changeState(Idle, "Received NOTIFICATION")
+				}
+
+				if nMsg.ErrorCode == packet.Cease {
+					// Was this connection to be closed anyways?
+					if fsm.dumpCon(recvMsg.con) {
+						continue
+					}
+				}
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "Received NOTIFICATION")
+			case packet.OpenMsg:
+				openMsg := msg.Body.(*packet.BGPOpen)
+				fsm.neighborID = openMsg.BGPIdentifier
+				fsm.resolveCollision()
+				stopTimer(fsm.connectRetryTimer)
+				err := fsm.sendKeepalive()
+				if err != nil {
+					return fsm.openSentTCPFail(err)
+				}
+				fsm.holdTime = time.Duration(math.Min(float64(fsm.holdTimeConfigured), float64(openMsg.HoldTime)))
+				if fsm.holdTime != 0 {
+					fsm.holdTimer.Reset(time.Second * fsm.holdTime)
+					fsm.keepaliveTime = fsm.holdTime / 3
+					fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime)
+				}
+				return fsm.changeState(OpenConfirm, "Received OPEN message")
+			default:
+				sendNotification(fsm.con, packet.FiniteStateMachineError, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "FSM Error")
+			}
+		case err := <-fsm.msgRecvFailCh:
+			if err.con == fsm.con && fsm.con2 != nil {
+				fsm.con.Close()
+				fsm.con = fsm.con2
+				fsm.con2 = nil
+				continue
+			}
+
+			if err.con == fsm.con2 {
+				fsm.con2.Close()
+				fsm.con2 = nil
+				continue
+			}
+			return fsm.openSentTCPFail(err.err)
+		}
+	}
+}
+
+func (fsm *FSM) openSentTCPFail(err error) int {
+	fsm.con.Close()
+	fsm.resetConnectRetryTimer()
+	return fsm.changeState(Active, fmt.Sprintf("TCP failure: %v", err))
+}
+
+func (fsm *FSM) dumpCon(c *net.TCPConn) bool {
+	p := fsm.isPassive(c)
+	if fsm.routerID > fsm.neighborID {
+		return p
+	}
+	return !p
+}
+
+func (fsm *FSM) resolveCollision() {
+	if fsm.con2 == nil {
+		return
+	}
+
+	if fsm.routerID > fsm.neighborID {
+		// Terminate passive connection
+		if fsm.isPassive(fsm.con) {
+			dumpCon(fsm.con)
+			fsm.con = fsm.con2
+			return
+		}
+		if fsm.isPassive(fsm.con2) {
+			dumpCon(fsm.con2)
+			return
+		}
+		return
+	}
+
+	// Terminate active connection
+	if !fsm.isPassive(fsm.con) {
+		dumpCon(fsm.con)
+		fsm.con = fsm.con2
+		return
+	}
+	if !fsm.isPassive(fsm.con2) {
+		dumpCon(fsm.con2)
+		fsm.con2.Close()
+		fsm.con2 = nil
+		return
+	}
+}
+
+func dumpCon(c *net.TCPConn) {
+	sendNotification(c, packet.Cease, packet.ConnectionCollisionResolution)
+	c.Close()
+}
+
+func (fsm *FSM) isPassive(c *net.TCPConn) bool {
+	if c.LocalAddr().String() == fmt.Sprintf("%s:179", fsm.local.String()) {
+		return true
+	}
+	return false
+}
+
+func (fsm *FSM) openConfirm() int {
+	for {
+		select {
+		case e := <-fsm.eventCh:
+			if e == ManualStop { // Event 2
+				sendNotification(fsm.con, packet.Cease, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter = 0
+				return fsm.changeState(Idle, "Manual stop event")
+			}
+			continue
+		case <-fsm.holdTimer.C:
+			sendNotification(fsm.con, packet.HoldTimeExpired, 0)
+			stopTimer(fsm.connectRetryTimer)
+			fsm.disconnect()
+			fsm.connectRetryCounter++
+			return fsm.changeState(Idle, "Holdtimer expired")
+		case <-fsm.keepaliveTimer.C:
+			err := fsm.sendKeepalive()
+			if err != nil {
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, fmt.Sprintf("Failed to send keepalive: %v", err))
+			}
+			fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime)
+			continue
+		case c := <-fsm.conCh:
+			if fsm.con2 != nil {
+				log.WithFields(log.Fields{
+					"peer":  fsm.remote.String(),
+					"local": fsm.local.String(),
+				}).Warningf("Received third connection from peer. Dropping new connection")
+				c.Close()
+				continue
+			}
+
+			err := fsm.sendOpen(c) // FIXME: Not sure if this is standard compliant
+			if err != nil {
+				c.Close()
+				continue
+			}
+			fsm.con2 = c
+			go fsm.msgReceiver(c)
+			continue
+		case recvMsg := <-fsm.msgRecvCh:
+			msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg))
+			if err != nil {
+				fmt.Printf("Failed to decode message: %v\n", recvMsg.msg)
+				switch bgperr := err.(type) {
+				case packet.BGPError:
+					sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode)
+					sendNotification(fsm.con2, bgperr.ErrorCode, bgperr.ErrorSubCode)
+				}
+				stopTimer(fsm.connectRetryTimer)
+				fsm.disconnect()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "Failed to decode BGP message")
+			}
+
+			switch msg.Header.Type {
+			case packet.NotificationMsg:
+				nMsg := msg.Body.(packet.BGPNotification)
+				if nMsg.ErrorCode == packet.UnsupportedVersionNumber {
+					stopTimer(fsm.connectRetryTimer)
+					fsm.con.Close()
+					return fsm.changeState(Idle, "Received NOTIFICATION")
+				}
+
+				if nMsg.ErrorCode == packet.Cease {
+					// Was this connection to be closed anyways?
+					if fsm.dumpCon(recvMsg.con) {
+						continue
+					}
+				}
+
+				return fsm.openConfirmTCPFail(fmt.Errorf("NOTIFICATION received"))
+			case packet.KeepaliveMsg:
+				fsm.holdTimer.Reset(time.Second * fsm.holdTime)
+				return fsm.changeState(Established, "Received KEEPALIVE")
+			case packet.OpenMsg:
+				openMsg := msg.Body.(*packet.BGPOpen)
+				fsm.neighborID = openMsg.BGPIdentifier
+				fsm.resolveCollision()
+			default:
+				sendNotification(fsm.con, packet.FiniteStateMachineError, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "FSM Error")
+			}
+		case err := <-fsm.msgRecvFailCh:
+			if err.con == fsm.con && fsm.con2 != nil {
+				fsm.con.Close()
+				fsm.con = fsm.con2
+				fsm.con2 = nil
+				continue
+			}
+
+			if err.con == fsm.con2 {
+				fsm.con2.Close()
+				fsm.con2 = nil
+				continue
+			}
+			return fsm.openConfirmTCPFail(err.err)
+		}
+	}
+}
+
+func (fsm *FSM) openConfirmTCPFail(err error) int {
+	fsm.con.Close()
+	fsm.resetConnectRetryTimer()
+	fsm.connectRetryCounter++
+	return fsm.changeState(Idle, fmt.Sprintf("Failure: %v", err))
+}
+
+func (fsm *FSM) established() int {
+	fsm.adjRibIn = rt.New()
+	go func() {
+		for {
+			time.Sleep(time.Second * 10)
+			fmt.Printf("Dumping AdjRibIn\n")
+			routes := fsm.adjRibIn.Dump()
+			for _, route := range routes {
+				fmt.Printf("LPM: %s\n", route.Prefix().String())
+			}
+		}
+	}()
+
+	for {
+		select {
+		case e := <-fsm.eventCh:
+			if e == ManualStop { // Event 2
+				sendNotification(fsm.con, packet.Cease, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter = 0
+				return fsm.changeState(Idle, "Manual stop event")
+			}
+			if e == AutomaticStop { // Event 8
+				sendNotification(fsm.con, packet.Cease, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "Automatic stop event")
+			}
+			continue
+		case <-fsm.holdTimer.C:
+			sendNotification(fsm.con, packet.HoldTimeExpired, 0)
+			stopTimer(fsm.connectRetryTimer)
+			fsm.con.Close()
+			fsm.connectRetryCounter++
+			return fsm.changeState(Idle, "Holdtimer expired")
+		case <-fsm.keepaliveTimer.C:
+			err := fsm.sendKeepalive()
+			if err != nil {
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, fmt.Sprintf("Failed to send keepalive: %v", err))
+			}
+			fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime)
+			continue
+		case c := <-fsm.conCh:
+			c.Close()
+			continue
+		case recvMsg := <-fsm.msgRecvCh:
+			msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg))
+			if err != nil {
+				switch bgperr := err.(type) {
+				case packet.BGPError:
+					sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode)
+				}
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "Failed to decode BGP message")
+			}
+			switch msg.Header.Type {
+			case packet.NotificationMsg:
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "Received NOTIFICATION")
+			case packet.UpdateMsg:
+				if fsm.holdTime != 0 {
+					fsm.holdTimer.Reset(time.Second * fsm.holdTime)
+				}
+
+				u := msg.Body.(*packet.BGPUpdate)
+
+				for r := u.WithdrawnRoutes; r != nil; r = r.Next {
+					x := r.IP.([4]byte)
+					pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen)
+					fmt.Printf("LPM: Removing prefix %s\n", pfx.String())
+					fsm.adjRibIn.RemovePfx(pfx)
+				}
+
+				for r := u.NLRI; r != nil; r = r.Next {
+					x := r.IP.([4]byte)
+					pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen)
+					fmt.Printf("LPM: Adding prefix %s\n", pfx.String())
+
+					path := &rt.Path{
+						Type:    rt.BGPPathType,
+						BGPPath: &rt.BGPPath{},
+					}
+
+					for pa := u.PathAttributes; pa.Next != nil; pa = pa.Next {
+						switch pa.TypeCode {
+						case packet.OriginAttr:
+							path.BGPPath.Origin = pa.Value.(uint8)
+						case packet.LocalPrefAttr:
+							path.BGPPath.LocalPref = pa.Value.(uint32)
+						case packet.MEDAttr:
+							path.BGPPath.MED = pa.Value.(uint32)
+						case packet.NextHopAttr:
+							path.BGPPath.NextHop = pa.Value.(uint32)
+						case packet.ASPathAttr:
+							path.BGPPath.ASPath = pa.ASPathString()
+							path.BGPPath.ASPathLen = pa.ASPathLen()
+						}
+					}
+					fsm.adjRibIn.Insert(rt.NewRoute(pfx, []*rt.Path{path}))
+				}
+
+				continue
+			case packet.KeepaliveMsg:
+				if fsm.holdTime != 0 {
+					fsm.holdTimer.Reset(time.Second * fsm.holdTime)
+				}
+				continue
+			case packet.OpenMsg:
+				if fsm.con2 != nil {
+					sendNotification(fsm.con2, packet.Cease, packet.ConnectionCollisionResolution)
+					fsm.con2.Close()
+					fsm.con2 = nil
+					continue
+				}
+				sendNotification(fsm.con, packet.FiniteStateMachineError, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "FSM Error")
+			default:
+				sendNotification(fsm.con, packet.FiniteStateMachineError, 0)
+				stopTimer(fsm.connectRetryTimer)
+				fsm.con.Close()
+				fsm.connectRetryCounter++
+				return fsm.changeState(Idle, "FSM Error")
+			}
+		case err := <-fsm.msgRecvFailCh:
+			if err.con == fsm.con && fsm.con2 != nil {
+				fsm.con.Close()
+				fsm.con = fsm.con2
+				fsm.con2 = nil
+				continue
+			}
+
+			if err.con == fsm.con2 {
+				fsm.con2.Close()
+				fsm.con2 = nil
+				continue
+			}
+			return fsm.openConfirmTCPFail(err.err)
+		}
+	}
+}
+
+func stopTimer(t *time.Timer) {
+	if !t.Stop() {
+		select {
+		case <-t.C:
+		default:
+		}
+	}
+}
+
+func (fsm *FSM) startConnectRetryTimer() {
+	fsm.connectRetryTimer = time.NewTimer(time.Second * fsm.connectRetryTime)
+}
+
+func (fsm *FSM) resetConnectRetryTimer() {
+	if !fsm.connectRetryTimer.Reset(time.Second * fsm.connectRetryTime) {
+		<-fsm.connectRetryTimer.C
+	}
+}
+
+func (fsm *FSM) resetDelayOpenTimer() {
+	if !fsm.delayOpenTimer.Reset(time.Second * fsm.delayOpenTime) {
+		<-fsm.delayOpenTimer.C
+	}
+}
+
+func (fsm *FSM) sendKeepalive() error {
+	msg := packet.SerializeKeepaliveMsg()
+
+	_, err := fsm.con.Write(msg)
+	if err != nil {
+		return fmt.Errorf("Unable to send KEEPALIVE message: %v", err)
+	}
+
+	return nil
+}
+
+func (fsm *FSM) sendOpen(c *net.TCPConn) error {
+	msg := packet.SerializeOpenMsg(&packet.BGPOpen{
+		Version:       BGPVersion,
+		AS:            fsm.localASN,
+		HoldTime:      uint16(fsm.holdTimeConfigured),
+		BGPIdentifier: fsm.routerID,
+		OptParmLen:    0,
+	})
+
+	_, err := c.Write(msg)
+	if err != nil {
+		return fmt.Errorf("Unable to send OPEN message: %v", err)
+	}
+
+	return nil
+}
+
+func sendNotification(c *net.TCPConn, errorCode uint8, errorSubCode uint8) error {
+	if c == nil {
+		return fmt.Errorf("connection is nil")
+	}
+
+	msg := packet.SerializeNotificationMsg(&packet.BGPNotification{})
+
+	_, err := c.Write(msg)
+	if err != nil {
+		return fmt.Errorf("Unable to send NOTIFICATION message: %v", err)
+	}
+
+	return nil
+}
diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go
new file mode 100644
index 00000000..d6ac7855
--- /dev/null
+++ b/protocols/bgp/server/peer.go
@@ -0,0 +1,36 @@
+package server
+
+import (
+	"net"
+
+	"github.com/taktv6/tbgp/config"
+)
+
+type Peer struct {
+	addr     net.IP
+	asn      uint32
+	fsm      *FSM
+	routerID uint32
+}
+
+func NewPeer(c config.Peer) (*Peer, error) {
+	p := &Peer{
+		addr: c.PeerAddress,
+		asn:  c.PeerAS,
+		fsm:  NewFSM(c),
+	}
+	return p, nil
+}
+
+func (p *Peer) GetAddr() net.IP {
+	return p.addr
+}
+
+func (p *Peer) GetASN() uint32 {
+	return p.asn
+}
+
+func (p *Peer) Start() {
+	p.fsm.start()
+	p.fsm.activate()
+}
diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go
new file mode 100644
index 00000000..3bc9b193
--- /dev/null
+++ b/protocols/bgp/server/server.go
@@ -0,0 +1,119 @@
+package server
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"strings"
+
+	log "github.com/sirupsen/logrus"
+	"github.com/taktv6/tbgp/config"
+	"github.com/taktv6/tbgp/packet"
+)
+
+const (
+	uint16max  = 65535
+	BGPVersion = 4
+)
+
+type BGPServer struct {
+	listeners []*TCPListener
+	acceptCh  chan *net.TCPConn
+	peers     map[string]*Peer
+	routerID  uint32
+}
+
+func NewBgpServer() *BGPServer {
+	return &BGPServer{
+		peers: make(map[string]*Peer),
+	}
+}
+
+func (b *BGPServer) RouterID() uint32 {
+	return b.routerID
+}
+
+func (b *BGPServer) Start(c *config.Global) error {
+	if err := c.SetDefaultGlobalConfigValues(); err != nil {
+		return fmt.Errorf("Failed to load defaults: %v", err)
+	}
+
+	fmt.Printf("ROUTER ID: %d\n", c.RouterID)
+	b.routerID = c.RouterID
+
+	if c.Listen {
+		acceptCh := make(chan *net.TCPConn, 4096)
+		for _, addr := range c.LocalAddressList {
+			l, err := NewTCPListener(addr, c.Port, acceptCh)
+			if err != nil {
+				return fmt.Errorf("Failed to start TCPListener for %s: %v", addr.String(), err)
+			}
+			b.listeners = append(b.listeners, l)
+		}
+		b.acceptCh = acceptCh
+
+		go b.incomingConnectionWorker()
+	}
+
+	return nil
+}
+
+func (b *BGPServer) incomingConnectionWorker() {
+	for {
+		c := <-b.acceptCh
+		fmt.Printf("Incoming connection!\n")
+		fmt.Printf("Connection from: %v\n", c.RemoteAddr())
+
+		peerAddr := strings.Split(c.RemoteAddr().String(), ":")[0]
+		if _, ok := b.peers[peerAddr]; !ok {
+			c.Close()
+			log.WithFields(log.Fields{
+				"source": c.RemoteAddr(),
+			}).Warning("TCP connection from unknown source")
+			continue
+		}
+
+		log.WithFields(log.Fields{
+			"source": c.RemoteAddr(),
+		}).Info("Incoming TCP connection")
+
+		fmt.Printf("DEBUG: Sending incoming TCP connection to fsm for peer %s\n", peerAddr)
+		b.peers[peerAddr].fsm.conCh <- c
+		fmt.Printf("DEBUG: Sending done\n")
+	}
+}
+
+func (b *BGPServer) AddPeer(c config.Peer) error {
+	if c.LocalAS > uint16max || c.PeerAS > uint16max {
+		return fmt.Errorf("32bit ASNs are not supported yet")
+	}
+
+	peer, err := NewPeer(c)
+	if err != nil {
+		return err
+	}
+
+	peer.routerID = c.RouterID
+	peerAddr := peer.GetAddr().String()
+	b.peers[peerAddr] = peer
+	b.peers[peerAddr].Start()
+
+	return nil
+}
+
+func recvMsg(c *net.TCPConn) (msg []byte, err error) {
+	buffer := make([]byte, packet.MaxLen)
+	_, err = io.ReadFull(c, buffer[0:packet.MinLen])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	l := int(buffer[16])*256 + int(buffer[17])
+	toRead := l
+	_, err = io.ReadFull(c, buffer[packet.MinLen:toRead])
+	if err != nil {
+		return nil, fmt.Errorf("Read failed: %v", err)
+	}
+
+	return buffer, nil
+}
diff --git a/protocols/bgp/server/sockopt.go b/protocols/bgp/server/sockopt.go
new file mode 100644
index 00000000..2984216a
--- /dev/null
+++ b/protocols/bgp/server/sockopt.go
@@ -0,0 +1,31 @@
+package server
+
+import (
+	"net"
+	"os"
+	"syscall"
+)
+
+const (
+	TCP_MD5SIG       = 14 // TCP MD5 Signature (RFC2385)
+	IPV6_MINHOPCOUNT = 73 // Generalized TTL Security Mechanism (RFC5082)
+)
+
+func SetListenTCPTTLSockopt(l *net.TCPListener, ttl int) error {
+	fi, family, err := extractFileAndFamilyFromTCPListener(l)
+	defer fi.Close()
+	if err != nil {
+		return err
+	}
+	return setsockoptIPTTL(int(fi.Fd()), family, ttl)
+}
+
+func setsockoptIPTTL(fd int, family int, value int) error {
+	level := syscall.IPPROTO_IP
+	name := syscall.IP_TTL
+	if family == syscall.AF_INET6 {
+		level = syscall.IPPROTO_IPV6
+		name = syscall.IPV6_UNICAST_HOPS
+	}
+	return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value))
+}
diff --git a/protocols/bgp/server/tcplistener.go b/protocols/bgp/server/tcplistener.go
new file mode 100644
index 00000000..04607c74
--- /dev/null
+++ b/protocols/bgp/server/tcplistener.go
@@ -0,0 +1,65 @@
+package server
+
+import (
+	"net"
+	"strconv"
+
+	log "github.com/sirupsen/logrus"
+)
+
+const (
+	BGPPORT = 179
+)
+
+type TCPListener struct {
+	l       *net.TCPListener
+	closeCh chan struct{}
+}
+
+func NewTCPListener(address net.IP, port uint16, ch chan *net.TCPConn) (*TCPListener, error) {
+	proto := "tcp4"
+	if address.To4() == nil {
+		proto = "tcp6"
+	}
+
+	addr, err := net.ResolveTCPAddr(proto, net.JoinHostPort(address.String(), strconv.Itoa(int(port))))
+	if err != nil {
+		return nil, err
+	}
+
+	l, err := net.ListenTCP(proto, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	// Note: Set TTL=255 for incoming connection listener in order to accept
+	// connection in case for the neighbor has TTL Security settings.
+	if err := SetListenTCPTTLSockopt(l, 255); err != nil {
+		log.WithFields(log.Fields{
+			"Topic": "Peer",
+			"Key":   addr,
+		}).Warnf("cannot set TTL(=%d) for TCPLisnter: %s", 255, err)
+	}
+
+	tl := &TCPListener{
+		l:       l,
+		closeCh: make(chan struct{}),
+	}
+
+	go func(tl *TCPListener) error {
+		for {
+			conn, err := tl.l.AcceptTCP()
+			if err != nil {
+				close(tl.closeCh)
+				log.WithFields(log.Fields{
+					"Topic": "Peer",
+					"Error": err,
+				}).Warn("Failed to AcceptTCP")
+				return err
+			}
+			ch <- conn
+		}
+	}(tl)
+
+	return tl, nil
+}
diff --git a/protocols/bgp/server/util.go b/protocols/bgp/server/util.go
new file mode 100644
index 00000000..871f6a58
--- /dev/null
+++ b/protocols/bgp/server/util.go
@@ -0,0 +1,33 @@
+package server
+
+import (
+	"net"
+	"os"
+	"strings"
+	"syscall"
+)
+
+func extractFileAndFamilyFromTCPListener(l *net.TCPListener) (*os.File, int, error) {
+	// Note #1: TCPListener.File() has the unexpected side-effect of putting
+	// the original socket into blocking mode. See Note #2.
+	fi, err := l.File()
+	if err != nil {
+		return nil, 0, err
+	}
+
+	// Note #2: Call net.FileListener() to put the original socket back into
+	// non-blocking mode.
+	fl, err := net.FileListener(fi)
+	if err != nil {
+		fi.Close()
+		return nil, 0, err
+	}
+	fl.Close()
+
+	family := syscall.AF_INET
+	if strings.Contains(l.Addr().String(), "[") {
+		family = syscall.AF_INET6
+	}
+
+	return fi, family, nil
+}
-- 
GitLab