diff --git a/.gitignore b/.gitignore
index 4732630cb517f763bfc35f781811cf2176e72c2a..bb99a56915e4f0f56746244dbb3affd0ee20712a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
 # IDE specific files
 .idea/
 *.iml
+.vscode
 
 # 'go build' binary
 bio-rd
diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel
index 49f16b7773345b729ff25a1a486a86b1669992ed..3a9b5fd463ba33831be932734b27a8c845e8069d 100644
--- a/protocols/bgp/packet/BUILD.bazel
+++ b/protocols/bgp/packet/BUILD.bazel
@@ -3,37 +3,36 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = [
-        "as_path.go",
         "bgp.go",
-        "community.go",
         "decoder.go",
         "encoder.go",
-        "large_community.go",
         "nlri.go",
-        "options.go",
         "parameters.go",
         "path_attribute_flags.go",
         "path_attributes.go",
+        "update.go",
     ],
     importpath = "github.com/bio-routing/bio-rd/protocols/bgp/packet",
     visibility = ["//visibility:public"],
-    deps = ["//vendor/github.com/taktv6/tflow2/convert:go_default_library"],
+    deps = [
+        "//protocols/bgp/types:go_default_library",
+        "//route:go_default_library",
+        "//vendor/github.com/taktv6/tflow2/convert:go_default_library",
+    ],
 )
 
 go_test(
     name = "go_default_test",
     srcs = [
-        "community_test.go",
         "decoder_test.go",
         "encoder_test.go",
-        "large_community_benchmark_test.go",
-        "large_community_test.go",
         "nlri_test.go",
         "path_attributes_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
         "//net:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
         "//vendor/github.com/taktv6/tflow2/convert:go_default_library",
     ],
diff --git a/protocols/bgp/packet/as_path.go b/protocols/bgp/packet/as_path.go
deleted file mode 100644
index 0cdfc96a1b6f418013ae6a6656f6cc8ecf554686..0000000000000000000000000000000000000000
--- a/protocols/bgp/packet/as_path.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package packet
-
-import "fmt"
-
-type ASPath []ASPathSegment
-
-type ASPathSegment struct {
-	Type  uint8
-	Count uint8
-	ASNs  []uint32
-}
-
-func (pa ASPath) String() (ret string) {
-	for _, p := range pa {
-		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 ASPath) Length() (ret uint16) {
-	for _, p := range pa {
-		if p.Type == ASSet {
-			ret++
-			continue
-		}
-		ret += uint16(len(p.ASNs))
-	}
-
-	return
-}
diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go
index 8aabec7f7c729cd23d47ed6abe517742f2583708..1859f665c0a96904c5a5cd2c4e7d4d7dfe640c08 100644
--- a/protocols/bgp/packet/bgp.go
+++ b/protocols/bgp/packet/bgp.go
@@ -74,10 +74,6 @@ const (
 	EGP        = 1
 	INCOMPLETE = 2
 
-	// ASPath Segment Types
-	ASSet      = 1
-	ASSequence = 2
-
 	// NOTIFICATION Cease error SubCodes (RFC4486)
 	MaxPrefReached                = 1
 	AdminShut                     = 2
@@ -133,22 +129,6 @@ type BGPNotification struct {
 	ErrorSubcode uint8
 }
 
-type BGPUpdate struct {
-	WithdrawnRoutesLen uint16
-	WithdrawnRoutes    *NLRI
-	TotalPathAttrLen   uint16
-	PathAttributes     *PathAttribute
-	NLRI               *NLRI
-}
-
-type BGPUpdateAddPath struct {
-	WithdrawnRoutesLen uint16
-	WithdrawnRoutes    *NLRIAddPath
-	TotalPathAttrLen   uint16
-	PathAttributes     *PathAttribute
-	NLRI               *NLRIAddPath
-}
-
 type PathAttribute struct {
 	Length         uint16
 	Optional       bool
@@ -160,19 +140,6 @@ type PathAttribute struct {
 	Next           *PathAttribute
 }
 
-type NLRI struct {
-	IP     uint32
-	Pfxlen uint8
-	Next   *NLRI
-}
-
-type NLRIAddPath struct {
-	PathIdentifier uint32
-	IP             uint32
-	Pfxlen         uint8
-	Next           *NLRIAddPath
-}
-
 type Aggretator struct {
 	Addr uint32
 	ASN  uint16
diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go
index c4d583f2138edfbbfb5b9be33b2a1c11764ee0bd..ca717c781194adf20d2d5c08f332c03f060be849 100644
--- a/protocols/bgp/packet/decoder.go
+++ b/protocols/bgp/packet/decoder.go
@@ -6,11 +6,12 @@ import (
 	"fmt"
 	"net"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/taktv6/tflow2/convert"
 )
 
 // Decode decodes a BGP message
-func Decode(buf *bytes.Buffer, opt *Options) (*BGPMessage, error) {
+func Decode(buf *bytes.Buffer, opt *types.Options) (*BGPMessage, error) {
 	hdr, err := decodeHeader(buf)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to decode header: %v", err)
@@ -27,7 +28,7 @@ func Decode(buf *bytes.Buffer, opt *Options) (*BGPMessage, error) {
 	}, nil
 }
 
-func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *Options) (interface{}, error) {
+func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *types.Options) (interface{}, error) {
 	switch msgType {
 	case OpenMsg:
 		return decodeOpenMsg(buf)
@@ -41,7 +42,7 @@ func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *Options) (in
 	return nil, fmt.Errorf("Unknown message type: %d", msgType)
 }
 
-func decodeUpdateMsg(buf *bytes.Buffer, l uint16, opt *Options) (*BGPUpdate, error) {
+func decodeUpdateMsg(buf *bytes.Buffer, l uint16, opt *types.Options) (*BGPUpdate, error) {
 	msg := &BGPUpdate{}
 
 	err := decode(buf, []interface{}{&msg.WithdrawnRoutesLen})
diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go
index 56416186ba1d714a13fb4f248281d3d5f48451dd..64c5cf0426596a04ce463fdfc38b7fb4d8511b87 100644
--- a/protocols/bgp/packet/decoder_test.go
+++ b/protocols/bgp/packet/decoder_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/stretchr/testify/assert"
 	"github.com/taktv6/tflow2/convert"
 )
@@ -71,7 +72,7 @@ func BenchmarkDecodeUpdateMsg(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		buf := bytes.NewBuffer(input)
-		_, err := decodeUpdateMsg(buf, uint16(len(input)), &Options{})
+		_, err := decodeUpdateMsg(buf, uint16(len(input)), &types.Options{})
 		if err != nil {
 			fmt.Printf("decodeUpdateMsg failed: %v\n", err)
 		}
@@ -252,7 +253,7 @@ func TestDecode(t *testing.T) {
 
 	for _, test := range tests {
 		buf := bytes.NewBuffer(test.input)
-		msg, err := Decode(buf, &Options{})
+		msg, err := Decode(buf, &types.Options{})
 
 		if err != nil && !test.wantFail {
 			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
@@ -519,10 +520,9 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         6,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -623,18 +623,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -702,18 +700,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -794,18 +790,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -899,18 +893,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -1017,18 +1009,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -1151,18 +1141,16 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         12,
 						TypeCode:       2,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
-								Type:  2,
-								Count: 2,
+								Type: 2,
 								ASNs: []uint32{
 									15169,
 									3320,
 								},
 							},
 							{
-								Type:  1,
-								Count: 2,
+								Type: 1,
 								ASNs: []uint32{
 									15169,
 									3320,
@@ -1425,11 +1413,10 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						ExtendedLength: false,
 						Length:         6,
 						TypeCode:       17,
-						Value: ASPath{
-							ASPathSegment{
-								Type:  2,
-								Count: 1,
-								ASNs:  []uint32{202739},
+						Value: types.ASPath{
+							types.ASPathSegment{
+								Type: 2,
+								ASNs: []uint32{202739},
 							},
 						},
 						Next: &PathAttribute{
@@ -1465,7 +1452,7 @@ func TestDecodeUpdateMsg(t *testing.T) {
 			if l == 0 {
 				l = uint16(len(test.input))
 			}
-			msg, err := decodeUpdateMsg(buf, l, &Options{})
+			msg, err := decodeUpdateMsg(buf, l, &types.Options{})
 
 			if err != nil && !test.wantFail {
 				t.Fatalf("Unexpected error in test %d: %v", test.testNum, err)
@@ -1501,7 +1488,7 @@ func TestDecodeMsgBody(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := decodeMsgBody(test.buffer, test.msgType, test.length, &Options{})
+		res, err := decodeMsgBody(test.buffer, test.msgType, test.length, &types.Options{})
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error dit not happen in test %q", test.name)
 		}
diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go
index ac9bc4483199b81f4e8ecc25c6b91e3ec8361b9c..122bca972219f246346b263f666554a13f035b04 100644
--- a/protocols/bgp/packet/encoder.go
+++ b/protocols/bgp/packet/encoder.go
@@ -2,7 +2,6 @@ package packet
 
 import (
 	"bytes"
-	"fmt"
 
 	"github.com/taktv6/tflow2/convert"
 )
@@ -62,121 +61,3 @@ func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) {
 	buf.Write(convert.Uint16Byte(length))
 	buf.WriteByte(typ)
 }
-
-func (b *BGPUpdateAddPath) SerializeUpdate(opt *Options) ([]byte, error) {
-	budget := MaxLen - MinLen
-	buf := bytes.NewBuffer(nil)
-
-	withdrawBuf := bytes.NewBuffer(nil)
-	for withdraw := b.WithdrawnRoutes; withdraw != nil; withdraw = withdraw.Next {
-		nlriLen := int(withdraw.serialize(withdrawBuf))
-		budget -= nlriLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	pathAttributesBuf := bytes.NewBuffer(nil)
-	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf, opt))
-		budget -= paLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	nlriBuf := bytes.NewBuffer(nil)
-	for nlri := b.NLRI; nlri != nil; nlri = nlri.Next {
-		nlriLen := int(nlri.serialize(nlriBuf))
-		budget -= nlriLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	withdrawnRoutesLen := withdrawBuf.Len()
-	if withdrawnRoutesLen > 65535 {
-		return nil, fmt.Errorf("Invalid Withdrawn Routes Length: %d", withdrawnRoutesLen)
-	}
-
-	totalPathAttributesLen := pathAttributesBuf.Len()
-	if totalPathAttributesLen > 65535 {
-		return nil, fmt.Errorf("Invalid Total Path Attribute Length: %d", totalPathAttributesLen)
-	}
-
-	totalLength := 2 + withdrawnRoutesLen + totalPathAttributesLen + 2 + nlriBuf.Len() + 19
-	if totalLength > 4096 {
-		return nil, fmt.Errorf("Update too long: %d bytes", totalLength)
-	}
-
-	serializeHeader(buf, uint16(totalLength), UpdateMsg)
-
-	buf.Write(convert.Uint16Byte(uint16(withdrawnRoutesLen)))
-	buf.Write(withdrawBuf.Bytes())
-
-	buf.Write(convert.Uint16Byte(uint16(totalPathAttributesLen)))
-	buf.Write(pathAttributesBuf.Bytes())
-
-	buf.Write(nlriBuf.Bytes())
-
-	return buf.Bytes(), nil
-}
-
-func (b *BGPUpdate) SerializeUpdate(opt *Options) ([]byte, error) {
-	budget := MaxLen - MinLen
-	buf := bytes.NewBuffer(nil)
-
-	withdrawBuf := bytes.NewBuffer(nil)
-	for withdraw := b.WithdrawnRoutes; withdraw != nil; withdraw = withdraw.Next {
-		nlriLen := int(withdraw.serialize(withdrawBuf))
-		budget -= nlriLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	pathAttributesBuf := bytes.NewBuffer(nil)
-	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf, opt))
-		budget -= paLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	nlriBuf := bytes.NewBuffer(nil)
-	for nlri := b.NLRI; nlri != nil; nlri = nlri.Next {
-		nlriLen := int(nlri.serialize(nlriBuf))
-		budget -= nlriLen
-		if budget < 0 {
-			return nil, fmt.Errorf("update too long")
-		}
-	}
-
-	withdrawnRoutesLen := withdrawBuf.Len()
-	if withdrawnRoutesLen > 65535 {
-		return nil, fmt.Errorf("Invalid Withdrawn Routes Length: %d", withdrawnRoutesLen)
-	}
-
-	totalPathAttributesLen := pathAttributesBuf.Len()
-	if totalPathAttributesLen > 65535 {
-		return nil, fmt.Errorf("Invalid Total Path Attribute Length: %d", totalPathAttributesLen)
-	}
-
-	totalLength := 2 + withdrawnRoutesLen + totalPathAttributesLen + 2 + nlriBuf.Len() + 19
-	if totalLength > 4096 {
-		return nil, fmt.Errorf("Update too long: %d bytes", totalLength)
-	}
-
-	serializeHeader(buf, uint16(totalLength), UpdateMsg)
-
-	buf.Write(convert.Uint16Byte(uint16(withdrawnRoutesLen)))
-	buf.Write(withdrawBuf.Bytes())
-
-	buf.Write(convert.Uint16Byte(uint16(totalPathAttributesLen)))
-	buf.Write(pathAttributesBuf.Bytes())
-
-	buf.Write(nlriBuf.Bytes())
-
-	return buf.Bytes(), nil
-}
diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go
index 6de92a6cd59ff167d87efbaf5d197fb46c00aaca..e4bd4cea7a7cf65a8ac5ad75ff17218a294cbfe5 100644
--- a/protocols/bgp/packet/nlri.go
+++ b/protocols/bgp/packet/nlri.go
@@ -9,6 +9,13 @@ import (
 	"github.com/taktv6/tflow2/convert"
 )
 
+type NLRI struct {
+	PathIdentifier uint32
+	IP             uint32
+	Pfxlen         uint8
+	Next           *NLRI
+}
+
 func decodeNLRIs(buf *bytes.Buffer, length uint16) (*NLRI, error) {
 	var ret *NLRI
 	var eol *NLRI
@@ -65,7 +72,7 @@ func (n *NLRI) serialize(buf *bytes.Buffer) uint8 {
 	a := convert.Uint32Byte(n.IP)
 
 	addr := [4]byte{a[0], a[1], a[2], a[3]}
-	nBytes := bytesInAddr(n.Pfxlen)
+	nBytes := BytesInAddr(n.Pfxlen)
 
 	buf.WriteByte(n.Pfxlen)
 	buf.Write(addr[:nBytes])
@@ -73,19 +80,20 @@ func (n *NLRI) serialize(buf *bytes.Buffer) uint8 {
 	return nBytes + 1
 }
 
-func (n *NLRIAddPath) serialize(buf *bytes.Buffer) uint8 {
+func (n *NLRI) serializeAddPath(buf *bytes.Buffer) uint8 {
 	a := convert.Uint32Byte(n.IP)
 
 	addr := [4]byte{a[0], a[1], a[2], a[3]}
-	nBytes := bytesInAddr(n.Pfxlen)
+	nBytes := BytesInAddr(n.Pfxlen)
 
 	buf.Write(convert.Uint32Byte(n.PathIdentifier))
 	buf.WriteByte(n.Pfxlen)
 	buf.Write(addr[:nBytes])
 
-	return nBytes + 1
+	return nBytes + 4
 }
 
-func bytesInAddr(pfxlen uint8) uint8 {
+// BytesInAddr gets the amount of bytes needed to encode an NLRI of prefix length pfxlen
+func BytesInAddr(pfxlen uint8) uint8 {
 	return uint8(math.Ceil(float64(pfxlen) / 8))
 }
diff --git a/protocols/bgp/packet/nlri_test.go b/protocols/bgp/packet/nlri_test.go
index 17a17dd3bf5ae997fa4676de27a5db5a4376907b..4cefc5adb3a664564ac201a81afc5efaebe015a8 100644
--- a/protocols/bgp/packet/nlri_test.go
+++ b/protocols/bgp/packet/nlri_test.go
@@ -162,7 +162,7 @@ func TestBytesInAddr(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res := bytesInAddr(test.input)
+		res := BytesInAddr(test.input)
 		if res != test.expected {
 			t.Errorf("Unexpected result for test %q: %d", test.name, res)
 		}
@@ -212,12 +212,12 @@ func TestNLRISerialize(t *testing.T) {
 func TestNLRIAddPathSerialize(t *testing.T) {
 	tests := []struct {
 		name     string
-		nlri     *NLRIAddPath
+		nlri     *NLRI
 		expected []byte
 	}{
 		{
 			name: "Test #1",
-			nlri: &NLRIAddPath{
+			nlri: &NLRI{
 				PathIdentifier: 100,
 				IP:             strAddr("1.2.3.0"),
 				Pfxlen:         25,
@@ -226,7 +226,7 @@ func TestNLRIAddPathSerialize(t *testing.T) {
 		},
 		{
 			name: "Test #2",
-			nlri: &NLRIAddPath{
+			nlri: &NLRI{
 				PathIdentifier: 100,
 				IP:             strAddr("1.2.3.0"),
 				Pfxlen:         24,
@@ -235,7 +235,7 @@ func TestNLRIAddPathSerialize(t *testing.T) {
 		},
 		{
 			name: "Test #3",
-			nlri: &NLRIAddPath{
+			nlri: &NLRI{
 				PathIdentifier: 100,
 				IP:             strAddr("100.200.128.0"),
 				Pfxlen:         17,
@@ -246,7 +246,7 @@ func TestNLRIAddPathSerialize(t *testing.T) {
 
 	for _, test := range tests {
 		buf := bytes.NewBuffer(nil)
-		test.nlri.serialize(buf)
+		test.nlri.serializeAddPath(buf)
 		res := buf.Bytes()
 		assert.Equal(t, test.expected, res)
 	}
diff --git a/protocols/bgp/packet/options.go b/protocols/bgp/packet/options.go
deleted file mode 100644
index b2d35bc60cbe17490f94314c28da26de9a19e40a..0000000000000000000000000000000000000000
--- a/protocols/bgp/packet/options.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package packet
-
-type Options struct {
-	Supports4OctetASN bool
-}
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
index 748e36535f049ddb31955e8e86078aef209641cd..000f0d8e95fe82ca67c8d57e9601dc090e58ca6f 100644
--- a/protocols/bgp/packet/path_attributes.go
+++ b/protocols/bgp/packet/path_attributes.go
@@ -4,10 +4,12 @@ import (
 	"bytes"
 	"fmt"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
+	"github.com/bio-routing/bio-rd/route"
 	"github.com/taktv6/tflow2/convert"
 )
 
-func decodePathAttrs(buf *bytes.Buffer, tpal uint16, opt *Options) (*PathAttribute, error) {
+func decodePathAttrs(buf *bytes.Buffer, tpal uint16, opt *types.Options) (*PathAttribute, error) {
 	var ret *PathAttribute
 	var eol *PathAttribute
 	var pa *PathAttribute
@@ -34,7 +36,7 @@ func decodePathAttrs(buf *bytes.Buffer, tpal uint16, opt *Options) (*PathAttribu
 	return ret, nil
 }
 
-func decodePathAttr(buf *bytes.Buffer, opt *Options) (pa *PathAttribute, consumed uint16, err error) {
+func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, consumed uint16, err error) {
 	pa = &PathAttribute{}
 
 	err = decodePathAttrFlags(buf, pa)
@@ -143,27 +145,28 @@ func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error {
 }
 
 func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer, asnLength uint8) error {
-	pa.Value = make(ASPath, 0)
+	pa.Value = make(types.ASPath, 0)
 	p := uint16(0)
 	for p < pa.Length {
-		segment := ASPathSegment{}
+		segment := types.ASPathSegment{}
+		count := uint8(0)
 
-		err := decode(buf, []interface{}{&segment.Type, &segment.Count})
+		err := decode(buf, []interface{}{&segment.Type, &count})
 		if err != nil {
 			return err
 		}
 		p += 2
 
-		if segment.Type != ASSet && segment.Type != ASSequence {
+		if segment.Type != types.ASSet && segment.Type != types.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)
+		if count == 0 {
+			return fmt.Errorf("Invalid AS Path segment length: %d", count)
 		}
 
-		segment.ASNs = make([]uint32, segment.Count)
-		for i := uint8(0); i < segment.Count; i++ {
+		segment.ASNs = make([]uint32, count)
+		for i := uint8(0); i < count; i++ {
 			asn, err := pa.decodeASN(buf, asnLength)
 			if err != nil {
 				return err
@@ -173,7 +176,7 @@ func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer, asnLength uint8) error
 			segment.ASNs[i] = asn
 		}
 
-		pa.Value = append(pa.Value.(ASPath), segment)
+		pa.Value = append(pa.Value.(types.ASPath), segment)
 	}
 
 	return nil
@@ -271,10 +274,10 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error {
 	}
 
 	count := pa.Length / LargeCommunityLen
-	coms := make([]LargeCommunity, count)
+	coms := make([]types.LargeCommunity, count)
 
 	for i := uint16(0); i < count; i++ {
-		com := LargeCommunity{}
+		com := types.LargeCommunity{}
 
 		v, err := read4BytesAsUint32(buf)
 		if err != nil {
@@ -342,6 +345,7 @@ func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) {
 	return bytesRead, nil
 }
 
+// Copy create a copy of a path attribute
 func (pa *PathAttribute) Copy() *PathAttribute {
 	return &PathAttribute{
 		ExtendedLength: pa.ExtendedLength,
@@ -368,28 +372,29 @@ func dumpNBytes(buf *bytes.Buffer, n uint16) error {
 	return nil
 }
 
-func (pa *PathAttribute) serialize(buf *bytes.Buffer, opt *Options) uint8 {
-	pathAttrLen := uint8(0)
+// Serialize serializes a path attribute
+func (pa *PathAttribute) Serialize(buf *bytes.Buffer, opt *types.Options) uint16 {
+	pathAttrLen := uint16(0)
 
 	switch pa.TypeCode {
 	case OriginAttr:
-		pathAttrLen = pa.serializeOrigin(buf)
+		pathAttrLen = uint16(pa.serializeOrigin(buf))
 	case ASPathAttr:
-		pathAttrLen = pa.serializeASPath(buf, opt)
+		pathAttrLen = uint16(pa.serializeASPath(buf, opt))
 	case NextHopAttr:
-		pathAttrLen = pa.serializeNextHop(buf)
+		pathAttrLen = uint16(pa.serializeNextHop(buf))
 	case MEDAttr:
-		pathAttrLen = pa.serializeMED(buf)
+		pathAttrLen = uint16(pa.serializeMED(buf))
 	case LocalPrefAttr:
-		pathAttrLen = pa.serializeLocalpref(buf)
+		pathAttrLen = uint16(pa.serializeLocalpref(buf))
 	case AtomicAggrAttr:
-		pathAttrLen = pa.serializeAtomicAggregate(buf)
+		pathAttrLen = uint16(pa.serializeAtomicAggregate(buf))
 	case AggregatorAttr:
-		pathAttrLen = pa.serializeAggregator(buf)
+		pathAttrLen = uint16(pa.serializeAggregator(buf))
 	case CommunitiesAttr:
-		pathAttrLen = pa.serializeCommunities(buf)
+		pathAttrLen = uint16(pa.serializeCommunities(buf))
 	case LargeCommunitiesAttr:
-		pathAttrLen = pa.serializeLargeCommunities(buf)
+		pathAttrLen = uint16(pa.serializeLargeCommunities(buf))
 	default:
 		pathAttrLen = pa.serializeUnknownAttribute(buf)
 	}
@@ -408,7 +413,7 @@ func (pa *PathAttribute) serializeOrigin(buf *bytes.Buffer) uint8 {
 	return 4
 }
 
-func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *Options) uint8 {
+func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *types.Options) uint8 {
 	attrFlags := uint8(0)
 	attrFlags = setTransitive(attrFlags)
 	buf.WriteByte(attrFlags)
@@ -421,7 +426,7 @@ func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *Options) uint8
 
 	length := uint8(0)
 	segmentsBuf := bytes.NewBuffer(nil)
-	for _, segment := range pa.Value.(ASPath) {
+	for _, segment := range pa.Value.(types.ASPath) {
 		segmentsBuf.WriteByte(segment.Type)
 		segmentsBuf.WriteByte(uint8(len(segment.ASNs)))
 
@@ -432,7 +437,6 @@ func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *Options) uint8
 				segmentsBuf.Write(convert.Uint32Byte(asn))
 			}
 		}
-		fmt.Println(segment.ASNs)
 		length += 2 + uint8(len(segment.ASNs))*asnLength
 	}
 
@@ -523,7 +527,7 @@ func (pa *PathAttribute) serializeCommunities(buf *bytes.Buffer) uint8 {
 }
 
 func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 {
-	coms := pa.Value.([]LargeCommunity)
+	coms := pa.Value.([]types.LargeCommunity)
 	if len(coms) == 0 {
 		return 0
 	}
@@ -548,21 +552,30 @@ func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 {
 	return length
 }
 
-func (pa *PathAttribute) serializeUnknownAttribute(buf *bytes.Buffer) uint8 {
+func (pa *PathAttribute) serializeUnknownAttribute(buf *bytes.Buffer) uint16 {
 	attrFlags := uint8(0)
 	if pa.Optional {
 		attrFlags = setOptional(attrFlags)
 	}
+	if pa.ExtendedLength {
+		attrFlags = setExtendedLength(attrFlags)
+	}
 	attrFlags = setTransitive(attrFlags)
 
 	buf.WriteByte(attrFlags)
 	buf.WriteByte(pa.TypeCode)
 
 	b := pa.Value.([]byte)
-	buf.WriteByte(uint8(len(b)))
+	if pa.ExtendedLength {
+		l := len(b)
+		buf.WriteByte(uint8(l >> 8))
+		buf.WriteByte(uint8(l & 0x0000FFFF))
+	} else {
+		buf.WriteByte(uint8(len(b)))
+	}
 	buf.Write(b)
 
-	return uint8(len(b) + 2)
+	return uint16(len(b) + 2)
 }
 
 func fourBytesToUint32(address [4]byte) uint32 {
@@ -581,3 +594,70 @@ func read4BytesAsUint32(buf *bytes.Buffer) (uint32, error) {
 
 	return fourBytesToUint32(b), nil
 }
+
+// AddOptionalPathAttributes adds optional path attributes to linked list pa
+func (pa *PathAttribute) AddOptionalPathAttributes(p *route.Path) *PathAttribute {
+	current := pa
+
+	if len(p.BGPPath.Communities) > 0 {
+		communities := &PathAttribute{
+			TypeCode: CommunitiesAttr,
+			Value:    p.BGPPath.Communities,
+		}
+		current.Next = communities
+		current = communities
+	}
+
+	if len(p.BGPPath.LargeCommunities) > 0 {
+		largeCommunities := &PathAttribute{
+			TypeCode: LargeCommunitiesAttr,
+			Value:    p.BGPPath.LargeCommunities,
+		}
+		current.Next = largeCommunities
+		current = largeCommunities
+	}
+
+	return current
+}
+
+// PathAttributes converts a path object into a linked list of path attributes
+func PathAttributes(p *route.Path) (*PathAttribute, error) {
+	asPath := &PathAttribute{
+		TypeCode: ASPathAttr,
+		Value:    p.BGPPath.ASPath,
+	}
+
+	origin := &PathAttribute{
+		TypeCode: OriginAttr,
+		Value:    p.BGPPath.Origin,
+	}
+	asPath.Next = origin
+
+	nextHop := &PathAttribute{
+		TypeCode: NextHopAttr,
+		Value:    p.BGPPath.NextHop,
+	}
+	origin.Next = nextHop
+
+	localPref := &PathAttribute{
+		TypeCode: LocalPrefAttr,
+		Value:    p.BGPPath.LocalPref,
+	}
+	nextHop.Next = localPref
+
+	optionals := localPref.AddOptionalPathAttributes(p)
+
+	last := optionals
+	for _, unknownAttr := range p.BGPPath.UnknownAttributes {
+		last.Next = &PathAttribute{
+			TypeCode:   unknownAttr.TypeCode,
+			Optional:   unknownAttr.Optional,
+			Transitive: unknownAttr.Transitive,
+			Partial:    unknownAttr.Partial,
+			Value:      unknownAttr.Value,
+		}
+		last = last.Next
+	}
+
+	return asPath, nil
+}
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
index fbf1d4806e127b1b2e4b15255a11e018bd00bbaf..93a0b2e515f0dfb9f0a0dde543085a37fb3c52c3 100644
--- a/protocols/bgp/packet/path_attributes_test.go
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"testing"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -50,7 +51,7 @@ func TestDecodePathAttrs(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &Options{})
+		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &types.Options{})
 
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error did not happen for test %q", test.name)
@@ -173,7 +174,7 @@ func TestDecodePathAttr(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &Options{})
+		res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &types.Options{})
 
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error did not happen for test %q", test.name)
@@ -277,10 +278,9 @@ func TestDecodeASPath(t *testing.T) {
 			wantFail: false,
 			expected: &PathAttribute{
 				Length: 10,
-				Value: ASPath{
-					ASPathSegment{
-						Type:  2,
-						Count: 4,
+				Value: types.ASPath{
+					types.ASPathSegment{
+						Type: 2,
 						ASNs: []uint32{
 							100, 200, 222, 240,
 						},
@@ -298,10 +298,9 @@ func TestDecodeASPath(t *testing.T) {
 			wantFail: false,
 			expected: &PathAttribute{
 				Length: 8,
-				Value: ASPath{
-					ASPathSegment{
-						Type:  1,
-						Count: 3,
+				Value: types.ASPath{
+					types.ASPathSegment{
+						Type: 1,
 						ASNs: []uint32{
 							100, 222, 240,
 						},
@@ -320,10 +319,9 @@ func TestDecodeASPath(t *testing.T) {
 			use4OctetASNs: true,
 			expected: &PathAttribute{
 				Length: 14,
-				Value: ASPath{
-					ASPathSegment{
-						Type:  1,
-						Count: 3,
+				Value: types.ASPath{
+					types.ASPathSegment{
+						Type: 1,
 						ASNs: []uint32{
 							100, 222, 240,
 						},
@@ -636,7 +634,7 @@ func TestDecodeLargeCommunity(t *testing.T) {
 			wantFail: false,
 			expected: &PathAttribute{
 				Length: 24,
-				Value: []LargeCommunity{
+				Value: []types.LargeCommunity{
 					{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
@@ -656,7 +654,7 @@ func TestDecodeLargeCommunity(t *testing.T) {
 			wantFail: false,
 			expected: &PathAttribute{
 				Length: 0,
-				Value:  []LargeCommunity{},
+				Value:  []types.LargeCommunity{},
 			},
 		},
 	}
@@ -1175,7 +1173,7 @@ func TestSerializeASPath(t *testing.T) {
 			name: "Test #1",
 			input: &PathAttribute{
 				TypeCode: ASPathAttr,
-				Value: ASPath{
+				Value: types.ASPath{
 					{
 						Type: 2, // Sequence
 						ASNs: []uint32{
@@ -1200,7 +1198,7 @@ func TestSerializeASPath(t *testing.T) {
 			name: "32bit ASN",
 			input: &PathAttribute{
 				TypeCode: ASPathAttr,
-				Value: ASPath{
+				Value: types.ASPath{
 					{
 						Type: 2, // Sequence
 						ASNs: []uint32{
@@ -1229,7 +1227,7 @@ func TestSerializeASPath(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			buf := bytes.NewBuffer(nil)
-			opt := &Options{
+			opt := &types.Options{
 				Supports4OctetASN: test.use32BitASN,
 			}
 			n := test.input.serializeASPath(buf, opt)
@@ -1253,7 +1251,7 @@ func TestSerializeLargeCommunities(t *testing.T) {
 			name: "2 large communities",
 			input: &PathAttribute{
 				TypeCode: LargeCommunitiesAttr,
-				Value: []LargeCommunity{
+				Value: []types.LargeCommunity{
 					{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
@@ -1278,7 +1276,7 @@ func TestSerializeLargeCommunities(t *testing.T) {
 			name: "empty list of communities",
 			input: &PathAttribute{
 				TypeCode: LargeCommunitiesAttr,
-				Value:    []LargeCommunity{},
+				Value:    []types.LargeCommunity{},
 			},
 			expected:    []byte{},
 			expectedLen: 0,
@@ -1350,7 +1348,7 @@ func TestSerializeUnknownAttribute(t *testing.T) {
 		name        string
 		input       *PathAttribute
 		expected    []byte
-		expectedLen uint8
+		expectedLen uint16
 	}{
 		{
 			name: "Arbritary attribute",
@@ -1367,6 +1365,36 @@ func TestSerializeUnknownAttribute(t *testing.T) {
 			},
 			expectedLen: 6,
 		},
+		{
+			name: "Extended length",
+			input: &PathAttribute{
+				TypeCode:       200,
+				Value:          make([]byte, 256),
+				Transitive:     true,
+				ExtendedLength: true,
+			},
+			expected: []byte{
+				80,   // Attribute flags
+				200,  // Type
+				1, 0, // Length
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Payload
+			},
+			expectedLen: 258,
+		},
 	}
 
 	for _, test := range tests {
@@ -1459,7 +1487,7 @@ func TestSerialize(t *testing.T) {
 					Value:    uint8(0),
 					Next: &PathAttribute{
 						TypeCode: ASPathAttr,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
 								Type: 2,
 								ASNs: []uint32{100, 155, 200},
@@ -1559,7 +1587,9 @@ func TestSerialize(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		opt := &Options{}
+		opt := &types.Options{
+			AddPathRX: false,
+		}
 		res, err := test.msg.SerializeUpdate(opt)
 		if err != nil {
 			if test.wantFail {
@@ -1582,14 +1612,14 @@ func TestSerialize(t *testing.T) {
 func TestSerializeAddPath(t *testing.T) {
 	tests := []struct {
 		name     string
-		msg      *BGPUpdateAddPath
+		msg      *BGPUpdate
 		expected []byte
 		wantFail bool
 	}{
 		{
 			name: "Withdraw only",
-			msg: &BGPUpdateAddPath{
-				WithdrawnRoutes: &NLRIAddPath{
+			msg: &BGPUpdate{
+				WithdrawnRoutes: &NLRI{
 					PathIdentifier: 257,
 					IP:             strAddr("100.110.120.0"),
 					Pfxlen:         24,
@@ -1607,8 +1637,8 @@ func TestSerializeAddPath(t *testing.T) {
 		},
 		{
 			name: "NLRI only",
-			msg: &BGPUpdateAddPath{
-				NLRI: &NLRIAddPath{
+			msg: &BGPUpdate{
+				NLRI: &NLRI{
 					PathIdentifier: 257,
 					IP:             strAddr("100.110.128.0"),
 					Pfxlen:         17,
@@ -1626,7 +1656,7 @@ func TestSerializeAddPath(t *testing.T) {
 		},
 		{
 			name: "Path Attributes only",
-			msg: &BGPUpdateAddPath{
+			msg: &BGPUpdate{
 				PathAttributes: &PathAttribute{
 					Optional:   true,
 					Transitive: true,
@@ -1648,11 +1678,11 @@ func TestSerializeAddPath(t *testing.T) {
 		},
 		{
 			name: "Full test",
-			msg: &BGPUpdateAddPath{
-				WithdrawnRoutes: &NLRIAddPath{
+			msg: &BGPUpdate{
+				WithdrawnRoutes: &NLRI{
 					IP:     strAddr("10.0.0.0"),
 					Pfxlen: 8,
-					Next: &NLRIAddPath{
+					Next: &NLRI{
 						IP:     strAddr("192.168.0.0"),
 						Pfxlen: 16,
 					},
@@ -1662,7 +1692,7 @@ func TestSerializeAddPath(t *testing.T) {
 					Value:    uint8(0),
 					Next: &PathAttribute{
 						TypeCode: ASPathAttr,
-						Value: ASPath{
+						Value: types.ASPath{
 							{
 								Type: 2,
 								ASNs: []uint32{100, 155, 200},
@@ -1693,10 +1723,10 @@ func TestSerializeAddPath(t *testing.T) {
 						},
 					},
 				},
-				NLRI: &NLRIAddPath{
+				NLRI: &NLRI{
 					IP:     strAddr("8.8.8.0"),
 					Pfxlen: 24,
-					Next: &NLRIAddPath{
+					Next: &NLRI{
 						IP:     strAddr("185.65.240.0"),
 						Pfxlen: 22,
 					},
@@ -1766,7 +1796,9 @@ func TestSerializeAddPath(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		opt := &Options{}
+		opt := &types.Options{
+			AddPathRX: true,
+		}
 		res, err := test.msg.SerializeUpdate(opt)
 		if err != nil {
 			if test.wantFail {
diff --git a/protocols/bgp/packet/update.go b/protocols/bgp/packet/update.go
new file mode 100644
index 0000000000000000000000000000000000000000..55b0cc0b9099076edbaef35fcfab1cfc83443330
--- /dev/null
+++ b/protocols/bgp/packet/update.go
@@ -0,0 +1,147 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
+	"github.com/taktv6/tflow2/convert"
+)
+
+type BGPUpdate struct {
+	WithdrawnRoutesLen uint16
+	WithdrawnRoutes    *NLRI
+	TotalPathAttrLen   uint16
+	PathAttributes     *PathAttribute
+	NLRI               *NLRI
+}
+
+// SerializeUpdate serializes an BGPUpdate to wire format
+func (b *BGPUpdate) SerializeUpdate(opt *types.Options) ([]byte, error) {
+	budget := MaxLen - MinLen
+	nlriLen := 0
+	buf := bytes.NewBuffer(nil)
+
+	withdrawBuf := bytes.NewBuffer(nil)
+	for withdraw := b.WithdrawnRoutes; withdraw != nil; withdraw = withdraw.Next {
+		if opt.AddPathRX {
+			nlriLen = int(withdraw.serializeAddPath(withdrawBuf))
+		} else {
+			nlriLen = int(withdraw.serialize(withdrawBuf))
+		}
+
+		budget -= nlriLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	pathAttributesBuf := bytes.NewBuffer(nil)
+	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
+		paLen := int(pa.Serialize(pathAttributesBuf, opt))
+		budget -= paLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	nlriBuf := bytes.NewBuffer(nil)
+	for nlri := b.NLRI; nlri != nil; nlri = nlri.Next {
+		if opt.AddPathRX {
+			nlriLen = int(nlri.serializeAddPath(nlriBuf))
+		} else {
+			nlriLen = int(nlri.serialize(nlriBuf))
+		}
+
+		budget -= nlriLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	withdrawnRoutesLen := withdrawBuf.Len()
+	if withdrawnRoutesLen > 65535 {
+		return nil, fmt.Errorf("Invalid Withdrawn Routes Length: %d", withdrawnRoutesLen)
+	}
+
+	totalPathAttributesLen := pathAttributesBuf.Len()
+	if totalPathAttributesLen > 65535 {
+		return nil, fmt.Errorf("Invalid Total Path Attribute Length: %d", totalPathAttributesLen)
+	}
+
+	totalLength := 2 + withdrawnRoutesLen + totalPathAttributesLen + 2 + nlriBuf.Len() + 19
+	if totalLength > 4096 {
+		return nil, fmt.Errorf("Update too long: %d bytes", totalLength)
+	}
+
+	serializeHeader(buf, uint16(totalLength), UpdateMsg)
+
+	buf.Write(convert.Uint16Byte(uint16(withdrawnRoutesLen)))
+	buf.Write(withdrawBuf.Bytes())
+
+	buf.Write(convert.Uint16Byte(uint16(totalPathAttributesLen)))
+	buf.Write(pathAttributesBuf.Bytes())
+
+	buf.Write(nlriBuf.Bytes())
+
+	return buf.Bytes(), nil
+}
+
+func (b *BGPUpdate) SerializeUpdateAddPath(opt *types.Options) ([]byte, error) {
+	budget := MaxLen - MinLen
+	buf := bytes.NewBuffer(nil)
+
+	withdrawBuf := bytes.NewBuffer(nil)
+	for withdraw := b.WithdrawnRoutes; withdraw != nil; withdraw = withdraw.Next {
+		nlriLen := int(withdraw.serialize(withdrawBuf))
+		budget -= nlriLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	pathAttributesBuf := bytes.NewBuffer(nil)
+	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
+		paLen := int(pa.Serialize(pathAttributesBuf, opt))
+		budget -= paLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	nlriBuf := bytes.NewBuffer(nil)
+	for nlri := b.NLRI; nlri != nil; nlri = nlri.Next {
+		nlriLen := int(nlri.serialize(nlriBuf))
+		budget -= nlriLen
+		if budget < 0 {
+			return nil, fmt.Errorf("update too long")
+		}
+	}
+
+	withdrawnRoutesLen := withdrawBuf.Len()
+	if withdrawnRoutesLen > 65535 {
+		return nil, fmt.Errorf("Invalid Withdrawn Routes Length: %d", withdrawnRoutesLen)
+	}
+
+	totalPathAttributesLen := pathAttributesBuf.Len()
+	if totalPathAttributesLen > 65535 {
+		return nil, fmt.Errorf("Invalid Total Path Attribute Length: %d", totalPathAttributesLen)
+	}
+
+	totalLength := 2 + withdrawnRoutesLen + totalPathAttributesLen + 2 + nlriBuf.Len() + 19
+	if totalLength > 4096 {
+		return nil, fmt.Errorf("Update too long: %d bytes", totalLength)
+	}
+
+	serializeHeader(buf, uint16(totalLength), UpdateMsg)
+
+	buf.Write(convert.Uint16Byte(uint16(withdrawnRoutesLen)))
+	buf.Write(withdrawBuf.Bytes())
+
+	buf.Write(convert.Uint16Byte(uint16(totalPathAttributesLen)))
+	buf.Write(pathAttributesBuf.Bytes())
+
+	buf.Write(nlriBuf.Bytes())
+
+	return buf.Bytes(), nil
+}
diff --git a/protocols/bgp/server/BUILD.bazel b/protocols/bgp/server/BUILD.bazel
index 89926f590b80e2c43dab3aa54a8a2d0f285e1189..e2e0edbc799e433d18180c5143a12536ef108aa9 100644
--- a/protocols/bgp/server/BUILD.bazel
+++ b/protocols/bgp/server/BUILD.bazel
@@ -19,7 +19,6 @@ go_library(
         "tcplistener.go",
         "update_helper.go",
         "update_sender.go",
-        "update_sender_add_path.go",
         "util.go",
         "withdraw.go",
     ],
@@ -29,6 +28,7 @@ go_library(
         "//config:go_default_library",
         "//net:go_default_library",
         "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//route:go_default_library",
         "//routingtable:go_default_library",
         "//routingtable/adjRIBIn:go_default_library",
@@ -42,6 +42,7 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "fsm_established_test.go",
         "fsm_open_sent_test.go",
         "fsm_test.go",
         "server_test.go",
diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go
index cf970b40483511c2ecafc1d36597f66117de06ef..e85bda8cc21d6c47ac2c300639177aab8623859f 100644
--- a/protocols/bgp/server/fsm.go
+++ b/protocols/bgp/server/fsm.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/routingtable"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	log "github.com/sirupsen/logrus"
@@ -54,10 +55,7 @@ type FSM struct {
 	msgRecvFailCh chan error
 	stopMsgRecvCh chan struct{}
 
-	capAddPathSend bool
-	capAddPathRecv bool
-
-	options *packet.Options
+	options *types.Options
 
 	local net.IP
 
@@ -65,7 +63,7 @@ type FSM struct {
 	adjRIBIn        routingtable.RouteTableClient
 	adjRIBOut       routingtable.RouteTableClient
 	rib             *locRIB.LocRIB
-	updateSender    routingtable.RouteTableClient
+	updateSender    *UpdateSender
 
 	neighborID uint32
 	state      state
@@ -102,7 +100,7 @@ func newFSM2(peer *peer) *FSM {
 		msgRecvFailCh:    make(chan error),
 		stopMsgRecvCh:    make(chan struct{}),
 		rib:              peer.rib,
-		options:          &packet.Options{},
+		options:          &types.Options{},
 	}
 }
 
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index fdb6ea30a1463ad06e0cb44507205eed71d7c75b..20ae668232984fead709cbfae9cceeecfec97217 100644
--- a/protocols/bgp/server/fsm_established.go
+++ b/protocols/bgp/server/fsm_established.go
@@ -7,6 +7,7 @@ import (
 
 	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/bio-routing/bio-rd/routingtable"
 	"github.com/bio-routing/bio-rd/routingtable/adjRIBIn"
@@ -77,20 +78,21 @@ func (s *establishedState) init() error {
 		LocalASN:          s.fsm.peer.localASN,
 		RouteServerClient: s.fsm.peer.routeServerClient,
 		LocalAddress:      bnet.IPv4ToUint32(hostIP),
-		CapAddPathRX:      s.fsm.capAddPathSend,
+		CapAddPathRX:      s.fsm.options.AddPathRX,
 	}
 
 	s.fsm.adjRIBOut = adjRIBOut.New(n, s.fsm.peer.exportFilter)
 	clientOptions := routingtable.ClientOptions{
 		BestOnly: true,
 	}
-	if s.fsm.capAddPathSend {
+	if s.fsm.options.AddPathRX {
 		s.fsm.updateSender = newUpdateSenderAddPath(s.fsm)
 		clientOptions = s.fsm.peer.addPathSend
 	} else {
 		s.fsm.updateSender = newUpdateSender(s.fsm)
 	}
 
+	s.fsm.updateSender.Start()
 	s.fsm.adjRIBOut.Register(s.fsm.updateSender)
 	s.fsm.rib.RegisterWithOptions(s.fsm.adjRIBOut, clientOptions)
 
@@ -103,6 +105,7 @@ func (s *establishedState) uninit() {
 	s.fsm.adjRIBIn.Unregister(s.fsm.rib)
 	s.fsm.rib.Unregister(s.fsm.adjRIBOut)
 	s.fsm.adjRIBOut.Unregister(s.fsm.updateSender)
+	s.fsm.updateSender.Destroy()
 
 	s.fsm.adjRIBIn = nil
 	s.fsm.adjRIBOut = nil
@@ -228,8 +231,6 @@ func (s *establishedState) updates(u *packet.BGPUpdate) {
 }
 
 func (s *establishedState) processAttributes(attrs *packet.PathAttribute, path *route.Path) {
-	var currentUnknown *packet.PathAttribute
-
 	for pa := attrs; pa != nil; pa = pa.Next {
 		switch pa.TypeCode {
 		case packet.OriginAttr:
@@ -241,33 +242,35 @@ func (s *establishedState) processAttributes(attrs *packet.PathAttribute, path *
 		case packet.NextHopAttr:
 			path.BGPPath.NextHop = pa.Value.(uint32)
 		case packet.ASPathAttr:
-			path.BGPPath.ASPath = pa.Value.(packet.ASPath)
+			path.BGPPath.ASPath = pa.Value.(types.ASPath)
 			path.BGPPath.ASPathLen = path.BGPPath.ASPath.Length()
 		case packet.CommunitiesAttr:
 			path.BGPPath.Communities = pa.Value.([]uint32)
 		case packet.LargeCommunitiesAttr:
-			path.BGPPath.LargeCommunities = pa.Value.([]packet.LargeCommunity)
+			path.BGPPath.LargeCommunities = pa.Value.([]types.LargeCommunity)
 		default:
-			currentUnknown = s.processUnknownAttribute(pa, currentUnknown)
-			if path.BGPPath.UnknownAttributes == nil {
-				path.BGPPath.UnknownAttributes = currentUnknown
+			unknownAttr := s.processUnknownAttribute(pa)
+			if unknownAttr != nil {
+				path.BGPPath.UnknownAttributes = append(path.BGPPath.UnknownAttributes, *unknownAttr)
 			}
 		}
 	}
 }
 
-func (s *establishedState) processUnknownAttribute(attr, current *packet.PathAttribute) *packet.PathAttribute {
+func (s *establishedState) processUnknownAttribute(attr *packet.PathAttribute) *types.UnknownPathAttribute {
 	if !attr.Transitive {
-		return current
+		return nil
 	}
 
-	p := attr.Copy()
-	if current == nil {
-		return p
+	u := &types.UnknownPathAttribute{
+		Transitive: true,
+		Optional:   attr.Optional,
+		Partial:    attr.Partial,
+		TypeCode:   attr.TypeCode,
+		Value:      attr.Value.([]byte),
 	}
 
-	current.Next = p
-	return p
+	return u
 }
 
 func (s *establishedState) keepaliveReceived() (state, string) {
diff --git a/protocols/bgp/server/fsm_established_test.go b/protocols/bgp/server/fsm_established_test.go
index 4e92e4eede30ed4b0dfedeb1217e5d11962c6252..61103d99c133e01e47ab923e81d121f5acc48018 100644
--- a/protocols/bgp/server/fsm_established_test.go
+++ b/protocols/bgp/server/fsm_established_test.go
@@ -4,11 +4,12 @@ import (
 	"testing"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestProcessAttribues(t *testing.T) {
+func TestProcessAttributes(t *testing.T) {
 	unknown3 := &packet.PathAttribute{
 		Transitive: true,
 		TypeCode:   100,
@@ -33,11 +34,10 @@ func TestProcessAttribues(t *testing.T) {
 	asPath := &packet.PathAttribute{
 		Transitive: true,
 		TypeCode:   packet.ASPathAttr,
-		Value: packet.ASPath{
-			packet.ASPathSegment{
-				Count: 0,
-				Type:  packet.ASSequence,
-				ASNs:  []uint32{},
+		Value: types.ASPath{
+			types.ASPathSegment{
+				Type: types.ASSequence,
+				ASNs: []uint32{},
 			},
 		},
 		Next: unknown1,
@@ -54,12 +54,12 @@ func TestProcessAttribues(t *testing.T) {
 	expectedValues := [][]byte{[]byte{5, 6}, []byte{1, 2, 3, 4}}
 
 	i := 0
-	for attr := p.BGPPath.UnknownAttributes; attr != nil; attr = attr.Next {
+	for _, attr := range p.BGPPath.UnknownAttributes {
 		assert.Equal(t, true, attr.Transitive, "Transitive")
 		assert.Equal(t, expectedCodes[i], attr.TypeCode, "Code")
 		assert.Equal(t, expectedValues[i], attr.Value, "Value")
 		i++
 	}
 
-	assert.Equal(t, i, 2, "Count")
+	assert.Equal(t, 2, i, "Count")
 }
diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go
index 121ea883a1c4d2de0e9384e275c394608b8665b7..928ed450b7b2a4cb230acaca5db418af80923c11 100644
--- a/protocols/bgp/server/fsm_open_sent.go
+++ b/protocols/bgp/server/fsm_open_sent.go
@@ -183,18 +183,18 @@ func (s *openSentState) processAddPathCapability(addPathCap packet.AddPathCapabi
 	switch addPathCap.SendReceive {
 	case packet.AddPathReceive:
 		if !s.fsm.peer.addPathSend.BestOnly {
-			s.fsm.capAddPathSend = true
+			s.fsm.options.AddPathRX = true
 		}
 	case packet.AddPathSend:
 		if s.fsm.peer.addPathRecv {
-			s.fsm.capAddPathRecv = true
+			s.fsm.options.AddPathRX = true
 		}
 	case packet.AddPathSendReceive:
 		if !s.fsm.peer.addPathSend.BestOnly {
-			s.fsm.capAddPathSend = true
+			s.fsm.options.AddPathRX = true
 		}
 		if s.fsm.peer.addPathRecv {
-			s.fsm.capAddPathRecv = true
+			s.fsm.options.AddPathRX = true
 		}
 	}
 }
diff --git a/protocols/bgp/server/fsm_test.go b/protocols/bgp/server/fsm_test.go
index a6f6bd7a19e2c571c374df2daa59914d02640a66..9cff29b3706dc30d11a1017a7d963d30bafcb1f2 100644
--- a/protocols/bgp/server/fsm_test.go
+++ b/protocols/bgp/server/fsm_test.go
@@ -88,6 +88,7 @@ func TestFSM100Updates(t *testing.T) {
 		}
 
 		fsmA.msgRecvCh <- update
+
 	}
 
 	time.Sleep(time.Second)
diff --git a/protocols/bgp/server/update_helper.go b/protocols/bgp/server/update_helper.go
index 1fe4fdbaf05d6e483047712861874365a3af74a3..2cb82934a0a12d2f25243db2bc82cd60fca10e02 100644
--- a/protocols/bgp/server/update_helper.go
+++ b/protocols/bgp/server/update_helper.go
@@ -4,72 +4,11 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	log "github.com/sirupsen/logrus"
 )
 
-func pathAttribues(p *route.Path) (*packet.PathAttribute, error) {
-	asPath := &packet.PathAttribute{
-		TypeCode: packet.ASPathAttr,
-		Value:    p.BGPPath.ASPath,
-	}
-
-	origin := &packet.PathAttribute{
-		TypeCode: packet.OriginAttr,
-		Value:    p.BGPPath.Origin,
-	}
-	asPath.Next = origin
-
-	nextHop := &packet.PathAttribute{
-		TypeCode: packet.NextHopAttr,
-		Value:    p.BGPPath.NextHop,
-	}
-	origin.Next = nextHop
-
-	localPref := &packet.PathAttribute{
-		TypeCode: packet.LocalPrefAttr,
-		Value:    p.BGPPath.LocalPref,
-	}
-	nextHop.Next = localPref
-
-	if p.BGPPath != nil {
-		optionals := addOptionalPathAttribues(p, localPref)
-		optionals.Next = p.BGPPath.UnknownAttributes
-	}
-
-	return asPath, nil
-}
-
-func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) *packet.PathAttribute {
-	current := parent
-
-	if len(p.BGPPath.Communities) > 0 {
-		communities := &packet.PathAttribute{
-			TypeCode: packet.CommunitiesAttr,
-			Value:    p.BGPPath.Communities,
-		}
-		current.Next = communities
-		current = communities
-	}
-
-	if len(p.BGPPath.LargeCommunities) > 0 {
-		largeCommunities := &packet.PathAttribute{
-			TypeCode: packet.LargeCommunitiesAttr,
-			Value:    p.BGPPath.LargeCommunities,
-		}
-		current.Next = largeCommunities
-		current = largeCommunities
-	}
-
-	return current
-}
-
-type serializeAbleUpdate interface {
-	SerializeUpdate(opt *packet.Options) ([]byte, error)
-}
-
-func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate, opt *packet.Options) error {
+func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate, opt *types.Options) error {
 	updateBytes, err := update.SerializeUpdate(opt)
 	if err != nil {
 		log.Errorf("Unable to serialize BGP Update: %v", err)
@@ -82,3 +21,7 @@ func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate, opt *pack
 	}
 	return nil
 }
+
+type serializeAbleUpdate interface {
+	SerializeUpdate(opt *types.Options) ([]byte, error)
+}
diff --git a/protocols/bgp/server/update_helper_test.go b/protocols/bgp/server/update_helper_test.go
index d2d0a7fcf424276b6a351176cebc9a3959f9ec69..6c599f18c965d55a9a611509ea10e2c5d5ffa35e 100644
--- a/protocols/bgp/server/update_helper_test.go
+++ b/protocols/bgp/server/update_helper_test.go
@@ -1,14 +1,13 @@
 package server
 
 import (
+	"bytes"
+	"errors"
 	"io"
 	"testing"
 
-	"bytes"
-
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-
-	"errors"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/stretchr/testify/assert"
@@ -16,7 +15,7 @@ import (
 
 type failingUpdate struct{}
 
-func (f *failingUpdate) SerializeUpdate(opt *packet.Options) ([]byte, error) {
+func (f *failingUpdate) SerializeUpdate(opt *types.Options) ([]byte, error) {
 	return nil, errors.New("general error")
 }
 
@@ -94,7 +93,7 @@ func TestSerializeAndSendUpdate(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			opt := &packet.Options{}
+			opt := &types.Options{}
 			err := serializeAndSendUpdate(test.buf, test.testUpdate, opt)
 			assert.Equal(t, test.err, err)
 
diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go
index 23da78c532952a70bce63b8cc9570dba2d9e503e..078faebfde101b8d774a8785ad0a71c9dce3b159 100644
--- a/protocols/bgp/server/update_sender.go
+++ b/protocols/bgp/server/update_sender.go
@@ -1,12 +1,12 @@
 package server
 
 import (
-	"fmt"
-	"strings"
+	"sync"
+	"time"
 
 	log "github.com/sirupsen/logrus"
 
-	"github.com/bio-routing/bio-rd/net"
+	bnet "github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/bio-routing/bio-rd/routingtable"
@@ -15,59 +15,152 @@ import (
 // UpdateSender converts table changes into BGP update messages
 type UpdateSender struct {
 	routingtable.ClientManager
-	fsm  *FSM
-	iBGP bool
+	fsm       *FSM
+	iBGP      bool
+	addPath   bool
+	toSendMu  sync.Mutex
+	toSend    map[string]*pathPfxs
+	destroyCh chan struct{}
+}
+
+type pathPfxs struct {
+	path *route.Path
+	pfxs []bnet.Prefix
+}
+
+func newUpdateSenderAddPath(fsm *FSM) *UpdateSender {
+	u := newUpdateSender(fsm)
+	u.addPath = true
+	return u
 }
 
 func newUpdateSender(fsm *FSM) *UpdateSender {
 	return &UpdateSender{
-		fsm:  fsm,
-		iBGP: fsm.peer.localASN == fsm.peer.peerASN,
+		fsm:       fsm,
+		iBGP:      fsm.peer.localASN == fsm.peer.peerASN,
+		destroyCh: make(chan struct{}),
 	}
 }
 
-// AddPath serializes a new path and sends out a BGP update message
-func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error {
-	pathAttrs, err := pathAttribues(p)
-	if err != nil {
-		log.Errorf("Unable to create BGP Update: %v", err)
+// Start starts the update sender
+func (u *UpdateSender) Start() {
+	go u.sender()
+}
+
+// Destroy destroys everything (with greetings to Hatebreed)
+func (u *UpdateSender) Destroy() {
+	u.destroyCh <- struct{}{}
+}
+
+// AddPath adds path p for pfx to toSend queue
+func (u *UpdateSender) AddPath(pfx bnet.Prefix, p *route.Path) error {
+	u.toSendMu.Lock()
+
+	hash := p.BGPPath.ComputeHash()
+	if _, exists := u.toSend[hash]; exists {
+		u.toSend[hash].pfxs = append(u.toSend[hash].pfxs, pfx)
+		u.toSendMu.Unlock()
 		return nil
 	}
 
-	update := &packet.BGPUpdate{
-		PathAttributes: pathAttrs,
-		NLRI: &packet.NLRI{
-			IP:     pfx.Addr(),
-			Pfxlen: pfx.Pfxlen(),
+	u.toSend[p.BGPPath.ComputeHash()] = &pathPfxs{
+		path: p,
+		pfxs: []bnet.Prefix{
+			pfx,
 		},
 	}
 
-	return serializeAndSendUpdate(u.fsm.con, update, u.fsm.options)
+	u.toSendMu.Unlock()
+	return nil
+}
+
+// sender serializes BGP update messages
+func (u *UpdateSender) sender() {
+	ticker := time.NewTicker(time.Millisecond * 5)
+	var err error
+	var pathAttrs *packet.PathAttribute
+	var budget int
+	var nlri *packet.NLRI
+
+	for {
+		select {
+		case <-u.destroyCh:
+			return
+		case <-ticker.C:
+		}
+
+		u.toSendMu.Lock()
+
+		for key, pathNLRIs := range u.toSend {
+			budget = packet.MaxLen - int(pathNLRIs.path.BGPPath.Length())
+			pathAttrs, err = packet.PathAttributes(pathNLRIs.path)
+			if err != nil {
+				log.Errorf("Unable to get path attributes: %v", err)
+				continue
+			}
+
+			updatesPrefixes := make([][]bnet.Prefix, 1)
+			prefixes := make([]bnet.Prefix, 1)
+			for _, pfx := range pathNLRIs.pfxs {
+				budget -= int(packet.BytesInAddr(nlri.Pfxlen)) - 5
+				if budget < 0 {
+					updatesPrefixes = append(updatesPrefixes, prefixes)
+					prefixes = make([]bnet.Prefix, 1)
+				}
+
+				prefixes = append(prefixes, pfx)
+			}
+
+			delete(u.toSend, key)
+			u.toSendMu.Unlock()
+
+			u.sendUpdates(pathAttrs, updatesPrefixes, pathNLRIs.path.BGPPath.PathIdentifier)
+			u.toSendMu.Lock()
+		}
+		u.toSendMu.Unlock()
+	}
+}
+
+func (u *UpdateSender) sendUpdates(pathAttrs *packet.PathAttribute, updatePrefixes [][]bnet.Prefix, pathID uint32) {
+	var nlri *packet.NLRI
+	var err error
+
+	for _, updatePrefix := range updatePrefixes {
+		update := &packet.BGPUpdate{
+			PathAttributes: pathAttrs,
+		}
+
+		for _, pfx := range updatePrefix {
+			nlri = &packet.NLRI{
+				PathIdentifier: pathID,
+				IP:             pfx.Addr(),
+				Pfxlen:         pfx.Pfxlen(),
+				Next:           update.NLRI,
+			}
+			update.NLRI = nlri
+		}
+
+		err = serializeAndSendUpdate(u.fsm.con, update, u.fsm.options)
+		if err != nil {
+			log.Errorf("Failed to serialize and send: %v", err)
+		}
+	}
 }
 
 // RemovePath withdraws prefix `pfx` from a peer
-func (u *UpdateSender) RemovePath(pfx net.Prefix, p *route.Path) bool {
-	err := withDrawPrefixes(u.fsm.con, u.fsm.options, pfx)
+func (u *UpdateSender) RemovePath(pfx bnet.Prefix, p *route.Path) bool {
+	err := withDrawPrefixesAddPath(u.fsm.con, u.fsm.options, pfx, p)
 	return err == nil
 }
 
 // UpdateNewClient does nothing
 func (u *UpdateSender) UpdateNewClient(client routingtable.RouteTableClient) error {
-	log.Warningf("BGP Update Sender: UpdateNewClient() not supported")
+	log.Warningf("BGP Update Sender: UpdateNewClient not implemented")
 	return nil
 }
 
-// RouteCount does nothing
+// RouteCount returns the number of stored routes
 func (u *UpdateSender) RouteCount() int64 {
-	log.Warningf("BGP Update Sender: RouteCount() not supported")
+	log.Warningf("BGP Update Sender: RouteCount not implemented")
 	return 0
 }
-
-func asPathString(iBGP bool, localASN uint16, asPath string) string {
-	ret := ""
-	if iBGP {
-		ret = ret + fmt.Sprintf("%d ", localASN)
-	}
-	ret = ret + asPath
-	return strings.TrimRight(ret, " ")
-}
diff --git a/protocols/bgp/server/update_sender_add_path.go b/protocols/bgp/server/update_sender_add_path.go
deleted file mode 100644
index 66d4a594cc6b59852bf206447c59cdb949d25abf..0000000000000000000000000000000000000000
--- a/protocols/bgp/server/update_sender_add_path.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package server
-
-import (
-	log "github.com/sirupsen/logrus"
-
-	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/bio-routing/bio-rd/route"
-	"github.com/bio-routing/bio-rd/routingtable"
-)
-
-// UpdateSenderAddPath converts table changes into BGP update messages with add path
-type UpdateSenderAddPath struct {
-	routingtable.ClientManager
-	fsm  *FSM
-	iBGP bool
-}
-
-func newUpdateSenderAddPath(fsm *FSM) *UpdateSenderAddPath {
-	return &UpdateSenderAddPath{
-		fsm:  fsm,
-		iBGP: fsm.peer.localASN == fsm.peer.peerASN,
-	}
-}
-
-// AddPath serializes a new path and sends out a BGP update message
-func (u *UpdateSenderAddPath) AddPath(pfx net.Prefix, p *route.Path) error {
-	pathAttrs, err := pathAttribues(p)
-
-	if err != nil {
-		log.Errorf("Unable to create BGP Update: %v", err)
-		return nil
-	}
-	update := &packet.BGPUpdateAddPath{
-		PathAttributes: pathAttrs,
-		NLRI: &packet.NLRIAddPath{
-			PathIdentifier: p.BGPPath.PathIdentifier,
-			IP:             pfx.Addr(),
-			Pfxlen:         pfx.Pfxlen(),
-		},
-	}
-	return serializeAndSendUpdate(u.fsm.con, update, u.fsm.options)
-}
-
-// RemovePath withdraws prefix `pfx` from a peer
-func (u *UpdateSenderAddPath) RemovePath(pfx net.Prefix, p *route.Path) bool {
-	err := withDrawPrefixesAddPath(u.fsm.con, u.fsm.options, pfx, p)
-	return err == nil
-}
-
-// UpdateNewClient does nothing
-func (u *UpdateSenderAddPath) UpdateNewClient(client routingtable.RouteTableClient) error {
-	log.Warningf("BGP Update Sender: UpdateNewClient not implemented")
-	return nil
-}
-
-// RouteCount returns the number of stored routes
-func (u *UpdateSenderAddPath) RouteCount() int64 {
-	log.Warningf("BGP Update Sender: RouteCount not implemented")
-	return 0
-}
diff --git a/protocols/bgp/server/withdraw.go b/protocols/bgp/server/withdraw.go
index 089f9f2c0f7a0e25d1c5d1f2e9f292cdeec74a3c..e3f1137e1e96469dfa69f3bd825a3a644a582fc2 100644
--- a/protocols/bgp/server/withdraw.go
+++ b/protocols/bgp/server/withdraw.go
@@ -6,12 +6,13 @@ import (
 
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 )
 
 // withDrawPrefixes generates a BGPUpdate message and write it to the given
 // io.Writer.
-func withDrawPrefixes(out io.Writer, opt *packet.Options, prefixes ...net.Prefix) error {
+func withDrawPrefixes(out io.Writer, opt *types.Options, prefixes ...net.Prefix) error {
 	if len(prefixes) < 1 {
 		return nil
 	}
@@ -41,15 +42,15 @@ func withDrawPrefixes(out io.Writer, opt *packet.Options, prefixes ...net.Prefix
 
 // withDrawPrefixesAddPath generates a BGPUpdateAddPath message and write it to the given
 // io.Writer.
-func withDrawPrefixesAddPath(out io.Writer, opt *packet.Options, pfx net.Prefix, p *route.Path) error {
+func withDrawPrefixesAddPath(out io.Writer, opt *types.Options, pfx net.Prefix, p *route.Path) error {
 	if p.Type != route.BGPPathType {
 		return errors.New("wrong path type, expected BGPPathType")
 	}
 	if p.BGPPath == nil {
 		return errors.New("got nil BGPPath")
 	}
-	update := &packet.BGPUpdateAddPath{
-		WithdrawnRoutes: &packet.NLRIAddPath{
+	update := &packet.BGPUpdate{
+		WithdrawnRoutes: &packet.NLRI{
 			PathIdentifier: p.BGPPath.PathIdentifier,
 			IP:             pfx.Addr(),
 			Pfxlen:         pfx.Pfxlen(),
diff --git a/protocols/bgp/server/withdraw_test.go b/protocols/bgp/server/withdraw_test.go
index d8831c3eb526d3afdaceb0a3506bf5dca39c2c41..c21b6ada956e4c694a8408e962a67efe611574c2 100644
--- a/protocols/bgp/server/withdraw_test.go
+++ b/protocols/bgp/server/withdraw_test.go
@@ -3,7 +3,7 @@ package server
 import (
 	"testing"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 
 	"errors"
 
@@ -54,7 +54,7 @@ func TestWithDrawPrefixes(t *testing.T) {
 	}
 	for _, tc := range testcases {
 		buf := bytes.NewBuffer([]byte{})
-		opt := &packet.Options{}
+		opt := &types.Options{}
 		err := withDrawPrefixes(buf, opt, tc.Prefix...)
 		assert.Equal(t, tc.ExpectedError, err, "error mismatch in testcase %v", tc.Name)
 		assert.Equal(t, tc.Expected, buf.Bytes(), "expected different bytes in testcase %v", tc.Name)
@@ -111,7 +111,9 @@ func TestWithDrawPrefixesAddPath(t *testing.T) {
 	}
 	for _, tc := range testcases {
 		buf := bytes.NewBuffer([]byte{})
-		opt := &packet.Options{}
+		opt := &types.Options{
+			AddPathRX: true,
+		}
 		err := withDrawPrefixesAddPath(buf, opt, tc.Prefix, tc.Path)
 		assert.Equal(t, tc.ExpectedError, err, "error mismatch in testcase %v", tc.Name)
 		assert.Equal(t, tc.Expected, buf.Bytes(), "expected different bytes in testcase %v", tc.Name)
diff --git a/protocols/bgp/types/BUILD.bazel b/protocols/bgp/types/BUILD.bazel
new file mode 100644
index 0000000000000000000000000000000000000000..cd582779e7275027d66fadef922165706b2c5003
--- /dev/null
+++ b/protocols/bgp/types/BUILD.bazel
@@ -0,0 +1,26 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "as_path.go",
+        "community.go",
+        "large_community.go",
+        "options.go",
+        "unknown_attribute.go",
+    ],
+    importpath = "github.com/bio-routing/bio-rd/protocols/bgp/types",
+    visibility = ["//visibility:public"],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+        "community_test.go",
+        "large_community_benchmark_test.go",
+        "large_community_test.go",
+        "unknown_attrinute_test.go",
+    ],
+    embed = [":go_default_library"],
+    deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
+)
diff --git a/protocols/bgp/types/as_path.go b/protocols/bgp/types/as_path.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1f7bd3a64ef629b3ced72a5fa60a5a99a0a74f4
--- /dev/null
+++ b/protocols/bgp/types/as_path.go
@@ -0,0 +1,59 @@
+package types
+
+import "fmt"
+
+// ASPath Segment Types
+const (
+	// ASSet is the AS Path type used to indicate an AS Set (RFC4271)
+	ASSet = 1
+
+	// ASSequence is tha AS Path type used to indicate an AS Sequence (RFC4271)
+	ASSequence = 2
+
+	// MaxASNsSegment is the maximum number of ASNs in an AS segment
+	MaxASNsSegment = 255
+)
+
+// ASPath represents an AS Path (RFC4271)
+type ASPath []ASPathSegment
+
+// ASPathSegment represents an AS Path Segment (RFC4271)
+type ASPathSegment struct {
+	Type uint8
+	ASNs []uint32
+}
+
+// String converts an ASPath to it's human redable representation
+func (pa ASPath) String() (ret string) {
+	for _, p := range pa {
+		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
+}
+
+// Length returns the AS path length as used by path selection
+func (pa ASPath) Length() (ret uint16) {
+	for _, p := range pa {
+		if p.Type == ASSet {
+			ret++
+			continue
+		}
+		ret += uint16(len(p.ASNs))
+	}
+
+	return
+}
diff --git a/protocols/bgp/packet/community.go b/protocols/bgp/types/community.go
similarity index 62%
rename from protocols/bgp/packet/community.go
rename to protocols/bgp/types/community.go
index 05b3d8fcb15d295df75853067eea5e124b1e47b2..c1748b649295355a97894d292daa803d61918035 100644
--- a/protocols/bgp/packet/community.go
+++ b/protocols/bgp/types/community.go
@@ -1,4 +1,4 @@
-package packet
+package types
 
 import (
 	"fmt"
@@ -7,10 +7,13 @@ import (
 )
 
 const (
-	WellKnownCommunityNoExport    = 0xFFFFFF01
+	// WellKnownCommunityNoExport is the well known no export BGP community (RFC1997)
+	WellKnownCommunityNoExport = 0xFFFFFF01
+	// WellKnownCommunityNoAdvertise is the well known no advertise BGP community (RFC1997)
 	WellKnownCommunityNoAdvertise = 0xFFFFFF02
 )
 
+// CommunityStringForUint32 transforms a community into a human readable representation
 func CommunityStringForUint32(v uint32) string {
 	e1 := v >> 16
 	e2 := v & 0x0000FFFF
@@ -18,6 +21,7 @@ func CommunityStringForUint32(v uint32) string {
 	return fmt.Sprintf("(%d,%d)", e1, e2)
 }
 
+// ParseCommunityString parses human readable community representation
 func ParseCommunityString(s string) (uint32, error) {
 	s = strings.Trim(s, "()")
 	t := strings.Split(s, ",")
diff --git a/protocols/bgp/packet/community_test.go b/protocols/bgp/types/community_test.go
similarity index 99%
rename from protocols/bgp/packet/community_test.go
rename to protocols/bgp/types/community_test.go
index f2dced9a4749cfb9d9aeabbe06ed1497595f1cc9..504859769aa4fd1b9eb5bb8ce1e18e69000e9701 100644
--- a/protocols/bgp/packet/community_test.go
+++ b/protocols/bgp/types/community_test.go
@@ -1,4 +1,4 @@
-package packet
+package types
 
 import (
 	"testing"
diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/types/large_community.go
similarity index 78%
rename from protocols/bgp/packet/large_community.go
rename to protocols/bgp/types/large_community.go
index 4d93eb600bff85f3899e3a3df8246002f903dfb4..18f859c2ff97fd3c62fc3ba310a8e824da46a3a6 100644
--- a/protocols/bgp/packet/large_community.go
+++ b/protocols/bgp/types/large_community.go
@@ -1,4 +1,4 @@
-package packet
+package types
 
 import (
 	"fmt"
@@ -6,16 +6,19 @@ import (
 	"strings"
 )
 
+// LargeCommunity represents a large community (RFC8195)
 type LargeCommunity struct {
 	GlobalAdministrator uint32
 	DataPart1           uint32
 	DataPart2           uint32
 }
 
+// String transitions a large community to it's human readable representation
 func (c *LargeCommunity) String() string {
 	return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2)
 }
 
+// ParseLargeCommunityString parses a human readable large community representation
 func ParseLargeCommunityString(s string) (com LargeCommunity, err error) {
 	s = strings.Trim(s, "()")
 	t := strings.Split(s, ",")
diff --git a/protocols/bgp/packet/large_community_benchmark_test.go b/protocols/bgp/types/large_community_benchmark_test.go
similarity index 97%
rename from protocols/bgp/packet/large_community_benchmark_test.go
rename to protocols/bgp/types/large_community_benchmark_test.go
index 7e37bcdf98de064442e4e10d343d31c45448ab74..d2ca3a4d9bedaf9e3a63df7cef843032fe891806 100644
--- a/protocols/bgp/packet/large_community_benchmark_test.go
+++ b/protocols/bgp/types/large_community_benchmark_test.go
@@ -1,4 +1,4 @@
-package packet
+package types
 
 import (
 	"fmt"
diff --git a/protocols/bgp/packet/large_community_test.go b/protocols/bgp/types/large_community_test.go
similarity index 99%
rename from protocols/bgp/packet/large_community_test.go
rename to protocols/bgp/types/large_community_test.go
index 6319ccff41daa9443cb887777129be52545c2e10..f3ff70222bf794e6edbc31fce9b811f02e40c582 100644
--- a/protocols/bgp/packet/large_community_test.go
+++ b/protocols/bgp/types/large_community_test.go
@@ -1,4 +1,4 @@
-package packet
+package types
 
 import (
 	"errors"
diff --git a/protocols/bgp/types/options.go b/protocols/bgp/types/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..19b580d2829b96d8f974311536b299211a98ca95
--- /dev/null
+++ b/protocols/bgp/types/options.go
@@ -0,0 +1,7 @@
+package types
+
+// Options represents options to the update sender, decoder and encoder
+type Options struct {
+	Supports4OctetASN bool
+	AddPathRX         bool
+}
diff --git a/protocols/bgp/types/unknown_attribute.go b/protocols/bgp/types/unknown_attribute.go
new file mode 100644
index 0000000000000000000000000000000000000000..66e7d9f0a90eb88d6b05ede9d561e2130d0dbed4
--- /dev/null
+++ b/protocols/bgp/types/unknown_attribute.go
@@ -0,0 +1,19 @@
+package types
+
+// UnknownPathAttribute represents an unknown path attribute BIO does not support
+type UnknownPathAttribute struct {
+	Optional   bool
+	Transitive bool
+	Partial    bool
+	TypeCode   uint8
+	Value      []byte
+}
+
+// WireLength returns the number of bytes the attribute need on the wire
+func (u *UnknownPathAttribute) WireLength() uint16 {
+	length := uint16(len(u.Value))
+	if length > 255 {
+		length++ // Extended length
+	}
+	return length + 3
+}
diff --git a/protocols/bgp/types/unknown_attrinute_test.go b/protocols/bgp/types/unknown_attrinute_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ab31f6ecc5095b5fa8a377643608e3c27edb97e
--- /dev/null
+++ b/protocols/bgp/types/unknown_attrinute_test.go
@@ -0,0 +1,94 @@
+package types
+
+import "testing"
+
+func TestWireLength(t *testing.T) {
+	tests := []struct {
+		name     string
+		pa       *UnknownPathAttribute
+		expected uint16
+	}{
+		{
+			name: "Test #1",
+			pa: &UnknownPathAttribute{
+				Value: []byte{1, 2, 3},
+			},
+			expected: 6,
+		},
+		{
+			name: "Non extended length corner case",
+			pa: &UnknownPathAttribute{
+				Value: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255,
+				},
+			},
+			expected: 258,
+		},
+		{
+			name: "Extended length corner case",
+			pa: &UnknownPathAttribute{
+				Value: []byte{
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+					255, 255, 255, 255, 255, 255,
+				},
+			},
+			expected: 260,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.pa.WireLength()
+		if res != test.expected {
+			t.Errorf("Unexpected result for test %q: Expected: %d Got: %d", test.name, test.expected, res)
+		}
+	}
+}
diff --git a/route/BUILD.bazel b/route/BUILD.bazel
index 43ece0720b95d36285849f9f80d9d07d2d259b6d..3673d29995e0ef11a14bbd7043135457f7ea1d7e 100644
--- a/route/BUILD.bazel
+++ b/route/BUILD.bazel
@@ -13,7 +13,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//net:go_default_library",
-        "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//vendor/github.com/taktv6/tflow2/convert:go_default_library",
     ],
 )
@@ -29,7 +29,7 @@ go_test(
     embed = [":go_default_library"],
     deps = [
         "//net:go_default_library",
-        "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
     ],
 )
diff --git a/route/bgp_path.go b/route/bgp_path.go
index d2aa5247abb665a53e3073a06a9d9ec1d0c52369..4eec57b3326b968a221f79c0359cfdcf73ba95c0 100644
--- a/route/bgp_path.go
+++ b/route/bgp_path.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	bnet "github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/taktv6/tflow2/convert"
 )
 
@@ -15,7 +15,7 @@ type BGPPath struct {
 	PathIdentifier    uint32
 	NextHop           bnet.IP
 	LocalPref         uint32
-	ASPath            packet.ASPath
+	ASPath            types.ASPath
 	ASPathLen         uint16
 	Origin            uint8
 	MED               uint32
@@ -23,8 +23,34 @@ type BGPPath struct {
 	BGPIdentifier     uint32
 	Source            bnet.IP
 	Communities       []uint32
-	LargeCommunities  []packet.LargeCommunity
-	UnknownAttributes *packet.PathAttribute
+	LargeCommunities  []types.LargeCommunity
+	UnknownAttributes []types.UnknownPathAttribute
+}
+
+// Length get's the length of serialized path
+func (b *BGPPath) Length() uint16 {
+	asPathLen := uint16(3)
+	for _, segment := range b.ASPath {
+		asPathLen++
+		asPathLen += uint16(4 * len(segment.ASNs))
+	}
+
+	communitiesLen := uint16(0)
+	if len(b.Communities) != 0 {
+		communitiesLen += 3 + uint16(len(b.Communities)*4)
+	}
+
+	largeCommunitiesLen := uint16(0)
+	if len(b.LargeCommunities) != 0 {
+		largeCommunitiesLen += 3 + uint16(len(b.LargeCommunities)*12)
+	}
+
+	unknownAttributesLen := uint16(0)
+	for _, unknownAttr := range b.UnknownAttributes {
+		unknownAttributesLen += unknownAttr.WireLength()
+	}
+
+	return communitiesLen + largeCommunitiesLen + 4*7 + 4 + asPathLen + unknownAttributesLen
 }
 
 // ECMP determines if routes b and c are euqal in terms of ECMP
@@ -207,12 +233,12 @@ func (b *BGPPath) Prepend(asn uint32, times uint16) {
 	}
 
 	first := b.ASPath[0]
-	if first.Type == packet.ASSet {
+	if first.Type == types.ASSet {
 		b.insertNewASSequence()
 	}
 
 	for i := 0; i < int(times); i++ {
-		if len(b.ASPath) == packet.MaxASNsSegment {
+		if len(b.ASPath) == types.MaxASNsSegment {
 			b.insertNewASSequence()
 		}
 
@@ -227,23 +253,33 @@ func (b *BGPPath) Prepend(asn uint32, times uint16) {
 }
 
 func (b *BGPPath) insertNewASSequence() {
-	pa := make(packet.ASPath, len(b.ASPath)+1)
-	copy(b.ASPath, pa[1:])
-	pa[0] = packet.ASPathSegment{
-		ASNs:  make([]uint32, 0),
-		Count: 0,
-		Type:  packet.ASSequence,
+	pa := make(types.ASPath, len(b.ASPath)+1)
+	copy(pa[1:], b.ASPath)
+	pa[0] = types.ASPathSegment{
+		ASNs: make([]uint32, 0),
+		Type: types.ASSequence,
 	}
 
 	b.ASPath = pa
 }
 
-func (p *BGPPath) Copy() *BGPPath {
-	if p == nil {
+// Copy creates a deep copy of a BGPPath
+func (b *BGPPath) Copy() *BGPPath {
+	if b == nil {
 		return nil
 	}
 
-	cp := *p
+	cp := *b
+
+	cp.ASPath = make(types.ASPath, len(cp.ASPath))
+	copy(cp.ASPath, b.ASPath)
+
+	cp.Communities = make([]uint32, len(cp.Communities))
+	copy(cp.Communities, b.Communities)
+
+	cp.LargeCommunities = make([]types.LargeCommunity, len(cp.LargeCommunities))
+	copy(cp.LargeCommunities, b.LargeCommunities)
+
 	return &cp
 }
 
@@ -269,7 +305,7 @@ func (b *BGPPath) ComputeHash() string {
 func (b *BGPPath) CommunitiesString() string {
 	str := ""
 	for _, com := range b.Communities {
-		str += packet.CommunityStringForUint32(com) + " "
+		str += types.CommunityStringForUint32(com) + " "
 	}
 
 	return strings.TrimRight(str, " ")
diff --git a/route/bgp_path_test.go b/route/bgp_path_test.go
index d0f0a9937a7ded1309664ffd31321d5edf844344..9ea931153ea5778061e852e34af7666204e97b0b 100644
--- a/route/bgp_path_test.go
+++ b/route/bgp_path_test.go
@@ -3,7 +3,7 @@ package route
 import (
 	"testing"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -34,12 +34,12 @@ func TestCommunitiesString(t *testing.T) {
 func TestLargeCommunitiesString(t *testing.T) {
 	tests := []struct {
 		name     string
-		comms    []packet.LargeCommunity
+		comms    []types.LargeCommunity
 		expected string
 	}{
 		{
 			name: "two attributes",
-			comms: []packet.LargeCommunity{
+			comms: []types.LargeCommunity{
 				{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
@@ -64,3 +64,72 @@ func TestLargeCommunitiesString(t *testing.T) {
 		})
 	}
 }
+
+func TestLength(t *testing.T) {
+	tests := []struct {
+		name     string
+		path     *BGPPath
+		expected uint16
+	}{
+		{
+			name: "No communities",
+			path: &BGPPath{
+				ASPath: []types.ASPathSegment{
+					{
+						Type: types.ASSequence,
+						ASNs: []uint32{15169, 199714},
+					},
+				},
+				LargeCommunities: []types.LargeCommunity{},
+				Communities:      []uint32{},
+			},
+			expected: 44,
+		},
+		{
+			name: "communities",
+			path: &BGPPath{
+				ASPath: []types.ASPathSegment{
+					{
+						Type: types.ASSequence,
+						ASNs: []uint32{15169, 199714},
+					},
+				},
+				LargeCommunities: []types.LargeCommunity{},
+				Communities:      []uint32{10, 20, 30},
+			},
+			expected: 59,
+		},
+		{
+			name: "large communities",
+			path: &BGPPath{
+				ASPath: []types.ASPathSegment{
+					{
+						Type: types.ASSequence,
+						ASNs: []uint32{15169, 199714},
+					},
+				},
+				LargeCommunities: []types.LargeCommunity{
+					{
+						GlobalAdministrator: 199714,
+						DataPart1:           100,
+						DataPart2:           200,
+					},
+					{
+						GlobalAdministrator: 199714,
+						DataPart1:           100,
+						DataPart2:           201,
+					},
+				},
+			},
+			expected: 71,
+		},
+	}
+
+	for _, test := range tests {
+		calcLen := test.path.Length()
+
+		if calcLen != test.expected {
+			t.Errorf("Unexpected result for test %q: Expected: %d Got: %d", test.name, test.expected, calcLen)
+		}
+	}
+}
diff --git a/route/bgp_test.go b/route/bgp_test.go
index 2eb017c2a6b33e5174b94cb9d43e4325750e968d..46ceb9af2364746aba48711400e9091cc882457f 100644
--- a/route/bgp_test.go
+++ b/route/bgp_test.go
@@ -3,18 +3,16 @@ package route
 import (
 	"testing"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestComputeHash(t *testing.T) {
 	p := &BGPPath{
-		ASPath: packet.ASPath{
-			packet.ASPathSegment{
-				ASNs:  []uint32{123, 456},
-				Count: 2,
-				Type:  packet.ASSequence,
+		ASPath: types.ASPath{
+			types.ASPathSegment{
+				ASNs: []uint32{123, 456},
+				Type: types.ASSequence,
 			},
 		},
 		BGPIdentifier: 1,
@@ -22,8 +20,8 @@ func TestComputeHash(t *testing.T) {
 			123, 456,
 		},
 		EBGP: false,
-		LargeCommunities: []packet.LargeCommunity{
-			packet.LargeCommunity{
+		LargeCommunities: []types.LargeCommunity{
+			types.LargeCommunity{
 				DataPart1:           1,
 				DataPart2:           2,
 				GlobalAdministrator: 3,
diff --git a/route/path.go b/route/path.go
index 7c0edb7e11443fcad6f2bb0024794c32036fb629..ef4da4068f7bd81b4051e5d91d93d137d8832af1 100644
--- a/route/path.go
+++ b/route/path.go
@@ -1,6 +1,8 @@
 package route
 
-import "fmt"
+import (
+	"fmt"
+)
 
 type Path struct {
 	Type       uint8
diff --git a/routingtable/BUILD.bazel b/routingtable/BUILD.bazel
index 1344741ba00193ae2cf24e4e93065d83c62985b0..4bde0bc33e4fc282414ac1a4849e5ce01bb6ecb1 100644
--- a/routingtable/BUILD.bazel
+++ b/routingtable/BUILD.bazel
@@ -17,7 +17,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//net:go_default_library",
-        "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//route:go_default_library",
     ],
 )
diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go
index e211479b0480a338259a0dccce1a66b060ebcfe1..a2179fc02d3d9cff82bc67f95ba1e86fa2a293bd 100644
--- a/routingtable/adjRIBOut/adj_rib_out.go
+++ b/routingtable/adjRIBOut/adj_rib_out.go
@@ -69,19 +69,21 @@ func (a *AdjRIBOut) AddPath(pfx bnet.Prefix, p *route.Path) error {
 	a.mu.Lock()
 	defer a.mu.Unlock()
 
-	if !a.neighbor.CapAddPathRX {
+	// AddPathRX capable neighbor
+	if a.neighbor.CapAddPathRX {
+		pathID, err := a.pathIDManager.addPath(p)
+		if err != nil {
+			return fmt.Errorf("Unable to get path ID: %v", err)
+		}
+
+		p.BGPPath.PathIdentifier = pathID
+		a.rt.AddPath(pfx, p)
+	} else {
+		// rt.ReplacePath will add this path to the rt in any case, so no rt.AddPath here!
 		oldPaths := a.rt.ReplacePath(pfx, p)
 		a.removePathsFromClients(pfx, oldPaths)
 	}
 
-	pathID, err := a.pathIDManager.addPath(p)
-	if err != nil {
-		return fmt.Errorf("Unable to get path ID: %v", err)
-	}
-
-	p.BGPPath.PathIdentifier = pathID
-	a.rt.AddPath(pfx, p)
-
 	for _, client := range a.ClientManager.Clients() {
 		err := client.AddPath(pfx, p)
 		if err != nil {
@@ -166,3 +168,17 @@ func (a *AdjRIBOut) Print() string {
 
 	return ret
 }
+
+// Dump all routes present in this AdjRIBOut
+func (a *AdjRIBOut) Dump() string {
+	a.mu.RLock()
+	defer a.mu.RUnlock()
+
+	ret := fmt.Sprintf("DUMPING ADJ-RIB-OUT:\n")
+	routes := a.rt.Dump()
+	for _, r := range routes {
+		ret += fmt.Sprintf("%s\n", r.Print())
+	}
+
+	return ret
+}
diff --git a/routingtable/filter/BUILD.bazel b/routingtable/filter/BUILD.bazel
index 9a27beb4b87a501ff7d18880248ef2418dda2199..4555e6a8dcb20f8f469dcb3826649ce69ba5877c 100644
--- a/routingtable/filter/BUILD.bazel
+++ b/routingtable/filter/BUILD.bazel
@@ -17,7 +17,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//net:go_default_library",
-        "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//route:go_default_library",
         "//routingtable/filter/actions:go_default_library",
     ],
diff --git a/routingtable/filter/actions/BUILD.bazel b/routingtable/filter/actions/BUILD.bazel
index 3f841a07d88ce65c8a65e31a5842ff08378a8058..994b95bf04dbb769eb30c209b87e484261d4254f 100644
--- a/routingtable/filter/actions/BUILD.bazel
+++ b/routingtable/filter/actions/BUILD.bazel
@@ -16,7 +16,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//net:go_default_library",
-        "//protocols/bgp/packet:go_default_library",
+        "//protocols/bgp/types:go_default_library",
         "//route:go_default_library",
     ],
 )
diff --git a/routingtable/filter/actions/add_large_community_action.go b/routingtable/filter/actions/add_large_community_action.go
index 534ad30b66e4580ca6b8078bf7a077f87b6e758f..cb5f1a020ea6bfdde33948cc8535077df0fec26d 100644
--- a/routingtable/filter/actions/add_large_community_action.go
+++ b/routingtable/filter/actions/add_large_community_action.go
@@ -2,15 +2,15 @@ package actions
 
 import (
 	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 )
 
 type AddLargeCommunityAction struct {
-	communities []packet.LargeCommunity
+	communities []types.LargeCommunity
 }
 
-func NewAddLargeCommunityAction(coms []packet.LargeCommunity) *AddLargeCommunityAction {
+func NewAddLargeCommunityAction(coms []types.LargeCommunity) *AddLargeCommunityAction {
 	return &AddLargeCommunityAction{
 		communities: coms,
 	}
diff --git a/routingtable/filter/actions/add_large_community_action_test.go b/routingtable/filter/actions/add_large_community_action_test.go
index f7af91396fdd7d697cf667d7712e18c9784d68e3..6b569d2ebd79e7225791b0172c2ad32fab764d88 100644
--- a/routingtable/filter/actions/add_large_community_action_test.go
+++ b/routingtable/filter/actions/add_large_community_action_test.go
@@ -4,7 +4,7 @@ import (
 	"testing"
 
 	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/stretchr/testify/assert"
 )
@@ -12,14 +12,14 @@ import (
 func TestAddingLargeCommunities(t *testing.T) {
 	tests := []struct {
 		name        string
-		current     []packet.LargeCommunity
-		communities []packet.LargeCommunity
+		current     []types.LargeCommunity
+		communities []types.LargeCommunity
 		expected    string
 	}{
 		{
 			name: "add one to empty",
-			communities: []packet.LargeCommunity{
-				packet.LargeCommunity{
+			communities: []types.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
@@ -29,15 +29,15 @@ func TestAddingLargeCommunities(t *testing.T) {
 		},
 		{
 			name: "add one to existing",
-			current: []packet.LargeCommunity{
-				packet.LargeCommunity{
+			current: []types.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 5,
 					DataPart1:           6,
 					DataPart2:           7,
 				},
 			},
-			communities: []packet.LargeCommunity{
-				packet.LargeCommunity{
+			communities: []types.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
@@ -47,20 +47,20 @@ func TestAddingLargeCommunities(t *testing.T) {
 		},
 		{
 			name: "add two to existing",
-			current: []packet.LargeCommunity{
-				packet.LargeCommunity{
+			current: []types.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 5,
 					DataPart1:           6,
 					DataPart2:           7,
 				},
 			},
-			communities: []packet.LargeCommunity{
-				packet.LargeCommunity{
+			communities: []types.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
 				},
-				packet.LargeCommunity{
+				types.LargeCommunity{
 					GlobalAdministrator: 7,
 					DataPart1:           8,
 					DataPart2:           9,
diff --git a/routingtable/filter/actions/as_path_prepend_action_test.go b/routingtable/filter/actions/as_path_prepend_action_test.go
index 253c119ffc91d702e0f4d4cf31e1f88c35d2008c..702a9497cc767cb6b240805a7b4af6f20e17277b 100644
--- a/routingtable/filter/actions/as_path_prepend_action_test.go
+++ b/routingtable/filter/actions/as_path_prepend_action_test.go
@@ -3,7 +3,7 @@ package actions
 import (
 	"testing"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/route"
@@ -25,11 +25,10 @@ func TestAppendPath(t *testing.T) {
 			name:  "append 0",
 			times: 0,
 			bgpPath: &route.BGPPath{
-				ASPath: packet.ASPath{
-					packet.ASPathSegment{
-						Count: 2,
-						Type:  packet.ASSequence,
-						ASNs:  []uint32{12345, 12345},
+				ASPath: types.ASPath{
+					types.ASPathSegment{
+						Type: types.ASSequence,
+						ASNs: []uint32{12345, 12345},
 					},
 				},
 				ASPathLen: 2,
@@ -41,11 +40,10 @@ func TestAppendPath(t *testing.T) {
 			name:  "append 3",
 			times: 3,
 			bgpPath: &route.BGPPath{
-				ASPath: packet.ASPath{
-					packet.ASPathSegment{
-						Count: 2,
-						Type:  packet.ASSequence,
-						ASNs:  []uint32{12345, 15169},
+				ASPath: types.ASPath{
+					types.ASPathSegment{
+						Type: types.ASSequence,
+						ASNs: []uint32{12345, 15169},
 					},
 				},
 				ASPathLen: 2,
diff --git a/routingtable/filter/large_community_filter.go b/routingtable/filter/large_community_filter.go
index cb4722bdfe98a68cd6c6e9938f188bbbf606c968..d119d4542dd591316bbe6e86e2aeed30215f29bf 100644
--- a/routingtable/filter/large_community_filter.go
+++ b/routingtable/filter/large_community_filter.go
@@ -1,14 +1,16 @@
 package filter
 
 import (
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 )
 
+// LargeCommunityFilter represents a filter for large communities
 type LargeCommunityFilter struct {
-	community packet.LargeCommunity
+	community types.LargeCommunity
 }
 
-func (f *LargeCommunityFilter) Matches(coms []packet.LargeCommunity) bool {
+// Matches checks if a community f.community is on the filter list
+func (f *LargeCommunityFilter) Matches(coms []types.LargeCommunity) bool {
 	for _, com := range coms {
 		if com == f.community {
 			return true
diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go
index e1b3d4bd6173b196ddaca029e768274a08364883..5217feaaacc67a57539a90369664a81663f4e642 100644
--- a/routingtable/filter/term_condition_test.go
+++ b/routingtable/filter/term_condition_test.go
@@ -4,7 +4,7 @@ import (
 	"testing"
 
 	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/stretchr/testify/assert"
 )
@@ -137,13 +137,13 @@ func TestMatches(t *testing.T) {
 			name:   "large community matches",
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			bgpPath: &route.BGPPath{
-				LargeCommunities: []packet.LargeCommunity{
-					packet.LargeCommunity{
+				LargeCommunities: []types.LargeCommunity{
+					types.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
 					},
-					packet.LargeCommunity{
+					types.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           0,
@@ -152,7 +152,7 @@ func TestMatches(t *testing.T) {
 			},
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					packet.LargeCommunity{
+					types.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
@@ -167,7 +167,7 @@ func TestMatches(t *testing.T) {
 			bgpPath: &route.BGPPath{},
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					packet.LargeCommunity{
+					types.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
@@ -181,7 +181,7 @@ func TestMatches(t *testing.T) {
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					packet.LargeCommunity{
+					types.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
diff --git a/routingtable/update_helper.go b/routingtable/update_helper.go
index 6709bd43ab7e2d39914c6ebd1c59abb6518e8189..48110df0a43f0951efce6384262b208402b998e6 100644
--- a/routingtable/update_helper.go
+++ b/routingtable/update_helper.go
@@ -2,7 +2,7 @@ package routingtable
 
 import (
 	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 )
 
@@ -30,7 +30,7 @@ func isDisallowedByCommunity(p *route.Path, n *Neighbor) bool {
 	}
 
 	for _, com := range p.BGPPath.Communities {
-		if (com == packet.WellKnownCommunityNoExport && !n.IBGP) || com == packet.WellKnownCommunityNoAdvertise {
+		if (com == types.WellKnownCommunityNoExport && !n.IBGP) || com == types.WellKnownCommunityNoAdvertise {
 			return true
 		}
 	}
diff --git a/routingtable/update_helper_test.go b/routingtable/update_helper_test.go
index 452a7e4af04152e21c13631c38190b8d4795649d..451b983beae4df2ce289b60be0be8d1b7bd7f67a 100644
--- a/routingtable/update_helper_test.go
+++ b/routingtable/update_helper_test.go
@@ -6,7 +6,7 @@ import (
 	"testing"
 
 	bnet "github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/stretchr/testify/assert"
 )
@@ -67,7 +67,7 @@ func TestShouldPropagateUpdate(t *testing.T) {
 					continue
 				}
 
-				com, err := packet.ParseCommunityString(s)
+				com, err := types.ParseCommunityString(s)
 				if err != nil {
 					t.Fatalf("test failed: %s", err)
 				}