diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel
index 49f16b7773345b729ff25a1a486a86b1669992ed..bc081c1e463c24e67b103de106d686462fd340f5 100644
--- a/protocols/bgp/packet/BUILD.bazel
+++ b/protocols/bgp/packet/BUILD.bazel
@@ -3,31 +3,30 @@ 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",
     ],
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 a8df09179d16d6154a6df2e8b0bbfe3ea7abd481..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
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
index 748e36535f049ddb31955e8e86078aef209641cd..2e47c5e6a7d17ab0a6d4882f712f50a9ae1f047c 100644
--- a/protocols/bgp/packet/path_attributes.go
+++ b/protocols/bgp/packet/path_attributes.go
@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"fmt"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
+	"github.com/bio-routing/bio-rd/route"
 	"github.com/taktv6/tflow2/convert"
 )
 
@@ -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,7 +372,8 @@ func dumpNBytes(buf *bytes.Buffer, n uint16) error {
 	return nil
 }
 
-func (pa *PathAttribute) serialize(buf *bytes.Buffer, opt *Options) uint8 {
+// Serialize serializes a path attribute
+func (pa *PathAttribute) Serialize(buf *bytes.Buffer, opt *Options) uint8 {
 	pathAttrLen := uint8(0)
 
 	switch pa.TypeCode {
@@ -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)))
 
@@ -523,7 +528,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
 	}
@@ -581,3 +586,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/update.go b/protocols/bgp/packet/update.go
index 001f2c1cdba2227d8bdb179789a9f0bebc03bb23..38e1d7196be15fba04554f6845a8265b330a39e2 100644
--- a/protocols/bgp/packet/update.go
+++ b/protocols/bgp/packet/update.go
@@ -36,7 +36,7 @@ func (b *BGPUpdate) SerializeUpdate(opt *Options) ([]byte, error) {
 
 	pathAttributesBuf := bytes.NewBuffer(nil)
 	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf, opt))
+		paLen := int(pa.Serialize(pathAttributesBuf, opt))
 		budget -= paLen
 		if budget < 0 {
 			return nil, fmt.Errorf("update too long")
@@ -100,7 +100,7 @@ func (b *BGPUpdate) SerializeUpdateAddPath(opt *Options) ([]byte, error) {
 
 	pathAttributesBuf := bytes.NewBuffer(nil)
 	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf, opt))
+		paLen := int(pa.Serialize(pathAttributesBuf, opt))
 		budget -= paLen
 		if budget < 0 {
 			return nil, fmt.Errorf("update too long")
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_established.go b/protocols/bgp/server/fsm_established.go
index 3111055d9c0757f329c72acf27c66a1942cc34a4..e184404c2ee95ab684250e4a96a04f34a9a8a9e8 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"
@@ -230,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:
@@ -243,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)
 			}
 		}
 	}
 }
 
-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/update_helper.go b/protocols/bgp/server/update_helper.go
index 38070219394100e9006e660f75f2212b1a2b5668..2c36aa2fdf7880d6f1e2c6b2ff76d8a7bade61f8 100644
--- a/protocols/bgp/server/update_helper.go
+++ b/protocols/bgp/server/update_helper.go
@@ -5,66 +5,9 @@ import (
 	"io"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/bio-routing/bio-rd/route"
 	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
-}
-
 func serializeAndSendUpdate(out io.Writer, update *packet.BGPUpdate, opt *packet.Options) error {
 	updateBytes, err := update.SerializeUpdate(opt)
 	if err != nil {
diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go
index 3ac1e6e9349d5e31caf29b139088b20a235daacc..ccb1d869316724c23933cbf00ee1f0008282e35e 100644
--- a/protocols/bgp/server/update_sender.go
+++ b/protocols/bgp/server/update_sender.go
@@ -93,7 +93,7 @@ func (u *UpdateSender) sender() {
 
 		for key, pathNLRIs := range u.toSend {
 			budget = packet.MaxLen - int(pathNLRIs.path.BGPPath.Length())
-			pathAttrs, err = pathAttribues(pathNLRIs.path)
+			pathAttrs, err = packet.PathAttributes(pathNLRIs.path)
 			if err != nil {
 				log.Errorf("Unable to get path attributes: %v", err)
 				continue
diff --git a/protocols/bgp/types/BUILD.bazel b/protocols/bgp/types/BUILD.bazel
new file mode 100644
index 0000000000000000000000000000000000000000..175cedfcb63e894221efe75d1e850acab65c7f2e
--- /dev/null
+++ b/protocols/bgp/types/BUILD.bazel
@@ -0,0 +1,25 @@
+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",
+        "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/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..b9634f7e7b152e52e9074b507643466c71ef77a6 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",
     ],
 )
diff --git a/route/bgp_path.go b/route/bgp_path.go
index 31837161b2a5a0ba91cb9b302b9a3b8ef3c975a9..6b31dd85a361f37d137e43a6daf28ac6702a4158 100644
--- a/route/bgp_path.go
+++ b/route/bgp_path.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/bio-routing/bio-rd/protocols/bgp/types"
 	"github.com/taktv6/tflow2/convert"
 )
 
@@ -14,7 +14,7 @@ type BGPPath struct {
 	PathIdentifier    uint32
 	NextHop           uint32
 	LocalPref         uint32
-	ASPath            packet.ASPath
+	ASPath            types.ASPath
 	ASPathLen         uint16
 	Origin            uint8
 	MED               uint32
@@ -22,8 +22,8 @@ type BGPPath struct {
 	BGPIdentifier     uint32
 	Source            uint32
 	Communities       []uint32
-	LargeCommunities  []packet.LargeCommunity
-	UnknownAttributes *packet.PathAttribute
+	LargeCommunities  []types.LargeCommunity
+	UnknownAttributes []types.UnknownPathAttribute
 }
 
 // Length get's the length of serialized path
@@ -34,19 +34,12 @@ func (b *BGPPath) Length() uint16 {
 		asPathLen += uint16(4 * len(segment.ASNs))
 	}
 
-	return uint16(len(b.Communities))*7 + uint16(len(b.LargeCommunities))*15 + 4*7 + 4 + asPathLen + b.unknownAttributesLength()
-}
-
-// unknownAttributesLength calculates the length of unknown attributes
-func (b *BGPPath) unknownAttributesLength() (length uint16) {
-	for a := b.UnknownAttributes; a != nil; a = a.Next {
-		length += a.Length + 3
-		if a.ExtendedLength {
-			length++
-		}
+	unknownAttributesLen := uint16(0)
+	for _, unknownAttr := range b.UnknownAttributes {
+		unknownAttributesLen += unknownAttr.WireLength()
 	}
 
-	return length
+	return uint16(len(b.Communities))*7 + uint16(len(b.LargeCommunities))*15 + 4*7 + 4 + asPathLen + unknownAttributesLen
 }
 
 // ECMP determines if routes b and c are euqal in terms of ECMP
@@ -229,12 +222,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()
 		}
 
@@ -248,24 +241,34 @@ func (b *BGPPath) Prepend(asn uint32, times uint16) {
 	b.ASPathLen = b.ASPath.Length()
 }
 
-func (b *BGPPath) insertNewASSequence() packet.ASPath {
-	pa := make(packet.ASPath, len(b.ASPath)+1)
+func (b *BGPPath) insertNewASSequence() types.ASPath {
+	pa := make(types.ASPath, len(b.ASPath)+1)
 	copy(pa[1:], b.ASPath)
-	pa[0] = packet.ASPathSegment{
-		ASNs:  make([]uint32, 0),
-		Count: 0,
-		Type:  packet.ASSequence,
+	pa[0] = types.ASPathSegment{
+		ASNs: make([]uint32, 0),
+		Type: types.ASSequence,
 	}
 
 	return 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
 }
 
@@ -291,7 +294,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..d1ea111a091ca1ce9c263c672b5f2a1a2e9ca73e 100644
--- a/route/bgp_path_test.go
+++ b/route/bgp_path_test.go
@@ -1,6 +1,7 @@
 package route
 
 import (
+	"bytes"
 	"testing"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
@@ -64,3 +65,42 @@ func TestLargeCommunitiesString(t *testing.T) {
 		})
 	}
 }
+
+func TestLength(t *testing.T) {
+	tests := []struct {
+		name     string
+		path     *BGPPath
+		options  packet.Options
+		wantFail bool
+	}{
+		{
+			name: "Test 1",
+			path: &BGPPath{},
+		},
+	}
+
+	for _, test := range tests {
+		calcLen := test.path.Length()
+		pa, err := test.path.PathAttributes()
+		if err != nil {
+			if test.wantFail {
+				continue
+			}
+
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+			continue
+		}
+
+		buf := bytes.Buffer(nil)
+		pa.Serialize(buf, test.options)
+		realLen := len(buf.Bytes())
+		if realLen != calcLen {
+			t.Errorf("Unexpected result for test %q: Expected: %d Got: %d", test.name, realLen, calcLen)
+		}
+	}
+}
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/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/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/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
 		}
 	}