diff --git a/.travis.yml b/.travis.yml
index c67caebe8740917db2ce244bc3eb06726091f5da..3bb9725c0aaec6fb74c58c06a540187b15b21800 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,6 +22,7 @@ script:
 - mkdir -p $HOME/gopath/src/github.com/bio-routing/
 - ln -s $TRAVIS_BUILD_DIR $HOME/gopath/src/github.com/bio-routing/bio-rd || true
 - cp .bazelrc.travis .bazelrc
+- bazel test //...
 - bazel coverage //...
 - bazel build //vendor/github.com/q3k/goveralls
 - bazel-bin/vendor/github.com/q3k/goveralls/linux_amd64_stripped/goveralls -coverprofile=$(find bazel-testlogs/ -iname coverage.dat -or -iname baseline_coverage.dat | paste -sd ',') -merge=false
diff --git a/README.md b/README.md
index 418f544cb659e5e4d174be5beed66f6fb0675c05..1c7e6bd80a9df3c714279ec50a9b5fdbde0fb8da 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # bio-rd
 
+A re-implementation of BGP, IS-IS and OSPF in go. We value respect and robustness!
+
 [![Build Status](https://travis-ci.org/bio-routing/bio-rd.svg?branch=master)](https://travis-ci.org/bio-routing/bio-rd)
 [![Coverage Status](https://coveralls.io/repos/bio-routing/bio-rd/badge.svg?branch=master&service=github)](https://coveralls.io/github/bio-routing/bio-rd?branch=master)
 [![Go ReportCard](http://goreportcard.com/badge/bio-routing/bio-rd)](http://goreportcard.com/report/bio-routing/bio-rd)
diff --git a/main.go b/main.go
index 7220ba4a6c669dd44368ccbc81c16e72c534d0d7..7c5bd7f1ba15450a5679688079307d3e0428e626 100644
--- a/main.go
+++ b/main.go
@@ -40,9 +40,9 @@ func main() {
 
 	b.AddPeer(config.Peer{
 		AdminEnabled:      true,
-		LocalAS:           6695,
+		LocalAS:           65200,
 		PeerAS:            65300,
-		PeerAddress:       net.IP([]byte{169, 254, 200, 1}),
+		PeerAddress:       net.IP([]byte{172, 17, 0, 3}),
 		LocalAddress:      net.IP([]byte{169, 254, 200, 0}),
 		ReconnectInterval: time.Second * 15,
 		HoldTime:          time.Second * 90,
@@ -59,9 +59,9 @@ func main() {
 
 	b.AddPeer(config.Peer{
 		AdminEnabled:      true,
-		LocalAS:           6695,
+		LocalAS:           65200,
 		PeerAS:            65100,
-		PeerAddress:       net.IP([]byte{169, 254, 100, 0}),
+		PeerAddress:       net.IP([]byte{172, 17, 0, 2}),
 		LocalAddress:      net.IP([]byte{169, 254, 100, 1}),
 		ReconnectInterval: time.Second * 15,
 		HoldTime:          time.Second * 90,
diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel
index 10c8af2338d9c6ce76bb2f37d23355b04d21af69..49f16b7773345b729ff25a1a486a86b1669992ed 100644
--- a/protocols/bgp/packet/BUILD.bazel
+++ b/protocols/bgp/packet/BUILD.bazel
@@ -3,12 +3,14 @@ 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",
diff --git a/protocols/bgp/packet/as_path.go b/protocols/bgp/packet/as_path.go
new file mode 100644
index 0000000000000000000000000000000000000000..0cdfc96a1b6f418013ae6a6656f6cc8ecf554686
--- /dev/null
+++ b/protocols/bgp/packet/as_path.go
@@ -0,0 +1,44 @@
+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 622b46f37b115dd27779a285284d177d75695230..8aabec7f7c729cd23d47ed6abe517742f2583708 100644
--- a/protocols/bgp/packet/bgp.go
+++ b/protocols/bgp/packet/bgp.go
@@ -92,9 +92,11 @@ const (
 	UnicastSAFI           = 1
 	CapabilitiesParamType = 2
 	AddPathCapabilityCode = 69
+	ASN4CapabilityCode    = 65
 	AddPathReceive        = 1
 	AddPathSend           = 2
 	AddPathSendReceive    = 3
+	ASTransASN            = 23456
 )
 
 type BGPError struct {
@@ -119,7 +121,7 @@ type BGPHeader struct {
 
 type BGPOpen struct {
 	Version       uint8
-	AS            uint16
+	ASN           uint16
 	HoldTime      uint16
 	BGPIdentifier uint32
 	OptParmLen    uint8
@@ -171,13 +173,6 @@ type NLRIAddPath struct {
 	Next           *NLRIAddPath
 }
 
-type ASPath []ASPathSegment
-type ASPathSegment struct {
-	Type  uint8
-	Count uint8
-	ASNs  []uint32
-}
-
 type Aggretator struct {
 	Addr uint32
 	ASN  uint16
diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go
index 31583bf1b68257625dfaa3d8b8e40893982d6713..c4d583f2138edfbbfb5b9be33b2a1c11764ee0bd 100644
--- a/protocols/bgp/packet/decoder.go
+++ b/protocols/bgp/packet/decoder.go
@@ -10,13 +10,13 @@ import (
 )
 
 // Decode decodes a BGP message
-func Decode(buf *bytes.Buffer) (*BGPMessage, error) {
+func Decode(buf *bytes.Buffer, opt *Options) (*BGPMessage, error) {
 	hdr, err := decodeHeader(buf)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to decode header: %v", err)
 	}
 
-	body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen)
+	body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen, opt)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to decode message: %v", err)
 	}
@@ -27,12 +27,12 @@ func Decode(buf *bytes.Buffer) (*BGPMessage, error) {
 	}, nil
 }
 
-func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, error) {
+func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16, opt *Options) (interface{}, error) {
 	switch msgType {
 	case OpenMsg:
 		return decodeOpenMsg(buf)
 	case UpdateMsg:
-		return decodeUpdateMsg(buf, l)
+		return decodeUpdateMsg(buf, l, opt)
 	case KeepaliveMsg:
 		return nil, nil // Nothing to decode in Keepalive message
 	case NotificationMsg:
@@ -41,7 +41,7 @@ func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, err
 	return nil, fmt.Errorf("Unknown message type: %d", msgType)
 }
 
-func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) {
+func decodeUpdateMsg(buf *bytes.Buffer, l uint16, opt *Options) (*BGPUpdate, error) {
 	msg := &BGPUpdate{}
 
 	err := decode(buf, []interface{}{&msg.WithdrawnRoutesLen})
@@ -59,7 +59,7 @@ func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) {
 		return msg, err
 	}
 
-	msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen)
+	msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen, opt)
 	if err != nil {
 		return msg, err
 	}
@@ -141,7 +141,7 @@ func _decodeOpenMsg(buf *bytes.Buffer) (interface{}, error) {
 
 	fields := []interface{}{
 		&msg.Version,
-		&msg.AS,
+		&msg.ASN,
 		&msg.HoldTime,
 		&msg.BGPIdentifier,
 		&msg.OptParmLen,
@@ -238,6 +238,12 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) {
 			return cap, fmt.Errorf("Unable to decode add path capability")
 		}
 		cap.Value = addPathCap
+	case ASN4CapabilityCode:
+		asn4Cap, err := decodeASN4Capability(buf)
+		if err != nil {
+			return cap, fmt.Errorf("Unable to decode 4 octet ASN capability")
+		}
+		cap.Value = asn4Cap
 	default:
 		for i := uint8(0); i < cap.Length; i++ {
 			_, err := buf.ReadByte()
@@ -266,6 +272,20 @@ func decodeAddPathCapability(buf *bytes.Buffer) (AddPathCapability, error) {
 	return addPathCap, nil
 }
 
+func decodeASN4Capability(buf *bytes.Buffer) (ASN4Capability, error) {
+	asn4Cap := ASN4Capability{}
+	fields := []interface{}{
+		&asn4Cap.ASN4,
+	}
+
+	err := decode(buf, fields)
+	if err != nil {
+		return asn4Cap, err
+	}
+
+	return asn4Cap, nil
+}
+
 func validateOpen(msg *BGPOpen) error {
 	if msg.Version != BGP4Version {
 		return BGPError{
diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go
index a9ae237b9cb94a3e0ec6af0b988bc09f41d3f806..56416186ba1d714a13fb4f248281d3d5f48451dd 100644
--- a/protocols/bgp/packet/decoder_test.go
+++ b/protocols/bgp/packet/decoder_test.go
@@ -3,6 +3,7 @@ package packet
 import (
 	"bytes"
 	"fmt"
+	"strconv"
 	"testing"
 
 	"github.com/bio-routing/bio-rd/net"
@@ -70,7 +71,7 @@ func BenchmarkDecodeUpdateMsg(b *testing.B) {
 
 	for i := 0; i < b.N; i++ {
 		buf := bytes.NewBuffer(input)
-		_, err := decodeUpdateMsg(buf, uint16(len(input)))
+		_, err := decodeUpdateMsg(buf, uint16(len(input)), &Options{})
 		if err != nil {
 			fmt.Printf("decodeUpdateMsg failed: %v\n", err)
 		}
@@ -156,7 +157,7 @@ func TestDecode(t *testing.T) {
 				},
 				Body: &BGPOpen{
 					Version:       4,
-					AS:            200,
+					ASN:           200,
 					HoldTime:      15,
 					BGPIdentifier: uint32(169090600),
 					OptParmLen:    0,
@@ -184,7 +185,7 @@ func TestDecode(t *testing.T) {
 				},
 				Body: &BGPOpen{
 					Version:       4,
-					AS:            200,
+					ASN:           200,
 					HoldTime:      15,
 					BGPIdentifier: uint32(100),
 				},
@@ -251,7 +252,7 @@ func TestDecode(t *testing.T) {
 
 	for _, test := range tests {
 		buf := bytes.NewBuffer(test.input)
-		msg, err := Decode(buf)
+		msg, err := Decode(buf, &Options{})
 
 		if err != nil && !test.wantFail {
 			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
@@ -1369,9 +1370,9 @@ func TestDecodeUpdateMsg(t *testing.T) {
 		},
 		{
 			// 2 withdraws with four path attributes (Communities + AS4Path +AS4Aggregator + Origin), valid update
-			testNum: 19,
+			testNum: 20,
 			input: []byte{0, 5, 8, 10, 16, 192, 168,
-				0, 30, // Total Path Attribute Length
+				0, 32, // Total Path Attribute Length
 
 				0,          // Attribute flags
 				8,          // Attribute Type code (Community)
@@ -1379,10 +1380,12 @@ func TestDecodeUpdateMsg(t *testing.T) {
 				0, 0, 1, 0, // Arbitrary Community
 				0, 0, 1, 1, // Arbitrary Community
 
-				128,        // Attribute flags
-				17,         // Attribute Type code (AS4Path)
-				4,          // Length
-				0, 0, 2, 3, // Arbitrary Bytes
+				128,                    // Attribute flags
+				17,                     // Attribute Type code (AS4Path)
+				6,                      // Length
+				2,                      // AS_SEQUENCE
+				1,                      // Number of ASNs
+				0x00, 0x03, 0x17, 0xf3, // 202739
 
 				128,        // Attribute flags
 				18,         // Attribute Type code (AS4Aggregator)
@@ -1406,7 +1409,7 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						Pfxlen: 16,
 					},
 				},
-				TotalPathAttrLen: 30,
+				TotalPathAttrLen: 32,
 				PathAttributes: &PathAttribute{
 					Optional:       false,
 					Transitive:     false,
@@ -1420,9 +1423,15 @@ func TestDecodeUpdateMsg(t *testing.T) {
 						Transitive:     false,
 						Partial:        false,
 						ExtendedLength: false,
-						Length:         4,
+						Length:         6,
 						TypeCode:       17,
-						Value:          uint32(515),
+						Value: ASPath{
+							ASPathSegment{
+								Type:  2,
+								Count: 1,
+								ASNs:  []uint32{202739},
+							},
+						},
 						Next: &PathAttribute{
 							Optional:       true,
 							Transitive:     false,
@@ -1447,29 +1456,31 @@ func TestDecodeUpdateMsg(t *testing.T) {
 		},
 	}
 
+	t.Parallel()
+
 	for _, test := range tests {
-		buf := bytes.NewBuffer(test.input)
-		l := test.explicitLength
-		if l == 0 {
-			l = uint16(len(test.input))
-		}
-		msg, err := decodeUpdateMsg(buf, l)
+		t.Run(strconv.Itoa(test.testNum), func(t *testing.T) {
+			buf := bytes.NewBuffer(test.input)
+			l := test.explicitLength
+			if l == 0 {
+				l = uint16(len(test.input))
+			}
+			msg, err := decodeUpdateMsg(buf, l, &Options{})
 
-		if err != nil && !test.wantFail {
-			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
-			continue
-		}
+			if err != nil && !test.wantFail {
+				t.Fatalf("Unexpected error in test %d: %v", test.testNum, err)
+			}
 
-		if err == nil && test.wantFail {
-			t.Errorf("Expected error did not happen in test %d", test.testNum)
-			continue
-		}
+			if err == nil && test.wantFail {
+				t.Fatalf("Expected error did not happen in test %d", test.testNum)
+			}
 
-		if err != nil && test.wantFail {
-			continue
-		}
+			if err != nil && test.wantFail {
+				return
+			}
 
-		assert.Equalf(t, test.expected, msg, "%d", test.testNum)
+			assert.Equalf(t, test.expected, msg, "%d", test.testNum)
+		})
 	}
 }
 
@@ -1490,7 +1501,7 @@ func TestDecodeMsgBody(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := decodeMsgBody(test.buffer, test.msgType, test.length)
+		res, err := decodeMsgBody(test.buffer, test.msgType, test.length, &Options{})
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error dit not happen in test %q", test.name)
 		}
@@ -1512,7 +1523,7 @@ func TestDecodeOpenMsg(t *testing.T) {
 			wantFail: false,
 			expected: &BGPOpen{
 				Version:       4,
-				AS:            257,
+				ASN:           257,
 				HoldTime:      15,
 				BGPIdentifier: 169090600,
 				OptParmLen:    0,
diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go
index ea87d1ff64cfc946688e738ae9f7ffa372cf10c7..ac9bc4483199b81f4e8ecc25c6b91e3ec8361b9c 100644
--- a/protocols/bgp/packet/encoder.go
+++ b/protocols/bgp/packet/encoder.go
@@ -35,7 +35,7 @@ func SerializeOpenMsg(msg *BGPOpen) []byte {
 	serializeHeader(buf, openLen, OpenMsg)
 
 	buf.WriteByte(msg.Version)
-	buf.Write(convert.Uint16Byte(msg.AS))
+	buf.Write(convert.Uint16Byte(msg.ASN))
 	buf.Write(convert.Uint16Byte(msg.HoldTime))
 	buf.Write(convert.Uint32Byte(msg.BGPIdentifier))
 
@@ -63,7 +63,7 @@ func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) {
 	buf.WriteByte(typ)
 }
 
-func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) {
+func (b *BGPUpdateAddPath) SerializeUpdate(opt *Options) ([]byte, error) {
 	budget := MaxLen - MinLen
 	buf := bytes.NewBuffer(nil)
 
@@ -78,7 +78,7 @@ func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) {
 
 	pathAttributesBuf := bytes.NewBuffer(nil)
 	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf))
+		paLen := int(pa.serialize(pathAttributesBuf, opt))
 		budget -= paLen
 		if budget < 0 {
 			return nil, fmt.Errorf("update too long")
@@ -122,7 +122,7 @@ func (b *BGPUpdateAddPath) SerializeUpdate() ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
-func (b *BGPUpdate) SerializeUpdate() ([]byte, error) {
+func (b *BGPUpdate) SerializeUpdate(opt *Options) ([]byte, error) {
 	budget := MaxLen - MinLen
 	buf := bytes.NewBuffer(nil)
 
@@ -137,7 +137,7 @@ func (b *BGPUpdate) SerializeUpdate() ([]byte, error) {
 
 	pathAttributesBuf := bytes.NewBuffer(nil)
 	for pa := b.PathAttributes; pa != nil; pa = pa.Next {
-		paLen := int(pa.serialize(pathAttributesBuf))
+		paLen := int(pa.serialize(pathAttributesBuf, opt))
 		budget -= paLen
 		if budget < 0 {
 			return nil, fmt.Errorf("update too long")
diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go
index 0d4f70a3483dabd8f7332dd148c72b09df626dee..fd7405d735a5d11a4117d144e7086828c86f5628 100644
--- a/protocols/bgp/packet/encoder_test.go
+++ b/protocols/bgp/packet/encoder_test.go
@@ -70,7 +70,7 @@ func TestSerializeOpenMsg(t *testing.T) {
 			name: "Valid #1",
 			input: &BGPOpen{
 				Version:       4,
-				AS:            15169,
+				ASN:           15169,
 				HoldTime:      120,
 				BGPIdentifier: convert.Uint32([]byte{100, 111, 120, 130}),
 				OptParmLen:    0,
diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/packet/large_community.go
index 5b3f7788b16c921f114880ac36d04e8868938627..4d93eb600bff85f3899e3a3df8246002f903dfb4 100644
--- a/protocols/bgp/packet/large_community.go
+++ b/protocols/bgp/packet/large_community.go
@@ -12,7 +12,7 @@ type LargeCommunity struct {
 	DataPart2           uint32
 }
 
-func (c LargeCommunity) String() string {
+func (c *LargeCommunity) String() string {
 	return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2)
 }
 
diff --git a/protocols/bgp/packet/options.go b/protocols/bgp/packet/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2d35bc60cbe17490f94314c28da26de9a19e40a
--- /dev/null
+++ b/protocols/bgp/packet/options.go
@@ -0,0 +1,5 @@
+package packet
+
+type Options struct {
+	Supports4OctetASN bool
+}
diff --git a/protocols/bgp/packet/parameters.go b/protocols/bgp/packet/parameters.go
index 8edf42fc58e05bb3b73cacd4272012844bedcd45..0ed9af66fe01f96600c02eeb2c00b69b11edc76c 100644
--- a/protocols/bgp/packet/parameters.go
+++ b/protocols/bgp/packet/parameters.go
@@ -52,3 +52,11 @@ func (a AddPathCapability) serialize(buf *bytes.Buffer) {
 	buf.WriteByte(a.SAFI)
 	buf.WriteByte(a.SendReceive)
 }
+
+type ASN4Capability struct {
+	ASN4 uint32
+}
+
+func (a ASN4Capability) serialize(buf *bytes.Buffer) {
+	buf.Write(convert.Uint32Byte(a.ASN4))
+}
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
index 3b3192034590bd357b29a13833a2bcaaa632f0cf..8be484780ee571432d159e63e7b2d4fcf86d2328 100644
--- a/protocols/bgp/packet/path_attributes.go
+++ b/protocols/bgp/packet/path_attributes.go
@@ -3,13 +3,11 @@ package packet
 import (
 	"bytes"
 	"fmt"
-	"strconv"
-	"strings"
 
 	"github.com/taktv6/tflow2/convert"
 )
 
-func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) {
+func decodePathAttrs(buf *bytes.Buffer, tpal uint16, opt *Options) (*PathAttribute, error) {
 	var ret *PathAttribute
 	var eol *PathAttribute
 	var pa *PathAttribute
@@ -18,7 +16,7 @@ func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) {
 
 	p := uint16(0)
 	for p < tpal {
-		pa, consumed, err = decodePathAttr(buf)
+		pa, consumed, err = decodePathAttr(buf, opt)
 		if err != nil {
 			return nil, fmt.Errorf("Unable to decode path attr: %v", err)
 		}
@@ -36,7 +34,7 @@ func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) {
 	return ret, nil
 }
 
-func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err error) {
+func decodePathAttr(buf *bytes.Buffer, opt *Options) (pa *PathAttribute, consumed uint16, err error) {
 	pa = &PathAttribute{}
 
 	err = decodePathAttrFlags(buf, pa)
@@ -63,9 +61,18 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err
 			return nil, consumed, fmt.Errorf("Failed to decode Origin: %v", err)
 		}
 	case ASPathAttr:
-		if err := pa.decodeASPath(buf); err != nil {
+		asnLength := uint8(2)
+		if opt.Supports4OctetASN {
+			asnLength = 4
+		}
+
+		if err := pa.decodeASPath(buf, asnLength); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to decode AS Path: %v", err)
 		}
+	case AS4PathAttr:
+		if err := pa.decodeASPath(buf, 4); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to decode AS4 Path: %v", err)
+		}
 	case NextHopAttr:
 		if err := pa.decodeNextHop(buf); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to decode Next-Hop: %v", err)
@@ -88,10 +95,6 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err
 		if err := pa.decodeCommunities(buf); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err)
 		}
-	case AS4PathAttr:
-		if err := pa.decodeAS4Path(buf); err != nil {
-			return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Path: %v", err)
-		}
 	case AS4AggregatorAttr:
 		if err := pa.decodeAS4Aggregator(buf); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err)
@@ -139,14 +142,11 @@ func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error {
 	return dumpNBytes(buf, pa.Length-p)
 }
 
-func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error {
+func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer, asnLength uint8) error {
 	pa.Value = make(ASPath, 0)
-
 	p := uint16(0)
 	for p < pa.Length {
-		segment := ASPathSegment{
-			ASNs: make([]uint32, 0),
-		}
+		segment := ASPathSegment{}
 
 		err := decode(buf, []interface{}{&segment.Type, &segment.Count})
 		if err != nil {
@@ -162,23 +162,51 @@ func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error {
 			return fmt.Errorf("Invalid AS Path segment length: %d", segment.Count)
 		}
 
+		segment.ASNs = make([]uint32, segment.Count)
 		for i := uint8(0); i < segment.Count; i++ {
-			asn := uint16(0)
-
-			err := decode(buf, []interface{}{&asn})
+			asn, err := pa.decodeASN(buf, asnLength)
 			if err != nil {
 				return err
 			}
-			p += 2
+			p += uint16(asnLength)
 
-			segment.ASNs = append(segment.ASNs, uint32(asn))
+			segment.ASNs[i] = asn
 		}
+
 		pa.Value = append(pa.Value.(ASPath), segment)
 	}
 
 	return nil
 }
 
+func (pa *PathAttribute) decodeASN(buf *bytes.Buffer, asnSize uint8) (asn uint32, err error) {
+	if asnSize == 4 {
+		return pa.decode4ByteASN(buf)
+	}
+
+	return pa.decode2ByteASN(buf)
+}
+
+func (pa *PathAttribute) decode4ByteASN(buf *bytes.Buffer) (asn uint32, err error) {
+	asn4 := uint32(0)
+	err = decode(buf, []interface{}{&asn4})
+	if err != nil {
+		return 0, err
+	}
+
+	return uint32(asn4), nil
+}
+
+func (pa *PathAttribute) decode2ByteASN(buf *bytes.Buffer) (asn uint32, err error) {
+	asn4 := uint16(0)
+	err = decode(buf, []interface{}{&asn4})
+	if err != nil {
+		return 0, err
+	}
+
+	return uint32(asn4), nil
+}
+
 func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error {
 	return pa.decodeUint32(buf, "next hop")
 }
@@ -273,10 +301,6 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error {
 	return nil
 }
 
-func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error {
-	return pa.decodeUint32(buf, "AS4Path")
-}
-
 func (pa *PathAttribute) decodeAS4Aggregator(buf *bytes.Buffer) error {
 	return pa.decodeUint32(buf, "AS4Aggregator")
 }
@@ -318,57 +342,6 @@ func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) {
 	return bytesRead, nil
 }
 
-func (pa *PathAttribute) ASPathString() (ret string) {
-	for _, p := range pa.Value.(ASPath) {
-		if p.Type == ASSet {
-			ret += " ("
-		}
-		n := len(p.ASNs)
-		for i, asn := range p.ASNs {
-			if i < n-1 {
-				ret += fmt.Sprintf("%d ", asn)
-				continue
-			}
-			ret += fmt.Sprintf("%d", asn)
-		}
-		if p.Type == ASSet {
-			ret += ")"
-		}
-	}
-
-	return
-}
-
-func (pa *PathAttribute) ASPathLen() (ret uint16) {
-	for _, p := range pa.Value.(ASPath) {
-		if p.Type == ASSet {
-			ret++
-			continue
-		}
-		ret += uint16(len(p.ASNs))
-	}
-
-	return
-}
-
-func (a *PathAttribute) CommunityString() string {
-	s := ""
-	for _, com := range a.Value.([]uint32) {
-		s += CommunityStringForUint32(com) + " "
-	}
-
-	return strings.TrimRight(s, " ")
-}
-
-func (a *PathAttribute) LargeCommunityString() string {
-	s := ""
-	for _, com := range a.Value.([]LargeCommunity) {
-		s += com.String() + " "
-	}
-
-	return strings.TrimRight(s, " ")
-}
-
 // dumpNBytes is used to dump n bytes of buf. This is useful in case an path attributes
 // length doesn't match a fixed length's attributes length (e.g. ORIGIN is always an octet)
 func dumpNBytes(buf *bytes.Buffer, n uint16) error {
@@ -383,14 +356,14 @@ func dumpNBytes(buf *bytes.Buffer, n uint16) error {
 	return nil
 }
 
-func (pa *PathAttribute) serialize(buf *bytes.Buffer) uint8 {
+func (pa *PathAttribute) serialize(buf *bytes.Buffer, opt *Options) uint8 {
 	pathAttrLen := uint8(0)
 
 	switch pa.TypeCode {
 	case OriginAttr:
 		pathAttrLen = pa.serializeOrigin(buf)
 	case ASPathAttr:
-		pathAttrLen = pa.serializeASPath(buf)
+		pathAttrLen = pa.serializeASPath(buf, opt)
 	case NextHopAttr:
 		pathAttrLen = pa.serializeNextHop(buf)
 	case MEDAttr:
@@ -421,21 +394,32 @@ func (pa *PathAttribute) serializeOrigin(buf *bytes.Buffer) uint8 {
 	return 4
 }
 
-func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer) uint8 {
+func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer, opt *Options) uint8 {
 	attrFlags := uint8(0)
 	attrFlags = setTransitive(attrFlags)
 	buf.WriteByte(attrFlags)
 	buf.WriteByte(ASPathAttr)
 
+	asnLength := uint8(2)
+	if opt.Supports4OctetASN {
+		asnLength = 4
+	}
+
 	length := uint8(0)
 	segmentsBuf := bytes.NewBuffer(nil)
 	for _, segment := range pa.Value.(ASPath) {
 		segmentsBuf.WriteByte(segment.Type)
 		segmentsBuf.WriteByte(uint8(len(segment.ASNs)))
+
 		for _, asn := range segment.ASNs {
-			segmentsBuf.Write(convert.Uint16Byte(uint16(asn)))
+			if asnLength == 2 {
+				segmentsBuf.Write(convert.Uint16Byte(uint16(asn)))
+			} else {
+				segmentsBuf.Write(convert.Uint32Byte(asn))
+			}
 		}
-		length += 2 + uint8(len(segment.ASNs))*2
+		fmt.Println(segment.ASNs)
+		length += 2 + uint8(len(segment.ASNs))*asnLength
 	}
 
 	buf.WriteByte(length)
@@ -550,135 +534,6 @@ func (pa *PathAttribute) serializeLargeCommunities(buf *bytes.Buffer) uint8 {
 	return length
 }
 
-/*func (pa *PathAttribute) PrependASPath(prepend []uint32) {
-	if pa.TypeCode != ASPathAttr {
-		return
-	}
-
-	asPath := pa.Value.(ASPath)
-	asPathSegementCount := len(asPath)
-	currentSegment := asPathSegementCount - 1
-
-	newSegmentNeeded := false
-	if asPath[asPathSegementCount-1].Type == ASSequence {
-		newSegmentNeeded = true
-	} else {
-		if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment {
-			newSegmentNeeded = true
-		}
-	}
-
-	for _, asn := range prepend {
-		if newSegmentNeeded {
-			segment := ASPathSegment{
-				Type: ASSequence,
-				ASNs: make([]uint32, 0),
-			},
-		}
-
-		asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, asn)
-		if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment {
-			newSegmentNeeded = true
-		}
-	}
-
-}*/
-
-// ParseASPathStr converts an AS path from string representation info an PathAttribute object
-func ParseASPathStr(asPathString string) (*PathAttribute, error) {
-	asPath := ASPath{}
-
-	currentType := ASSequence
-	newSegmentNeeded := true
-	currentSegment := -1
-	for _, asn := range strings.Split(asPathString, " ") {
-		if asn == "" {
-			continue
-		}
-
-		if isBeginOfASSet(asn) {
-			currentType = ASSet
-			newSegmentNeeded = true
-			asn = strings.Replace(asn, "(", "", 1)
-		}
-
-		if newSegmentNeeded {
-			seg := ASPathSegment{
-				Type: uint8(currentType),
-				ASNs: make([]uint32, 0),
-			}
-			asPath = append(asPath, seg)
-			currentSegment++
-			newSegmentNeeded = false
-		}
-
-		if isEndOfASSset(asn) {
-			currentType = ASSequence
-			newSegmentNeeded = true
-			asn = strings.Replace(asn, ")", "", 1)
-		}
-
-		numericASN, err := strconv.Atoi(asn)
-		if err != nil {
-			return nil, fmt.Errorf("Unable to convert ASN: %v", err)
-		}
-		asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, uint32(numericASN))
-
-		if len(asPath[currentSegment].ASNs) == MaxASNsSegment {
-			newSegmentNeeded = true
-		}
-	}
-
-	return &PathAttribute{
-		TypeCode: ASPathAttr,
-		Value:    asPath,
-	}, nil
-}
-
-func LargeCommunityAttributeForString(s string) (*PathAttribute, error) {
-	strs := strings.Split(s, " ")
-	coms := make([]LargeCommunity, len(strs))
-
-	var err error
-	for i, str := range strs {
-		coms[i], err = ParseLargeCommunityString(str)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return &PathAttribute{
-		TypeCode: LargeCommunitiesAttr,
-		Value:    coms,
-	}, nil
-}
-
-func CommunityAttributeForString(s string) (*PathAttribute, error) {
-	strs := strings.Split(s, " ")
-	coms := make([]uint32, len(strs))
-
-	var err error
-	for i, str := range strs {
-		coms[i], err = ParseCommunityString(str)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return &PathAttribute{
-		TypeCode: CommunitiesAttr,
-		Value:    coms,
-	}, nil
-}
-
-func isBeginOfASSet(asPathPart string) bool {
-	return strings.Contains(asPathPart, "(")
-}
-
-func isEndOfASSset(asPathPart string) bool {
-	return strings.Contains(asPathPart, ")")
-}
-
 func fourBytesToUint32(address [4]byte) uint32 {
 	return uint32(address[0])<<24 + uint32(address[1])<<16 + uint32(address[2])<<8 + uint32(address[3])
 }
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
index 248b731714389096a62e2381a548780beb8318e7..96200f4dc8f355da9b640388b097a3a2a9cc5062 100644
--- a/protocols/bgp/packet/path_attributes_test.go
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -50,7 +50,7 @@ func TestDecodePathAttrs(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)))
+		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &Options{})
 
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error did not happen for test %q", test.name)
@@ -173,7 +173,7 @@ func TestDecodePathAttr(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, _, err := decodePathAttr(bytes.NewBuffer(test.input))
+		res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &Options{})
 
 		if test.wantFail && err == nil {
 			t.Errorf("Expected error did not happen for test %q", test.name)
@@ -264,6 +264,7 @@ func TestDecodeASPath(t *testing.T) {
 		input          []byte
 		wantFail       bool
 		explicitLength uint16
+		use4OctetASNs  bool
 		expected       *PathAttribute
 	}{
 		{
@@ -308,6 +309,28 @@ func TestDecodeASPath(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "32 bit ASNs in AS_PATH",
+			input: []byte{
+				1, // AS_SEQUENCE
+				3, // Path Length
+				0, 0, 0, 100, 0, 0, 0, 222, 0, 0, 0, 240,
+			},
+			wantFail:      false,
+			use4OctetASNs: true,
+			expected: &PathAttribute{
+				Length: 14,
+				Value: ASPath{
+					ASPathSegment{
+						Type:  1,
+						Count: 3,
+						ASNs: []uint32{
+							100, 222, 240,
+						},
+					},
+				},
+			},
+		},
 		{
 			name:           "Empty input",
 			input:          []byte{},
@@ -326,28 +349,36 @@ func TestDecodeASPath(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		l := uint16(len(test.input))
-		if test.explicitLength != 0 {
-			l = test.explicitLength
-		}
-		pa := &PathAttribute{
-			Length: l,
-		}
-		err := pa.decodeASPath(bytes.NewBuffer(test.input))
+		t.Run(test.name, func(t *testing.T) {
+			l := uint16(len(test.input))
+			if test.explicitLength != 0 {
+				l = test.explicitLength
+			}
+			pa := &PathAttribute{
+				Length: l,
+			}
 
-		if test.wantFail && err == nil {
-			t.Errorf("Expected error did not happen for test %q", test.name)
-		}
+			asnLength := uint8(2)
+			if test.use4OctetASNs {
+				asnLength = 4
+			}
 
-		if !test.wantFail && err != nil {
-			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
-		}
+			err := pa.decodeASPath(bytes.NewBuffer(test.input), asnLength)
 
-		if err != nil {
-			continue
-		}
+			if test.wantFail && err == nil {
+				t.Errorf("Expected error did not happen for test %q", test.name)
+			}
 
-		assert.Equal(t, test.expected, pa)
+			if !test.wantFail && err != nil {
+				t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			}
+
+			if err != nil {
+				return
+			}
+
+			assert.Equal(t, test.expected, pa)
+		})
 	}
 }
 
@@ -841,107 +872,6 @@ func TestDecodeUint32(t *testing.T) {
 	}
 }
 
-func TestASPathString(t *testing.T) {
-	tests := []struct {
-		name     string
-		pa       *PathAttribute
-		expected string
-	}{
-		{
-			name: "Test #1",
-			pa: &PathAttribute{
-				Value: ASPath{
-					{
-						Type: ASSequence,
-						ASNs: []uint32{10, 20, 30},
-					},
-				},
-			},
-			expected: "10 20 30",
-		},
-		{
-			name: "Test #2",
-			pa: &PathAttribute{
-				Value: ASPath{
-					{
-						Type: ASSequence,
-						ASNs: []uint32{10, 20, 30},
-					},
-					{
-						Type: ASSet,
-						ASNs: []uint32{200, 300},
-					},
-				},
-			},
-			expected: "10 20 30 (200 300)",
-		},
-	}
-
-	for _, test := range tests {
-		res := test.pa.ASPathString()
-		assert.Equal(t, test.expected, res)
-	}
-}
-
-func TestLargeCommunityString(t *testing.T) {
-	tests := []struct {
-		name     string
-		pa       *PathAttribute
-		expected string
-	}{
-		{
-			name: "two attributes",
-			pa: &PathAttribute{
-				Value: []LargeCommunity{
-					{
-						GlobalAdministrator: 1,
-						DataPart1:           2,
-						DataPart2:           3,
-					},
-					{
-						GlobalAdministrator: 4,
-						DataPart1:           5,
-						DataPart2:           6,
-					},
-				},
-			},
-			expected: "(1,2,3) (4,5,6)",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(te *testing.T) {
-			res := test.pa.LargeCommunityString()
-			assert.Equal(te, test.expected, res)
-		})
-	}
-}
-
-func TestCommunityString(t *testing.T) {
-	tests := []struct {
-		name     string
-		pa       *PathAttribute
-		expected string
-	}{
-		{
-			name: "two attributes",
-			pa: &PathAttribute{
-				Value: []uint32{
-					131080, 16778241,
-				},
-			},
-			expected: "(2,8) (256,1025)",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(te *testing.T) {
-			res := test.pa.CommunityString()
-			assert.Equal(te, test.expected, res)
-		})
-	}
-}
-
 func TestSetOptional(t *testing.T) {
 	tests := []struct {
 		name     string
@@ -1239,6 +1169,7 @@ func TestSerializeASPath(t *testing.T) {
 		input       *PathAttribute
 		expected    []byte
 		expectedLen uint8
+		use32BitASN bool
 	}{
 		{
 			name: "Test #1",
@@ -1265,17 +1196,49 @@ func TestSerializeASPath(t *testing.T) {
 			},
 			expectedLen: 10,
 		},
+		{
+			name: "32bit ASN",
+			input: &PathAttribute{
+				TypeCode: ASPathAttr,
+				Value: ASPath{
+					{
+						Type: 2, // Sequence
+						ASNs: []uint32{
+							100, 200, 210,
+						},
+					},
+				},
+			},
+			expected: []byte{
+				64,           // Attribute flags
+				2,            // Type
+				14,           // Length
+				2,            // AS_SEQUENCE
+				3,            // ASN count
+				0, 0, 0, 100, // ASN 100
+				0, 0, 0, 200, // ASN 200
+				0, 0, 0, 210, // ASN 210
+			},
+			expectedLen: 16,
+			use32BitASN: true,
+		},
 	}
 
+	t.Parallel()
+
 	for _, test := range tests {
-		buf := bytes.NewBuffer(nil)
-		n := test.input.serializeASPath(buf)
-		if n != test.expectedLen {
-			t.Errorf("Unexpected length for test %q: %d", test.name, n)
-			continue
-		}
+		t.Run(test.name, func(t *testing.T) {
+			buf := bytes.NewBuffer(nil)
+			opt := &Options{
+				Supports4OctetASN: test.use32BitASN,
+			}
+			n := test.input.serializeASPath(buf, opt)
+			if n != test.expectedLen {
+				t.Fatalf("Unexpected length for test %q: %d", test.name, n)
+			}
 
-		assert.Equal(t, test.expected, buf.Bytes())
+			assert.Equal(t, test.expected, buf.Bytes())
+		})
 	}
 }
 
@@ -1561,7 +1524,8 @@ func TestSerialize(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := test.msg.SerializeUpdate()
+		opt := &Options{}
+		res, err := test.msg.SerializeUpdate(opt)
 		if err != nil {
 			if test.wantFail {
 				continue
@@ -1767,7 +1731,8 @@ func TestSerializeAddPath(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		res, err := test.msg.SerializeUpdate()
+		opt := &Options{}
+		res, err := test.msg.SerializeUpdate(opt)
 		if err != nil {
 			if test.wantFail {
 				continue
@@ -1786,108 +1751,6 @@ func TestSerializeAddPath(t *testing.T) {
 	}
 }
 
-func TestParseASPathStr(t *testing.T) {
-	tests := []struct {
-		name     string
-		input    string
-		wantFail bool
-		expected *PathAttribute
-	}{
-		{
-			name:     "Empty AS Path",
-			input:    "",
-			wantFail: false,
-			expected: &PathAttribute{
-				TypeCode: ASPathAttr,
-				Value:    ASPath{},
-			},
-		},
-		{
-			name:     "Simple AS_SEQUENCE",
-			input:    "3320 15169",
-			wantFail: false,
-			expected: &PathAttribute{
-				TypeCode: ASPathAttr,
-				Value: ASPath{
-					ASPathSegment{
-						Type: ASSequence,
-						ASNs: []uint32{3320, 15169},
-					},
-				},
-			},
-		},
-		{
-			name:     "AS_SEQUENCE with more than 255 elements",
-			input:    "123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123",
-			wantFail: false,
-			expected: &PathAttribute{
-				TypeCode: ASPathAttr,
-				Value: ASPath{
-					ASPathSegment{
-						Type: ASSequence,
-						ASNs: []uint32{123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123},
-					},
-					ASPathSegment{
-						Type: ASSequence,
-						ASNs: []uint32{123, 123, 123, 123, 123},
-					},
-				},
-			},
-		},
-		{
-			name:     "AS_SET only",
-			input:    "(3320 201701 15169)",
-			wantFail: false,
-			expected: &PathAttribute{
-				TypeCode: ASPathAttr,
-				Value: ASPath{
-					ASPathSegment{
-						Type: ASSet,
-						ASNs: []uint32{3320, 201701, 15169},
-					},
-				},
-			},
-		},
-		{
-			name:     "Mixed AS Path",
-			input:    "199714 51324 (3320 201701 15169)",
-			wantFail: false,
-			expected: &PathAttribute{
-				TypeCode: ASPathAttr,
-				Value: ASPath{
-					ASPathSegment{
-						Type: ASSequence,
-						ASNs: []uint32{199714, 51324},
-					},
-					ASPathSegment{
-						Type: ASSet,
-						ASNs: []uint32{3320, 201701, 15169},
-					},
-				},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		res, err := ParseASPathStr(test.input)
-		if err != nil {
-			if test.wantFail {
-				continue
-			}
-
-			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
-			continue
-		}
-
-		if test.wantFail {
-			t.Errorf("Unexpected success for test %q", test.name)
-			continue
-		}
-
-		assert.Equal(t, test.expected, res)
-	}
-}
-
 func TestFourBytesToUint32(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/protocols/bgp/server/BUILD.bazel b/protocols/bgp/server/BUILD.bazel
index 02f664a367f52241763b99d0e46bf462ff5b0f67..183cb1c9a288ac66a0713c1438ebe4baf4c7d8ae 100644
--- a/protocols/bgp/server/BUILD.bazel
+++ b/protocols/bgp/server/BUILD.bazel
@@ -33,6 +33,7 @@ go_library(
         "//routingtable/adjRIBIn:go_default_library",
         "//routingtable/adjRIBOut:go_default_library",
         "//routingtable/filter:go_default_library",
+        "//routingtable/locRIB:go_default_library",
         "//vendor/github.com/sirupsen/logrus:go_default_library",
     ],
 )
@@ -40,6 +41,8 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "fsm_open_sent_test.go",
+        "fsm_test.go",
         "server_test.go",
         "update_helper_test.go",
         "withdraw_test.go",
@@ -53,6 +56,7 @@ go_test(
         "//routingtable:go_default_library",
         "//routingtable/filter:go_default_library",
         "//routingtable/locRIB:go_default_library",
+        "//testing:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
     ],
 )
diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go
index 33e000d1c6010a2e3b976b13bec591a7e0e67545..cf970b40483511c2ecafc1d36597f66117de06ef 100644
--- a/protocols/bgp/server/fsm.go
+++ b/protocols/bgp/server/fsm.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -56,12 +57,14 @@ type FSM struct {
 	capAddPathSend bool
 	capAddPathRecv bool
 
+	options *packet.Options
+
 	local net.IP
 
 	ribsInitialized bool
 	adjRIBIn        routingtable.RouteTableClient
 	adjRIBOut       routingtable.RouteTableClient
-	rib             routingtable.RouteTableClient
+	rib             *locRIB.LocRIB
 	updateSender    routingtable.RouteTableClient
 
 	neighborID uint32
@@ -99,6 +102,7 @@ func newFSM2(peer *peer) *FSM {
 		msgRecvFailCh:    make(chan error),
 		stopMsgRecvCh:    make(chan struct{}),
 		rib:              peer.rib,
+		options:          &packet.Options{},
 	}
 }
 
@@ -219,13 +223,7 @@ func (fsm *FSM) resetConnectRetryCounter() {
 }
 
 func (fsm *FSM) sendOpen() error {
-	msg := packet.SerializeOpenMsg(&packet.BGPOpen{
-		Version:       BGPVersion,
-		AS:            uint16(fsm.peer.localASN),
-		HoldTime:      uint16(fsm.peer.holdTime / time.Second),
-		BGPIdentifier: fsm.peer.server.routerID,
-		OptParams:     fsm.peer.optOpenParams,
-	})
+	msg := packet.SerializeOpenMsg(fsm.openMessage())
 
 	_, err := fsm.con.Write(msg)
 	if err != nil {
@@ -235,6 +233,24 @@ func (fsm *FSM) sendOpen() error {
 	return nil
 }
 
+func (fsm *FSM) openMessage() *packet.BGPOpen {
+	return &packet.BGPOpen{
+		Version:       BGPVersion,
+		ASN:           fsm.local16BitASN(),
+		HoldTime:      uint16(fsm.peer.holdTime / time.Second),
+		BGPIdentifier: fsm.peer.routerID,
+		OptParams:     fsm.peer.optOpenParams,
+	}
+}
+
+func (fsm *FSM) local16BitASN() uint16 {
+	if fsm.peer.localASN > uint32(^uint16(0)) {
+		return packet.ASTransASN
+	}
+
+	return uint16(fsm.peer.localASN)
+}
+
 func (fsm *FSM) sendNotification(errorCode uint8, errorSubCode uint8) error {
 	msg := packet.SerializeNotificationMsg(&packet.BGPNotification{})
 
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index 80ff7fa7990493186924f8c35f1a90deb0c93e5b..e74a660ab859e00b2d70f66175ddc6c750ffd495 100644
--- a/protocols/bgp/server/fsm_established.go
+++ b/protocols/bgp/server/fsm_established.go
@@ -55,7 +55,10 @@ func (s establishedState) run() (state, string) {
 }
 
 func (s *establishedState) init() error {
-	s.fsm.adjRIBIn = adjRIBIn.New(s.fsm.peer.importFilter)
+	contributingASNs := s.fsm.rib.GetContributingASNs()
+
+	s.fsm.adjRIBIn = adjRIBIn.New(s.fsm.peer.importFilter, contributingASNs)
+	contributingASNs.Add(s.fsm.peer.localASN)
 	s.fsm.adjRIBIn.Register(s.fsm.rib)
 
 	host, _, err := net.SplitHostPort(s.fsm.con.LocalAddr().String())
@@ -96,6 +99,7 @@ func (s *establishedState) init() error {
 }
 
 func (s *establishedState) uninit() {
+	s.fsm.rib.GetContributingASNs().Remove(s.fsm.peer.localASN)
 	s.fsm.adjRIBIn.Unregister(s.fsm.rib)
 	s.fsm.rib.Unregister(s.fsm.adjRIBOut)
 	s.fsm.adjRIBOut.Unregister(s.fsm.updateSender)
@@ -154,7 +158,7 @@ func (s *establishedState) keepaliveTimerExpired() (state, string) {
 }
 
 func (s *establishedState) msgReceived(data []byte) (state, string) {
-	msg, err := packet.Decode(bytes.NewBuffer(data))
+	msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options)
 	if err != nil {
 		switch bgperr := err.(type) {
 		case packet.BGPError:
@@ -167,6 +171,7 @@ func (s *establishedState) msgReceived(data []byte) (state, string) {
 	}
 	switch msg.Header.Type {
 	case packet.NotificationMsg:
+		fmt.Println(data)
 		return s.notification()
 	case packet.UpdateMsg:
 		return s.update(msg)
@@ -226,12 +231,12 @@ func (s *establishedState) updates(u *packet.BGPUpdate) {
 			case packet.NextHopAttr:
 				path.BGPPath.NextHop = pa.Value.(uint32)
 			case packet.ASPathAttr:
-				path.BGPPath.ASPath = pa.ASPathString()
-				path.BGPPath.ASPathLen = pa.ASPathLen()
+				path.BGPPath.ASPath = pa.Value.(packet.ASPath)
+				path.BGPPath.ASPathLen = path.BGPPath.ASPath.Length()
 			case packet.CommunitiesAttr:
-				path.BGPPath.Communities = pa.CommunityString()
+				path.BGPPath.Communities = pa.Value.([]uint32)
 			case packet.LargeCommunitiesAttr:
-				path.BGPPath.LargeCommunities = pa.LargeCommunityString()
+				path.BGPPath.LargeCommunities = pa.Value.([]packet.LargeCommunity)
 			}
 		}
 		s.fsm.adjRIBIn.AddPath(pfx, path)
diff --git a/protocols/bgp/server/fsm_open_confirm.go b/protocols/bgp/server/fsm_open_confirm.go
index d2d25e4f533ec30d9a5d15aec25504d055c45f54..07b8a6152dd64c56055c7cbd0745980e462c3886 100644
--- a/protocols/bgp/server/fsm_open_confirm.go
+++ b/protocols/bgp/server/fsm_open_confirm.go
@@ -82,7 +82,7 @@ func (s *openConfirmState) keepaliveTimerExpired() (state, string) {
 }
 
 func (s *openConfirmState) msgReceived(data []byte) (state, string) {
-	msg, err := packet.Decode(bytes.NewBuffer(data))
+	msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options)
 	if err != nil {
 		switch bgperr := err.(type) {
 		case packet.BGPError:
diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go
index 63680636d8e0c65afe1dad9ec9d1280c34e9c37d..121ea883a1c4d2de0e9384e275c394608b8665b7 100644
--- a/protocols/bgp/server/fsm_open_sent.go
+++ b/protocols/bgp/server/fsm_open_sent.go
@@ -10,7 +10,8 @@ import (
 )
 
 type openSentState struct {
-	fsm *FSM
+	fsm         *FSM
+	peerASNRcvd uint32
 }
 
 func newOpenSentState(fsm *FSM) *openSentState {
@@ -73,7 +74,7 @@ func (s *openSentState) holdTimerExpired() (state, string) {
 }
 
 func (s *openSentState) msgReceived(data []byte) (state, string) {
-	msg, err := packet.Decode(bytes.NewBuffer(data))
+	msg, err := packet.Decode(bytes.NewBuffer(data), s.fsm.options)
 	if err != nil {
 		switch bgperr := err.(type) {
 		case packet.BGPError:
@@ -104,6 +105,8 @@ func (s *openSentState) unexpectedMessage() (state, string) {
 
 func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string) {
 	openMsg := msg.Body.(*packet.BGPOpen)
+	s.peerASNRcvd = uint32(openMsg.ASN)
+
 	s.fsm.neighborID = openMsg.BGPIdentifier
 	stopTimer(s.fsm.connectRetryTimer)
 	if s.fsm.peer.collisionHandling(s.fsm) {
@@ -114,6 +117,10 @@ func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string)
 		return s.tcpFailure()
 	}
 
+	return s.handleOpenMessage(openMsg)
+}
+
+func (s *openSentState) handleOpenMessage(openMsg *packet.BGPOpen) (state, string) {
 	s.fsm.holdTime = time.Duration(math.Min(float64(s.fsm.peer.holdTime), float64(time.Duration(openMsg.HoldTime)*time.Second)))
 	if s.fsm.holdTime != 0 {
 		if !s.fsm.holdTimer.Reset(s.fsm.holdTime) {
@@ -123,7 +130,14 @@ func (s *openSentState) openMsgReceived(msg *packet.BGPMessage) (state, string)
 		s.fsm.keepaliveTimer = time.NewTimer(s.fsm.keepaliveTime)
 	}
 
+	s.peerASNRcvd = uint32(openMsg.ASN)
 	s.processOpenOptions(openMsg.OptParams)
+
+	if s.peerASNRcvd != s.fsm.peer.peerASN {
+		s.fsm.sendNotification(packet.OpenMessageError, packet.BadPeerAS)
+		return newCeaseState(), fmt.Sprintf("Bad Peer AS %d, expected: %d", s.peerASNRcvd, s.fsm.peer.peerASN)
+	}
+
 	return newOpenConfirmState(s.fsm), "Received OPEN message"
 }
 
@@ -153,7 +167,8 @@ func (s *openSentState) processCapability(cap packet.Capability) {
 	switch cap.Code {
 	case packet.AddPathCapabilityCode:
 		s.processAddPathCapability(cap.Value.(packet.AddPathCapability))
-
+	case packet.ASN4CapabilityCode:
+		s.processASN4Capability(cap.Value.(packet.ASN4Capability))
 	}
 }
 
@@ -184,6 +199,14 @@ func (s *openSentState) processAddPathCapability(addPathCap packet.AddPathCapabi
 	}
 }
 
+func (s *openSentState) processASN4Capability(cap packet.ASN4Capability) {
+	s.fsm.options.Supports4OctetASN = true
+
+	if s.peerASNRcvd == packet.ASTransASN {
+		s.peerASNRcvd = cap.ASN4
+	}
+}
+
 func (s *openSentState) notification(msg *packet.BGPMessage) (state, string) {
 	stopTimer(s.fsm.connectRetryTimer)
 	s.fsm.con.Close()
diff --git a/protocols/bgp/server/fsm_open_sent_test.go b/protocols/bgp/server/fsm_open_sent_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe34eede357c762de2674701132ddfa91bf170b9
--- /dev/null
+++ b/protocols/bgp/server/fsm_open_sent_test.go
@@ -0,0 +1,89 @@
+package server
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	btesting "github.com/bio-routing/bio-rd/testing"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOpenMsgReceived(t *testing.T) {
+	tests := []struct {
+		asn        uint32
+		name       string
+		msg        packet.BGPOpen
+		wantsCease bool
+	}{
+		{
+			name: "valid open message (16bit ASN)",
+			asn:  12345,
+			msg: packet.BGPOpen{
+				HoldTime:      90,
+				BGPIdentifier: 1,
+				Version:       4,
+				ASN:           12345,
+			},
+		},
+		{
+			name: "valid open message (32bit ASN)",
+			asn:  202739,
+			msg: packet.BGPOpen{
+				HoldTime:      90,
+				BGPIdentifier: 1,
+				Version:       4,
+				ASN:           23456,
+				OptParmLen:    1,
+				OptParams: []packet.OptParam{
+					{
+						Type:   packet.CapabilitiesParamType,
+						Length: 6,
+						Value: packet.Capabilities{
+							packet.Capability{
+								Code:   packet.ASN4CapabilityCode,
+								Length: 4,
+								Value: packet.ASN4Capability{
+									ASN4: 202739,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "open message does not match configured remote ASN",
+			asn:  12345,
+			msg: packet.BGPOpen{
+				HoldTime:      90,
+				BGPIdentifier: 1,
+				Version:       4,
+				ASN:           54321,
+			},
+			wantsCease: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			fsm := newFSM2(&peer{
+				peerASN: test.asn,
+			})
+			fsm.con = &btesting.MockConn{}
+
+			s := &openSentState{
+				fsm: fsm,
+			}
+
+			state, _ := s.handleOpenMessage(&test.msg)
+
+			if test.wantsCease {
+				assert.IsType(t, &ceaseState{}, state, "state")
+				return
+			}
+
+			assert.IsType(t, &openConfirmState{}, state, "state")
+			assert.Equal(t, test.asn, s.peerASNRcvd, "asn")
+		})
+	}
+}
diff --git a/protocols/bgp/server/fsm_test.go b/protocols/bgp/server/fsm_test.go
index 1382af03915ffe0b3d6b8c23377dcc7e29be78e9..a6f6bd7a19e2c571c374df2daa59914d02640a66 100644
--- a/protocols/bgp/server/fsm_test.go
+++ b/protocols/bgp/server/fsm_test.go
@@ -1,15 +1,15 @@
 package server
 
 import (
+	"net"
 	"sync"
 	"testing"
 	"time"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/routingtable/filter"
-
-	"net"
-
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	"github.com/stretchr/testify/assert"
 )
 
 // TestFSM100Updates emulates receiving 100 BGP updates and withdraws. Checks route counts.
@@ -121,3 +121,92 @@ func TestFSM100Updates(t *testing.T) {
 	fsmA.eventCh <- ManualStop
 	wg.Wait()
 }
+
+func TestOpenMessage(t *testing.T) {
+	tests := []struct {
+		name     string
+		localASN uint32
+		holdTime time.Duration
+		routerID uint32
+		expected packet.BGPOpen
+	}{
+		{
+			name:     "16bit ASN",
+			localASN: 12345,
+			holdTime: time.Duration(30 * time.Second),
+			routerID: 1,
+			expected: packet.BGPOpen{
+				ASN:           12345,
+				BGPIdentifier: 1,
+				HoldTime:      30,
+				OptParams: []packet.OptParam{
+					packet.OptParam{
+						Type: packet.CapabilitiesParamType,
+						Value: packet.Capabilities{
+							packet.Capability{
+								Code: 65,
+								Value: packet.ASN4Capability{
+									ASN4: 12345,
+								},
+							},
+						},
+					},
+				},
+				Version: 4,
+			},
+		},
+		{
+			name:     "32bit ASN",
+			localASN: 202739,
+			holdTime: time.Duration(30 * time.Second),
+			routerID: 1,
+			expected: packet.BGPOpen{
+				ASN:           23456,
+				BGPIdentifier: 1,
+				HoldTime:      30,
+				OptParams: []packet.OptParam{
+					packet.OptParam{
+						Type: packet.CapabilitiesParamType,
+						Value: packet.Capabilities{
+							packet.Capability{
+								Code: 65,
+								Value: packet.ASN4Capability{
+									ASN4: 202739,
+								},
+							},
+						},
+					},
+				},
+				Version: 4,
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			p := peer{
+				localASN: test.localASN,
+				holdTime: test.holdTime,
+				routerID: test.routerID,
+				optOpenParams: []packet.OptParam{
+					packet.OptParam{
+						Type: packet.CapabilitiesParamType,
+						Value: packet.Capabilities{
+							packet.Capability{
+								Code: 65,
+								Value: packet.ASN4Capability{
+									ASN4: test.localASN,
+								},
+							},
+						},
+					},
+				},
+			}
+
+			fsm := newFSM2(&p)
+			msg := fsm.openMessage()
+
+			assert.Equal(t, &test.expected, msg)
+		})
+	}
+}
diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go
index bad268285a156e5d32576af75521f42a7436a603..125393fb8fbfbfb5ed4bb626a5bc0aa352094d82 100644
--- a/protocols/bgp/server/peer.go
+++ b/protocols/bgp/server/peer.go
@@ -9,6 +9,7 @@ import (
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/routingtable"
 	"github.com/bio-routing/bio-rd/routingtable/filter"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 )
 
 type PeerInfo struct {
@@ -27,7 +28,7 @@ type peer struct {
 	fsms   []*FSM
 	fsmsMu sync.Mutex
 
-	rib               routingtable.RouteTableClient
+	rib               *locRIB.LocRIB
 	routerID          uint32
 	addPathSend       routingtable.ClientOptions
 	addPathRecv       bool
@@ -100,7 +101,7 @@ func isEstablishedState(s state) bool {
 
 // NewPeer creates a new peer with the given config. If an connection is established, the adjRIBIN of the peer is connected
 // to the given rib. To actually connect the peer, call Start() on the returned peer.
-func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer) (*peer, error) {
+func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error) {
 	if c.LocalAS == 0 {
 		c.LocalAS = server.localASN
 	}
@@ -125,24 +126,12 @@ func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer
 
 	caps := make([]packet.Capability, 0)
 
-	addPath := uint8(0)
-	if c.AddPathRecv {
-		addPath += packet.AddPathReceive
-	}
-	if !c.AddPathSend.BestOnly {
-		addPath += packet.AddPathSend
+	addPathEnabled, addPathCap := handleAddPathCapability(c)
+	if addPathEnabled {
+		caps = append(caps, addPathCap)
 	}
 
-	if addPath > 0 {
-		caps = append(caps, packet.Capability{
-			Code: packet.AddPathCapabilityCode,
-			Value: packet.AddPathCapability{
-				AFI:         packet.IPv4AFI,
-				SAFI:        packet.UnicastSAFI,
-				SendReceive: addPath,
-			},
-		})
-	}
+	caps = append(caps, asn4Capability(c))
 
 	for _, cap := range caps {
 		p.optOpenParams = append(p.optOpenParams, packet.OptParam{
@@ -154,6 +143,38 @@ func newPeer(c config.Peer, rib routingtable.RouteTableClient, server *bgpServer
 	return p, nil
 }
 
+func asn4Capability(c config.Peer) packet.Capability {
+	return packet.Capability{
+		Code: packet.ASN4CapabilityCode,
+		Value: packet.ASN4Capability{
+			ASN4: c.LocalAS,
+		},
+	}
+}
+
+func handleAddPathCapability(c config.Peer) (bool, packet.Capability) {
+	addPath := uint8(0)
+	if c.AddPathRecv {
+		addPath += packet.AddPathReceive
+	}
+	if !c.AddPathSend.BestOnly {
+		addPath += packet.AddPathSend
+	}
+
+	if addPath == 0 {
+		return false, packet.Capability{}
+	}
+
+	return true, packet.Capability{
+		Code: packet.AddPathCapabilityCode,
+		Value: packet.AddPathCapability{
+			AFI:         packet.IPv4AFI,
+			SAFI:        packet.UnicastSAFI,
+			SendReceive: addPath,
+		},
+	}
+}
+
 func filterOrDefault(f *filter.Filter) *filter.Filter {
 	if f != nil {
 		return f
diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go
index 61c79de10315e5efa65c27e7da820b3a39aee4df..db6ae24f310cf027114e5e95aee7fee3211aac8b 100644
--- a/protocols/bgp/server/server.go
+++ b/protocols/bgp/server/server.go
@@ -9,12 +9,11 @@ import (
 
 	"github.com/bio-routing/bio-rd/config"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 	log "github.com/sirupsen/logrus"
 )
 
 const (
-	uint16max  = 65535
 	BGPVersion = 4
 )
 
@@ -29,7 +28,7 @@ type bgpServer struct {
 type BGPServer interface {
 	RouterID() uint32
 	Start(*config.Global) error
-	AddPeer(config.Peer, routingtable.RouteTableClient) error
+	AddPeer(config.Peer, *locRIB.LocRIB) error
 	GetPeerInfoAll() map[string]PeerInfo
 }
 
@@ -113,11 +112,7 @@ func (b *bgpServer) incomingConnectionWorker() {
 	}
 }
 
-func (b *bgpServer) AddPeer(c config.Peer, rib routingtable.RouteTableClient) error {
-	if c.LocalAS > uint16max || c.PeerAS > uint16max {
-		return fmt.Errorf("32bit ASNs are not supported yet")
-	}
-
+func (b *bgpServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error {
 	peer, err := newPeer(c, rib, b)
 	if err != nil {
 		return err
diff --git a/protocols/bgp/server/server_test.go b/protocols/bgp/server/server_test.go
index 86095be8e22e28168e2b66c1a70217969263fe18..baf30a19344cb12c284ec22eb364809d7dc4cd72 100644
--- a/protocols/bgp/server/server_test.go
+++ b/protocols/bgp/server/server_test.go
@@ -11,23 +11,6 @@ import (
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 )
 
-func TestBgpServerConfigCheck(t *testing.T) {
-	s := NewBgpServer()
-
-	err := s.Start(&config.Global{})
-	if err == nil {
-		t.Fatalf("server with empty config should not start")
-	}
-
-	err = s.Start(&config.Global{
-		LocalASN: 204880,
-		RouterID: 2137,
-	})
-	if err != nil {
-		t.Fatalf("server should have started, got err: %v", err)
-	}
-}
-
 func TestBgpServerPeerSnapshot(t *testing.T) {
 	s := NewBgpServer()
 	err := s.Start(&config.Global{
diff --git a/protocols/bgp/server/update_helper.go b/protocols/bgp/server/update_helper.go
index e0a24a6e3bbf1369b59b25d04f1f6c3c16930844..50850cb6e3747f95bee8dd802797269bb3d63724 100644
--- a/protocols/bgp/server/update_helper.go
+++ b/protocols/bgp/server/update_helper.go
@@ -3,7 +3,6 @@ package server
 import (
 	"fmt"
 	"io"
-	"strings"
 
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/route"
@@ -11,22 +10,22 @@ import (
 )
 
 func pathAttribues(p *route.Path) (*packet.PathAttribute, error) {
-	asPathPA, err := packet.ParseASPathStr(strings.TrimRight(p.BGPPath.ASPath, " "))
-	if err != nil {
-		return nil, fmt.Errorf("Unable to parse AS path: %v", err)
+	asPath := &packet.PathAttribute{
+		TypeCode: packet.ASPathAttr,
+		Value:    p.BGPPath.ASPath,
 	}
 
 	origin := &packet.PathAttribute{
 		TypeCode: packet.OriginAttr,
 		Value:    p.BGPPath.Origin,
-		Next:     asPathPA,
 	}
+	asPath.Next = origin
 
 	nextHop := &packet.PathAttribute{
 		TypeCode: packet.NextHopAttr,
 		Value:    p.BGPPath.NextHop,
 	}
-	asPathPA.Next = nextHop
+	origin.Next = nextHop
 
 	localPref := &packet.PathAttribute{
 		TypeCode: packet.LocalPrefAttr,
@@ -42,28 +41,26 @@ func pathAttribues(p *route.Path) (*packet.PathAttribute, error) {
 		}
 	}
 
-	return origin, nil
+	return asPath, nil
 }
 
 func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error {
 	current := parent
 
 	if len(p.BGPPath.Communities) > 0 {
-		communities, err := packet.CommunityAttributeForString(p.BGPPath.Communities)
-		if err != nil {
-			return fmt.Errorf("Could not create communities attribute: %v", err)
+		communities := &packet.PathAttribute{
+			TypeCode: packet.CommunitiesAttr,
+			Value:    p.BGPPath.Communities,
 		}
-
 		current.Next = communities
 		current = communities
 	}
 
 	if len(p.BGPPath.LargeCommunities) > 0 {
-		largeCommunities, err := packet.LargeCommunityAttributeForString(p.BGPPath.LargeCommunities)
-		if err != nil {
-			return fmt.Errorf("Could not create large communities attribute: %v", err)
+		largeCommunities := &packet.PathAttribute{
+			TypeCode: packet.LargeCommunitiesAttr,
+			Value:    p.BGPPath.LargeCommunities,
 		}
-
 		current.Next = largeCommunities
 		current = largeCommunities
 	}
@@ -72,11 +69,11 @@ func addOptionalPathAttribues(p *route.Path, parent *packet.PathAttribute) error
 }
 
 type serializeAbleUpdate interface {
-	SerializeUpdate() ([]byte, error)
+	SerializeUpdate(opt *packet.Options) ([]byte, error)
 }
 
-func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate) error {
-	updateBytes, err := update.SerializeUpdate()
+func serializeAndSendUpdate(out io.Writer, update serializeAbleUpdate, opt *packet.Options) error {
+	updateBytes, err := update.SerializeUpdate(opt)
 	if err != nil {
 		log.Errorf("Unable to serialize BGP Update: %v", err)
 		return nil
diff --git a/protocols/bgp/server/update_helper_test.go b/protocols/bgp/server/update_helper_test.go
index 5752edded4aba03b66acd88950796e21c2c82620..d2d0a7fcf424276b6a351176cebc9a3959f9ec69 100644
--- a/protocols/bgp/server/update_helper_test.go
+++ b/protocols/bgp/server/update_helper_test.go
@@ -16,7 +16,7 @@ import (
 
 type failingUpdate struct{}
 
-func (f *failingUpdate) SerializeUpdate() ([]byte, error) {
+func (f *failingUpdate) SerializeUpdate(opt *packet.Options) ([]byte, error) {
 	return nil, errors.New("general error")
 }
 
@@ -94,12 +94,12 @@ func TestSerializeAndSendUpdate(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			err := serializeAndSendUpdate(test.buf, test.testUpdate)
+			opt := &packet.Options{}
+			err := serializeAndSendUpdate(test.buf, test.testUpdate, opt)
 			assert.Equal(t, test.err, err)
 
 			assert.Equal(t, test.expected, test.buf.Bytes())
 		})
-
 	}
 }
 
diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go
index b389ae117076e0df9e4755ac173a95f02173b28f..23da78c532952a70bce63b8cc9570dba2d9e503e 100644
--- a/protocols/bgp/server/update_sender.go
+++ b/protocols/bgp/server/update_sender.go
@@ -42,12 +42,12 @@ func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error {
 		},
 	}
 
-	return serializeAndSendUpdate(u.fsm.con, update)
+	return serializeAndSendUpdate(u.fsm.con, update, u.fsm.options)
 }
 
 // RemovePath withdraws prefix `pfx` from a peer
 func (u *UpdateSender) RemovePath(pfx net.Prefix, p *route.Path) bool {
-	err := withDrawPrefixes(u.fsm.con, pfx)
+	err := withDrawPrefixes(u.fsm.con, u.fsm.options, pfx)
 	return err == nil
 }
 
diff --git a/protocols/bgp/server/update_sender_add_path.go b/protocols/bgp/server/update_sender_add_path.go
index 2414e14d8fe879c68d6b6e249f6d74b8f1e4e3aa..66d4a594cc6b59852bf206447c59cdb949d25abf 100644
--- a/protocols/bgp/server/update_sender_add_path.go
+++ b/protocols/bgp/server/update_sender_add_path.go
@@ -39,12 +39,12 @@ func (u *UpdateSenderAddPath) AddPath(pfx net.Prefix, p *route.Path) error {
 			Pfxlen:         pfx.Pfxlen(),
 		},
 	}
-	return serializeAndSendUpdate(u.fsm.con, update)
+	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, pfx, p)
+	err := withDrawPrefixesAddPath(u.fsm.con, u.fsm.options, pfx, p)
 	return err == nil
 }
 
diff --git a/protocols/bgp/server/withdraw.go b/protocols/bgp/server/withdraw.go
index 42e97a399cdcf8c208fc29063f7213e4d780ad96..089f9f2c0f7a0e25d1c5d1f2e9f292cdeec74a3c 100644
--- a/protocols/bgp/server/withdraw.go
+++ b/protocols/bgp/server/withdraw.go
@@ -11,7 +11,7 @@ import (
 
 // withDrawPrefixes generates a BGPUpdate message and write it to the given
 // io.Writer.
-func withDrawPrefixes(out io.Writer, prefixes ...net.Prefix) error {
+func withDrawPrefixes(out io.Writer, opt *packet.Options, prefixes ...net.Prefix) error {
 	if len(prefixes) < 1 {
 		return nil
 	}
@@ -35,13 +35,13 @@ func withDrawPrefixes(out io.Writer, prefixes ...net.Prefix) error {
 	update := &packet.BGPUpdate{
 		WithdrawnRoutes: rootNLRI,
 	}
-	return serializeAndSendUpdate(out, update)
+	return serializeAndSendUpdate(out, update, opt)
 
 }
 
 // withDrawPrefixesAddPath generates a BGPUpdateAddPath message and write it to the given
 // io.Writer.
-func withDrawPrefixesAddPath(out io.Writer, pfx net.Prefix, p *route.Path) error {
+func withDrawPrefixesAddPath(out io.Writer, opt *packet.Options, pfx net.Prefix, p *route.Path) error {
 	if p.Type != route.BGPPathType {
 		return errors.New("wrong path type, expected BGPPathType")
 	}
@@ -55,5 +55,5 @@ func withDrawPrefixesAddPath(out io.Writer, pfx net.Prefix, p *route.Path) error
 			Pfxlen:         pfx.Pfxlen(),
 		},
 	}
-	return serializeAndSendUpdate(out, update)
+	return serializeAndSendUpdate(out, update, opt)
 }
diff --git a/protocols/bgp/server/withdraw_test.go b/protocols/bgp/server/withdraw_test.go
index 0df94f75259aa446af0d78d1a65aa9ea7a4c67b2..d8831c3eb526d3afdaceb0a3506bf5dca39c2c41 100644
--- a/protocols/bgp/server/withdraw_test.go
+++ b/protocols/bgp/server/withdraw_test.go
@@ -3,6 +3,8 @@ package server
 import (
 	"testing"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+
 	"errors"
 
 	"bytes"
@@ -52,7 +54,8 @@ func TestWithDrawPrefixes(t *testing.T) {
 	}
 	for _, tc := range testcases {
 		buf := bytes.NewBuffer([]byte{})
-		err := withDrawPrefixes(buf, tc.Prefix...)
+		opt := &packet.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)
 	}
@@ -108,7 +111,8 @@ func TestWithDrawPrefixesAddPath(t *testing.T) {
 	}
 	for _, tc := range testcases {
 		buf := bytes.NewBuffer([]byte{})
-		err := withDrawPrefixesAddPath(buf, tc.Prefix, tc.Path)
+		opt := &packet.Options{}
+		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/route/BUILD.bazel b/route/BUILD.bazel
index 924cd80663f4a23fc0de1b3e5dd33b6f4500eceb..43ece0720b95d36285849f9f80d9d07d2d259b6d 100644
--- a/route/BUILD.bazel
+++ b/route/BUILD.bazel
@@ -3,7 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = [
-        "bgp.go",
+        "bgp_path.go",
         "bgp_path_manager.go",
         "path.go",
         "route.go",
@@ -13,6 +13,7 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//net:go_default_library",
+        "//protocols/bgp/packet:go_default_library",
         "//vendor/github.com/taktv6/tflow2/convert:go_default_library",
     ],
 )
@@ -20,12 +21,15 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "bgp_path_test.go",
+        "bgp_test.go",
         "path_test.go",
         "route_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
         "//net:go_default_library",
+        "//protocols/bgp/packet:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
     ],
 )
diff --git a/route/bgp.go b/route/bgp_path.go
similarity index 60%
rename from route/bgp.go
rename to route/bgp_path.go
index a63eb2fd396f16b2ae61e6f24d863ea5fbace185..0ab7dc4b2a3f6a23d77be1a0613b68515a5d1604 100644
--- a/route/bgp.go
+++ b/route/bgp_path.go
@@ -1,10 +1,11 @@
 package route
 
 import (
+	"crypto/sha256"
 	"fmt"
-	"strconv"
 	"strings"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/taktv6/tflow2/convert"
 )
 
@@ -13,15 +14,15 @@ type BGPPath struct {
 	PathIdentifier   uint32
 	NextHop          uint32
 	LocalPref        uint32
-	ASPath           string
+	ASPath           packet.ASPath
 	ASPathLen        uint16
 	Origin           uint8
 	MED              uint32
 	EBGP             bool
 	BGPIdentifier    uint32
 	Source           uint32
-	Communities      string
-	LargeCommunities string
+	Communities      []uint32
+	LargeCommunities []packet.LargeCommunity
 }
 
 // ECMP determines if routes b and c are euqal in terms of ECMP
@@ -154,14 +155,14 @@ func (b *BGPPath) Print() string {
 	}
 	ret := fmt.Sprintf("\t\tLocal Pref: %d\n", b.LocalPref)
 	ret += fmt.Sprintf("\t\tOrigin: %s\n", origin)
-	ret += fmt.Sprintf("\t\tAS Path: %s\n", b.ASPath)
+	ret += fmt.Sprintf("\t\tAS Path: %v\n", b.ASPath)
 	nh := uint32To4Byte(b.NextHop)
 	ret += fmt.Sprintf("\t\tNEXT HOP: %d.%d.%d.%d\n", nh[0], nh[1], nh[2], nh[3])
 	ret += fmt.Sprintf("\t\tMED: %d\n", b.MED)
 	ret += fmt.Sprintf("\t\tPath ID: %d\n", b.PathIdentifier)
 	ret += fmt.Sprintf("\t\tSource: %d\n", b.Source)
-	ret += fmt.Sprintf("\t\tCommunities: %s\n", b.Communities)
-	ret += fmt.Sprintf("\t\tLargeCommunities: %s\n", b.LargeCommunities)
+	ret += fmt.Sprintf("\t\tCommunities: %v\n", b.Communities)
+	ret += fmt.Sprintf("\t\tLargeCommunities: %v\n", b.LargeCommunities)
 
 	return ret
 }
@@ -171,16 +172,40 @@ func (b *BGPPath) Prepend(asn uint32, times uint16) {
 		return
 	}
 
-	asnStr := strconv.FormatUint(uint64(asn), 10)
+	if len(b.ASPath) == 0 {
+		b.insertNewASSequence()
+	}
 
-	path := make([]string, times+1)
-	for i := 0; uint16(i) < times; i++ {
-		path[i] = asnStr
+	first := b.ASPath[0]
+	if first.Type == packet.ASSet {
+		b.insertNewASSequence()
 	}
-	path[times] = b.ASPath
 
-	b.ASPath = strings.TrimSuffix(strings.Join(path, " "), " ")
-	b.ASPathLen = b.ASPathLen + times
+	for i := 0; i < int(times); i++ {
+		if len(b.ASPath) == packet.MaxASNsSegment {
+			b.insertNewASSequence()
+		}
+
+		old := b.ASPath[0].ASNs
+		asns := make([]uint32, len(old)+1)
+		copy(asns[1:], old)
+		asns[0] = asn
+		b.ASPath[0].ASNs = asns
+	}
+
+	b.ASPathLen = b.ASPath.Length()
+}
+
+func (b *BGPPath) insertNewASSequence() packet.ASPath {
+	pa := make(packet.ASPath, len(b.ASPath)+1)
+	copy(pa[1:], b.ASPath)
+	pa[0] = packet.ASPathSegment{
+		ASNs:  make([]uint32, 0),
+		Count: 0,
+		Type:  packet.ASSequence,
+	}
+
+	return pa
 }
 
 func (p *BGPPath) Copy() *BGPPath {
@@ -192,6 +217,44 @@ func (p *BGPPath) Copy() *BGPPath {
 	return &cp
 }
 
+// ComputeHash computes an hash over all attributes of the path
+func (b *BGPPath) ComputeHash() string {
+	s := fmt.Sprintf("%d\t%d\t%v\t%d\t%d\t%v\t%d\t%d\t%v\t%v\t%d",
+		b.NextHop,
+		b.LocalPref,
+		b.ASPath,
+		b.Origin,
+		b.MED,
+		b.EBGP,
+		b.BGPIdentifier,
+		b.Source,
+		b.Communities,
+		b.LargeCommunities,
+		b.PathIdentifier)
+
+	return fmt.Sprintf("%x", sha256.Sum256([]byte(s)))
+}
+
+// CommunitiesString returns the formated communities
+func (b *BGPPath) CommunitiesString() string {
+	str := ""
+	for _, com := range b.Communities {
+		str += packet.CommunityStringForUint32(com) + " "
+	}
+
+	return strings.TrimRight(str, " ")
+}
+
+// LargeCommunitiesString returns the formated communities
+func (b *BGPPath) LargeCommunitiesString() string {
+	str := ""
+	for _, com := range b.LargeCommunities {
+		str += com.String() + " "
+	}
+
+	return strings.TrimRight(str, " ")
+}
+
 func uint32To4Byte(addr uint32) [4]byte {
 	slice := convert.Uint32Byte(addr)
 	ret := [4]byte{
diff --git a/route/bgp_path_manager.go b/route/bgp_path_manager.go
index f846d3b60e108fc4be97597fabefe4ab46cb9d13..0eec9427911eced59371ea3ebf44a91bd2649342 100644
--- a/route/bgp_path_manager.go
+++ b/route/bgp_path_manager.go
@@ -7,7 +7,7 @@ import (
 
 // BGPPathManager is a component used to deduplicate BGP Path objects
 type BGPPathManager struct {
-	paths map[BGPPath]*BGPPathCounter
+	paths map[string]*BGPPathCounter
 	mu    sync.Mutex
 }
 
@@ -24,7 +24,7 @@ func NewBGPPathManager() *BGPPathManager {
 }
 
 func (m *BGPPathManager) lookup(p BGPPath) *BGPPath {
-	pathCounter, ok := m.paths[p]
+	pathCounter, ok := m.paths[p.ComputeHash()]
 	if !ok {
 		return nil
 	}
@@ -37,15 +37,17 @@ func (m *BGPPathManager) AddPath(p BGPPath) *BGPPath {
 	m.mu.Lock()
 	defer m.mu.Unlock()
 
+	hash := p.ComputeHash()
+
 	q := m.lookup(p)
 	if q == nil {
-		m.paths[p] = &BGPPathCounter{
+		m.paths[hash] = &BGPPathCounter{
 			path: &p,
 		}
 	}
 
-	m.paths[p].usageCount++
-	return m.paths[p].path
+	m.paths[hash].usageCount++
+	return m.paths[hash].path
 }
 
 // RemovePath notifies us that there is one user less for path p
@@ -58,8 +60,10 @@ func (m *BGPPathManager) RemovePath(p BGPPath) {
 		return
 	}
 
-	m.paths[p].usageCount--
-	if m.paths[p].usageCount == 0 {
-		delete(m.paths, p)
+	hash := p.ComputeHash()
+
+	m.paths[hash].usageCount--
+	if m.paths[hash].usageCount == 0 {
+		delete(m.paths, hash)
 	}
 }
diff --git a/route/bgp_path_test.go b/route/bgp_path_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0f0a9937a7ded1309664ffd31321d5edf844344
--- /dev/null
+++ b/route/bgp_path_test.go
@@ -0,0 +1,66 @@
+package route
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommunitiesString(t *testing.T) {
+	tests := []struct {
+		name     string
+		comms    []uint32
+		expected string
+	}{
+		{
+			name:     "two attributes",
+			comms:    []uint32{131080, 16778241},
+			expected: "(2,8) (256,1025)",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			p := &BGPPath{
+				Communities: test.comms,
+			}
+
+			assert.Equal(te, test.expected, p.CommunitiesString())
+		})
+	}
+}
+
+func TestLargeCommunitiesString(t *testing.T) {
+	tests := []struct {
+		name     string
+		comms    []packet.LargeCommunity
+		expected string
+	}{
+		{
+			name: "two attributes",
+			comms: []packet.LargeCommunity{
+				{
+					GlobalAdministrator: 1,
+					DataPart1:           2,
+					DataPart2:           3,
+				},
+				{
+					GlobalAdministrator: 4,
+					DataPart1:           5,
+					DataPart2:           6,
+				},
+			},
+			expected: "(1,2,3) (4,5,6)",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			p := &BGPPath{
+				LargeCommunities: test.comms,
+			}
+			assert.Equal(te, test.expected, p.LargeCommunitiesString())
+		})
+	}
+}
diff --git a/route/bgp_test.go b/route/bgp_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2eb017c2a6b33e5174b94cb9d43e4325750e968d
--- /dev/null
+++ b/route/bgp_test.go
@@ -0,0 +1,44 @@
+package route
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+
+	"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,
+			},
+		},
+		BGPIdentifier: 1,
+		Communities: []uint32{
+			123, 456,
+		},
+		EBGP: false,
+		LargeCommunities: []packet.LargeCommunity{
+			packet.LargeCommunity{
+				DataPart1:           1,
+				DataPart2:           2,
+				GlobalAdministrator: 3,
+			},
+		},
+		LocalPref:      100,
+		MED:            1,
+		NextHop:        100,
+		PathIdentifier: 5,
+		Source:         4,
+	}
+
+	assert.Equal(t, "45e238420552b88043edb8cb402034466b08d53b49f8e0fedc680747014ddeff", p.ComputeHash())
+
+	p.LocalPref = 150
+
+	assert.NotEqual(t, "45e238420552b88043edb8cb402034466b08d53b49f8e0fedc680747014ddeff", p.ComputeHash())
+}
diff --git a/routingtable/BUILD.bazel b/routingtable/BUILD.bazel
index 1c166185a9a502a88d8442ba33f90aac43a22ee1..1344741ba00193ae2cf24e4e93065d83c62985b0 100644
--- a/routingtable/BUILD.bazel
+++ b/routingtable/BUILD.bazel
@@ -5,6 +5,8 @@ go_library(
     srcs = [
         "client_interface.go",
         "client_manager.go",
+        "contributing_asn_list.go",
+        "mock_client.go",
         "neighbor.go",
         "rib_interface.go",
         "table.go",
@@ -17,7 +19,6 @@ go_library(
         "//net:go_default_library",
         "//protocols/bgp/packet:go_default_library",
         "//route:go_default_library",
-        "//vendor/github.com/sirupsen/logrus:go_default_library",
     ],
 )
 
@@ -25,6 +26,7 @@ go_test(
     name = "go_default_test",
     srcs = [
         "client_manager_test.go",
+        "contributing_asn_list_test.go",
         "table_test.go",
         "trie_test.go",
         "update_helper_test.go",
@@ -32,6 +34,7 @@ go_test(
     embed = [":go_default_library"],
     deps = [
         "//net:go_default_library",
+        "//protocols/bgp/packet:go_default_library",
         "//route:go_default_library",
         "//vendor/github.com/stretchr/testify/assert:go_default_library",
     ],
diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go
index ac58e1399b3d5321f151153f5d839ce4e1667174..bf3bab1c8a4aa14d4f969429e4c10eae0aa74731 100644
--- a/routingtable/adjRIBIn/adj_rib_in.go
+++ b/routingtable/adjRIBIn/adj_rib_in.go
@@ -14,16 +14,18 @@ import (
 // AdjRIBIn represents an Adjacency RIB In as described in RFC4271
 type AdjRIBIn struct {
 	routingtable.ClientManager
-	rt           *routingtable.RoutingTable
-	mu           sync.RWMutex
-	exportFilter *filter.Filter
+	rt               *routingtable.RoutingTable
+	mu               sync.RWMutex
+	exportFilter     *filter.Filter
+	contributingASNs *routingtable.ContributingASNs
 }
 
 // New creates a new Adjacency RIB In
-func New(exportFilter *filter.Filter) *AdjRIBIn {
+func New(exportFilter *filter.Filter, contributingASNs *routingtable.ContributingASNs) *AdjRIBIn {
 	a := &AdjRIBIn{
-		rt:           routingtable.NewRoutingTable(),
-		exportFilter: exportFilter,
+		rt:               routingtable.NewRoutingTable(),
+		exportFilter:     exportFilter,
+		contributingASNs: contributingASNs,
 	}
 	a.ClientManager = routingtable.NewClientManager(a)
 	return a
@@ -70,12 +72,29 @@ func (a *AdjRIBIn) AddPath(pfx net.Prefix, p *route.Path) error {
 		return nil
 	}
 
+	// Bail out - for all clients for now - if any of our ASNs is within the path
+	if a.ourASNsInPath(p) {
+		return nil
+	}
+
 	for _, client := range a.ClientManager.Clients() {
 		client.AddPath(pfx, p)
 	}
 	return nil
 }
 
+func (a *AdjRIBIn) ourASNsInPath(p *route.Path) bool {
+	for _, pathSegment := range p.BGPPath.ASPath {
+		for _, asn := range pathSegment.ASNs {
+			if a.contributingASNs.IsContributingASN(asn) {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
 // RemovePath removes the path for prefix `pfx`
 func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) bool {
 	a.mu.Lock()
diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go
index 48ac1fdb3efbe6c4aac8c0cbbef8371f1dc691af..9939e805d0dc73bd9e9f6555463c343f6ca16bcc 100644
--- a/routingtable/adjRIBIn/adj_rib_in_test.go
+++ b/routingtable/adjRIBIn/adj_rib_in_test.go
@@ -1,56 +1,16 @@
 package adjRIBIn
 
 import (
-	"fmt"
 	"testing"
 
-	"github.com/bio-routing/bio-rd/routingtable/filter"
-
 	"github.com/stretchr/testify/assert"
 
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/filter"
 )
 
-type RTMockClient struct {
-	removePathParams struct {
-		pfx  net.Prefix
-		path *route.Path
-	}
-}
-
-func NewRTMockClient() *RTMockClient {
-	return &RTMockClient{}
-}
-
-func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error {
-	return nil
-}
-
-func (m *RTMockClient) UpdateNewClient(client routingtable.RouteTableClient) error {
-	return fmt.Errorf("Not implemented")
-}
-
-func (m *RTMockClient) Register(routingtable.RouteTableClient) {
-	return
-}
-
-func (m *RTMockClient) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
-	return
-}
-
-func (m *RTMockClient) Unregister(routingtable.RouteTableClient) {
-	return
-}
-
-// RemovePath removes the path for prefix `pfx`
-func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool {
-	m.removePathParams.pfx = pfx
-	m.removePathParams.path = p
-	return true
-}
-
 func TestAddPath(t *testing.T) {
 	tests := []struct {
 		name       string
@@ -115,19 +75,20 @@ func TestAddPath(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		adjRIBIn := New(filter.NewAcceptAllFilter())
-		mc := NewRTMockClient()
+		adjRIBIn := New(filter.NewAcceptAllFilter(), routingtable.NewContributingASNs())
+		mc := routingtable.NewRTMockClient()
 		adjRIBIn.ClientManager.Register(mc)
 
 		for _, route := range test.routes {
 			adjRIBIn.AddPath(route.Prefix(), route.Paths()[0])
 		}
 
-		if mc.removePathParams.pfx != test.removePfx {
-			t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String())
+		removePathParams := mc.GetRemovePathParams()
+		if removePathParams.Pfx != test.removePfx {
+			t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, removePathParams.Pfx.String(), test.removePfx.String())
 		}
 
-		assert.Equal(t, test.removePath, mc.removePathParams.path)
+		assert.Equal(t, test.removePath, removePathParams.Path)
 		assert.Equal(t, test.expected, adjRIBIn.rt.Dump())
 	}
 }
@@ -206,22 +167,24 @@ func TestRemovePath(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		adjRIBIn := New(filter.NewAcceptAllFilter())
+		adjRIBIn := New(filter.NewAcceptAllFilter(), routingtable.NewContributingASNs())
 		for _, route := range test.routes {
 			adjRIBIn.AddPath(route.Prefix(), route.Paths()[0])
 		}
 
-		mc := NewRTMockClient()
+		mc := routingtable.NewRTMockClient()
 		adjRIBIn.ClientManager.Register(mc)
 		adjRIBIn.RemovePath(test.removePfx, test.removePath)
 
 		if test.wantPropagation {
-			if mc.removePathParams.pfx != test.removePfx {
-				t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String())
+			removePathParams := mc.GetRemovePathParams()
+			if removePathParams.Pfx != test.removePfx {
+				t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, removePathParams.Pfx.String(), test.removePfx.String())
 			}
-			assert.Equal(t, test.removePath, mc.removePathParams.path)
+			assert.Equal(t, test.removePath, removePathParams.Path)
 		} else {
-			if mc.removePathParams.pfx != net.NewPfx(0, 0) {
+			removePathParams := mc.GetRemovePathParams()
+			if removePathParams.Pfx != net.NewPfx(0, 0) {
 				t.Errorf("Test %q failed: Call to RemovePath propagated unexpectedly", test.name)
 			}
 		}
diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go
index 1c1e8cef6d3def34e53b8fd2a5cc86c08c8f72b9..180e2aeca7d94d92e078528279ca89ff5a572936 100644
--- a/routingtable/adjRIBOut/adj_rib_out.go
+++ b/routingtable/adjRIBOut/adj_rib_out.go
@@ -57,7 +57,7 @@ func (a *AdjRIBOut) AddPath(pfx bnet.Prefix, p *route.Path) error {
 
 	p = p.Copy()
 	if !a.neighbor.IBGP && !a.neighbor.RouteServerClient {
-		p.BGPPath.ASPath = fmt.Sprintf("%d %s", a.neighbor.LocalASN, p.BGPPath.ASPath)
+		p.BGPPath.Prepend(a.neighbor.LocalASN, 1)
 	}
 
 	if !a.neighbor.IBGP && !a.neighbor.RouteServerClient {
diff --git a/routingtable/adjRIBOut/path_id_manager.go b/routingtable/adjRIBOut/path_id_manager.go
index 377ce9b3057101b6c1561d0124d9802f83c74faf..78054e3ec0294a7ba472e6de3928e2705c5ed536 100644
--- a/routingtable/adjRIBOut/path_id_manager.go
+++ b/routingtable/adjRIBOut/path_id_manager.go
@@ -11,7 +11,7 @@ var maxUint32 = ^uint32(0)
 // pathIDManager manages BGP path identifiers for add-path. This is no thread safe (and doesn't need to be).
 type pathIDManager struct {
 	ids      map[uint32]uint64
-	idByPath map[route.BGPPath]uint32
+	idByPath map[string]uint32
 	last     uint32
 	used     uint32
 }
@@ -19,13 +19,15 @@ type pathIDManager struct {
 func newPathIDManager() *pathIDManager {
 	return &pathIDManager{
 		ids:      make(map[uint32]uint64),
-		idByPath: make(map[route.BGPPath]uint32),
+		idByPath: make(map[string]uint32),
 	}
 }
 
 func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) {
-	if _, exists := fm.idByPath[*p.BGPPath]; exists {
-		id := fm.idByPath[*p.BGPPath]
+	hash := p.BGPPath.ComputeHash()
+
+	if _, exists := fm.idByPath[hash]; exists {
+		id := fm.idByPath[hash]
 		fm.ids[id]++
 		return id, nil
 	}
@@ -43,7 +45,7 @@ func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) {
 		break
 	}
 
-	fm.idByPath[*p.BGPPath] = fm.last
+	fm.idByPath[hash] = fm.last
 	fm.ids[fm.last] = 1
 	fm.used++
 
@@ -51,16 +53,19 @@ func (fm *pathIDManager) addPath(p *route.Path) (uint32, error) {
 }
 
 func (fm *pathIDManager) releasePath(p *route.Path) (uint32, error) {
-	if _, exists := fm.idByPath[*p.BGPPath]; !exists {
+	hash := p.BGPPath.ComputeHash()
+
+	if _, exists := fm.idByPath[hash]; !exists {
 		return 0, fmt.Errorf("ID not found for path: %s", p.Print())
 	}
 
-	id := fm.idByPath[*p.BGPPath]
+	id := fm.idByPath[hash]
 	fm.ids[id]--
 	if fm.ids[id] == 0 {
-		delete(fm.ids, fm.idByPath[*p.BGPPath])
-		delete(fm.idByPath, *p.BGPPath)
+		delete(fm.ids, fm.idByPath[hash])
+		delete(fm.idByPath, hash)
 	}
+	fm.used--
 
 	return id, nil
 }
diff --git a/routingtable/adjRIBOut/path_id_manager_test.go b/routingtable/adjRIBOut/path_id_manager_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e4ac49466587f0f11af3910d5fd2785e030e7cca
--- /dev/null
+++ b/routingtable/adjRIBOut/path_id_manager_test.go
@@ -0,0 +1,173 @@
+package adjRIBOut
+
+import (
+	"testing"
+
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAddPath(t *testing.T) {
+	tests := []struct {
+		name     string
+		maxIDs   uint32
+		count    int
+		wantFail bool
+	}{
+		{
+			name:     "Out of path IDs",
+			maxIDs:   10,
+			count:    11,
+			wantFail: true,
+		},
+		{
+			name:     "Success",
+			maxIDs:   10,
+			count:    10,
+			wantFail: false,
+		},
+	}
+
+X:
+	for _, test := range tests {
+		maxUint32 = test.maxIDs
+		m := newPathIDManager()
+		for i := 0; i < test.count; i++ {
+			_, err := m.addPath(&route.Path{BGPPath: &route.BGPPath{LocalPref: uint32(i)}})
+			if err != nil {
+				if test.wantFail {
+					continue X
+				}
+
+				t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+				continue X
+			}
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+			continue
+		}
+	}
+
+}
+
+func TestReleasePath(t *testing.T) {
+	tests := []struct {
+		name     string
+		pm       *pathIDManager
+		release  *route.Path
+		expected *pathIDManager
+		wantFail bool
+	}{
+		{
+			name: "Release existent",
+			pm: &pathIDManager{
+				ids: map[uint32]uint64{
+					0: 1,
+					1: 1,
+					2: 1,
+				},
+				idByPath: map[route.BGPPath]uint32{
+					route.BGPPath{
+						LocalPref: 0,
+					}: 0,
+					route.BGPPath{
+						LocalPref: 1,
+					}: 1,
+					route.BGPPath{
+						LocalPref: 2,
+					}: 2,
+				},
+				last: 2,
+				used: 3,
+			},
+			release: &route.Path{BGPPath: &route.BGPPath{
+				LocalPref: 2,
+			}},
+			expected: &pathIDManager{
+				ids: map[uint32]uint64{
+					0: 1,
+					1: 1,
+				},
+				idByPath: map[route.BGPPath]uint32{
+					route.BGPPath{
+						LocalPref: 0,
+					}: 0,
+					route.BGPPath{
+						LocalPref: 1,
+					}: 1,
+				},
+				last: 2,
+				used: 2,
+			},
+		},
+		{
+			name: "Release non-existent",
+			pm: &pathIDManager{
+				ids: map[uint32]uint64{
+					0: 1,
+					1: 1,
+					2: 1,
+				},
+				idByPath: map[route.BGPPath]uint32{
+					route.BGPPath{
+						LocalPref: 0,
+					}: 0,
+					route.BGPPath{
+						LocalPref: 1,
+					}: 1,
+					route.BGPPath{
+						LocalPref: 2,
+					}: 2,
+				},
+				last: 2,
+				used: 3,
+			},
+			release: &route.Path{BGPPath: &route.BGPPath{
+				LocalPref: 4,
+			}},
+			expected: &pathIDManager{
+				ids: map[uint32]uint64{
+					0: 1,
+					1: 1,
+					2: 1,
+				},
+				idByPath: map[route.BGPPath]uint32{
+					route.BGPPath{
+						LocalPref: 0,
+					}: 0,
+					route.BGPPath{
+						LocalPref: 1,
+					}: 1,
+					route.BGPPath{
+						LocalPref: 2,
+					}: 2,
+				},
+				last: 2,
+				used: 3,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		_, err := test.pm.releasePath(test.release)
+		if err != nil {
+			if test.wantFail {
+				continue
+			}
+
+			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
+			continue
+		}
+
+		if test.wantFail {
+			t.Errorf("Unexpected success for test %q", test.name)
+			continue
+		}
+
+		assert.Equalf(t, test.expected, test.pm, "%s", test.name)
+	}
+}
+
diff --git a/routingtable/contributing_asn_list.go b/routingtable/contributing_asn_list.go
new file mode 100644
index 0000000000000000000000000000000000000000..500b79b61d66825b6f0e9c21d3508adc970c36e3
--- /dev/null
+++ b/routingtable/contributing_asn_list.go
@@ -0,0 +1,88 @@
+package routingtable
+
+import (
+	"fmt"
+	"math"
+	"sync"
+)
+
+type contributingASN struct {
+	asn   uint32
+	count uint32
+}
+
+// ContributingASNs contains a list of contributing ASN to a LocRIB to check ASPaths for possible routing loops.
+type ContributingASNs struct {
+	contributingASNs     []*contributingASN
+	contributingASNsLock sync.RWMutex
+}
+
+// NewContributingASNs creates a list of contributing ASNs to a LocRIB for routing loop prevention.
+func NewContributingASNs() *ContributingASNs {
+	c := &ContributingASNs{
+		contributingASNs: []*contributingASN{},
+	}
+
+	return c
+}
+
+// Add a new ASN to the list of contributing ASNs or add the ref count of an existing one.
+func (c *ContributingASNs) Add(asn uint32) {
+	c.contributingASNsLock.Lock()
+	defer c.contributingASNsLock.Unlock()
+
+	for _, cASN := range c.contributingASNs {
+		if cASN.asn == asn {
+			cASN.count++
+
+			if cASN.count == math.MaxUint32 {
+				panic(fmt.Sprintf("Contributing ASNs counter overflow triggered for AS %d. Dyning of shame.", asn))
+			}
+
+			return
+		}
+	}
+
+	c.contributingASNs = append(c.contributingASNs, &contributingASN{
+		asn:   asn,
+		count: 1,
+	})
+}
+
+// Remove a ASN to the list of contributing ASNs or decrement the ref count of an existing one.
+func (c *ContributingASNs) Remove(asn uint32) {
+	c.contributingASNsLock.Lock()
+	defer c.contributingASNsLock.Unlock()
+
+	asnList := c.contributingASNs
+
+	for i, cASN := range asnList {
+		if cASN.asn != asn {
+			continue
+		}
+
+		cASN.count--
+
+		if cASN.count == 0 {
+			copy(asnList[i:], asnList[i+1:])
+			asnList = asnList[:len(asnList)]
+			c.contributingASNs = asnList[:len(asnList)-1]
+		}
+
+		return
+	}
+}
+
+// IsContributingASN checks if  a given ASN is part of the contributing ASNs
+func (c *ContributingASNs) IsContributingASN(asn uint32) bool {
+	c.contributingASNsLock.RLock()
+	defer c.contributingASNsLock.RUnlock()
+
+	for _, cASN := range c.contributingASNs {
+		if asn == cASN.asn {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/routingtable/contributing_asn_list_test.go b/routingtable/contributing_asn_list_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6387b18bcca92c290bc724ed8d13032104d247fa
--- /dev/null
+++ b/routingtable/contributing_asn_list_test.go
@@ -0,0 +1,90 @@
+package routingtable
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestFancy(t *testing.T) {
+	c := NewContributingASNs()
+
+	tests := []struct {
+		runCmd func()
+		expect func() bool
+		msg    string
+	}{
+		// Empty list
+		{
+			runCmd: func() {},
+			expect: func() bool { return !c.IsContributingASN(41981) },
+			msg:    "AS41981 shouldn't be contributing yet.",
+		},
+
+		// Add and remove one ASN
+		{
+			runCmd: func() { c.Add(41981) },
+			expect: func() bool { return c.IsContributingASN(41981) },
+			msg:    "AS41981 should be contributing.",
+		},
+		{
+			runCmd: func() { c.Remove(41981) },
+			expect: func() bool { return !c.IsContributingASN(41981) },
+			msg:    "AS41981 shouldn't be contributing no more.",
+		},
+
+		// Two ASNs present
+		{
+			runCmd: func() { c.Add(41981) },
+			expect: func() bool { return c.IsContributingASN(41981) },
+			msg:    "AS41981 should be contributing.",
+		},
+		{
+			runCmd: func() { c.Add(201701) },
+			expect: func() bool { return c.IsContributingASN(41981) },
+			msg:    "AS201701 should be contributing.",
+		},
+
+		// Add AS41981 2nd time
+		{
+			runCmd: func() { c.Add(41981) },
+			expect: func() bool { return c.IsContributingASN(41981) },
+			msg:    "AS41981 should be still contributing.",
+		},
+		{
+			runCmd: func() {},
+			expect: func() bool { return c.contributingASNs[0].asn == 41981 },
+			msg:    "AS41981 is first ASN in list.",
+		},
+		{
+			runCmd: func() { fmt.Printf("%+v", c.contributingASNs) },
+			expect: func() bool { return c.contributingASNs[0].count == 2 },
+			msg:    "AS41981 should be present twice.",
+		},
+
+		// Remove 2nd AS41981
+		{
+			runCmd: func() { c.Remove(41981) },
+			expect: func() bool { return c.IsContributingASN(41981) },
+			msg:    "AS41981 should still be contributing.",
+		},
+		{
+			runCmd: func() {},
+			expect: func() bool { return c.contributingASNs[0].count == 1 },
+			msg:    "S41981 should be present once.",
+		},
+
+		// Remove AS201701
+		{
+			runCmd: func() { c.Remove(201701) },
+			expect: func() bool { return !c.IsContributingASN(201701) },
+			msg:    "AS201701 shouldn't be contributing no more.",
+		},
+	}
+
+	for i, test := range tests {
+		test.runCmd()
+		if !test.expect() {
+			t.Errorf("Test %d failed: %v", i, test.msg)
+		}
+	}
+}
diff --git a/routingtable/filter/actions/add_community_action.go b/routingtable/filter/actions/add_community_action.go
index 01ad05f62936574bb576d55fe1c7bcf23697a38f..c24107e8b94be6657174fbd3dffb06ae978ff767 100644
--- a/routingtable/filter/actions/add_community_action.go
+++ b/routingtable/filter/actions/add_community_action.go
@@ -1,10 +1,7 @@
 package actions
 
 import (
-	"strings"
-
 	"github.com/bio-routing/bio-rd/net"
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/route"
 )
 
@@ -26,9 +23,8 @@ func (a *AddCommunityAction) Do(p net.Prefix, pa *route.Path) (modPath *route.Pa
 	modified := pa.Copy()
 
 	for _, com := range a.communities {
-		modified.BGPPath.Communities = modified.BGPPath.Communities + " " + packet.CommunityStringForUint32(com)
+		modified.BGPPath.Communities = append(modified.BGPPath.Communities, com)
 	}
-	modified.BGPPath.Communities = strings.TrimLeft(modified.BGPPath.Communities, " ")
 
 	return modified, false
 }
diff --git a/routingtable/filter/actions/add_community_action_test.go b/routingtable/filter/actions/add_community_action_test.go
index 0687e86c4bc80b8648d172cb125449962dea9cf6..9ec3d1401b87da808c324a984e1d5ef7620658cd 100644
--- a/routingtable/filter/actions/add_community_action_test.go
+++ b/routingtable/filter/actions/add_community_action_test.go
@@ -11,7 +11,7 @@ import (
 func TestAddingCommunities(t *testing.T) {
 	tests := []struct {
 		name        string
-		current     string
+		current     []uint32
 		communities []uint32
 		expected    string
 	}{
@@ -23,16 +23,20 @@ func TestAddingCommunities(t *testing.T) {
 			expected: "(1,2)",
 		},
 		{
-			name:    "add one to existing",
-			current: "(1,2)",
+			name: "add one to existing",
+			current: []uint32{
+				65538,
+			},
 			communities: []uint32{
 				196612,
 			},
 			expected: "(1,2) (3,4)",
 		},
 		{
-			name:    "add two to existing",
-			current: "(1,2)",
+			name: "add two to existing",
+			current: []uint32{
+				65538,
+			},
 			communities: []uint32{
 				196612, 327686,
 			},
@@ -41,7 +45,7 @@ func TestAddingCommunities(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		t.Run(test.name, func(te *testing.T) {
+		t.Run(test.name, func(t *testing.T) {
 			p := &route.Path{
 				BGPPath: &route.BGPPath{
 					Communities: test.current,
@@ -51,7 +55,7 @@ func TestAddingCommunities(t *testing.T) {
 			a := NewAddCommunityAction(test.communities)
 			modPath, _ := a.Do(net.Prefix{}, p)
 
-			assert.Equal(te, test.expected, modPath.BGPPath.Communities)
+			assert.Equal(t, test.expected, modPath.BGPPath.CommunitiesString())
 		})
 	}
 }
diff --git a/routingtable/filter/actions/add_large_community_action.go b/routingtable/filter/actions/add_large_community_action.go
index 17169c315817509c8c6c1a2b382ab39c3b5f0428..534ad30b66e4580ca6b8078bf7a077f87b6e758f 100644
--- a/routingtable/filter/actions/add_large_community_action.go
+++ b/routingtable/filter/actions/add_large_community_action.go
@@ -1,18 +1,16 @@
 package actions
 
 import (
-	"strings"
-
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/route"
 )
 
 type AddLargeCommunityAction struct {
-	communities []*packet.LargeCommunity
+	communities []packet.LargeCommunity
 }
 
-func NewAddLargeCommunityAction(coms []*packet.LargeCommunity) *AddLargeCommunityAction {
+func NewAddLargeCommunityAction(coms []packet.LargeCommunity) *AddLargeCommunityAction {
 	return &AddLargeCommunityAction{
 		communities: coms,
 	}
@@ -24,11 +22,7 @@ func (a *AddLargeCommunityAction) Do(p net.Prefix, pa *route.Path) (modPath *rou
 	}
 
 	modified := pa.Copy()
-
-	for _, com := range a.communities {
-		modified.BGPPath.LargeCommunities = modified.BGPPath.LargeCommunities + " " + com.String()
-	}
-	modified.BGPPath.LargeCommunities = strings.TrimLeft(modified.BGPPath.LargeCommunities, " ")
+	modified.BGPPath.LargeCommunities = append(modified.BGPPath.LargeCommunities, a.communities...)
 
 	return modified, false
 }
diff --git a/routingtable/filter/actions/add_large_community_action_test.go b/routingtable/filter/actions/add_large_community_action_test.go
index d2c597fd5cbe8011b3eeb7b74443a09c726d6eb7..f7af91396fdd7d697cf667d7712e18c9784d68e3 100644
--- a/routingtable/filter/actions/add_large_community_action_test.go
+++ b/routingtable/filter/actions/add_large_community_action_test.go
@@ -12,14 +12,14 @@ import (
 func TestAddingLargeCommunities(t *testing.T) {
 	tests := []struct {
 		name        string
-		current     string
-		communities []*packet.LargeCommunity
+		current     []packet.LargeCommunity
+		communities []packet.LargeCommunity
 		expected    string
 	}{
 		{
 			name: "add one to empty",
-			communities: []*packet.LargeCommunity{
-				&packet.LargeCommunity{
+			communities: []packet.LargeCommunity{
+				packet.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
@@ -28,10 +28,16 @@ func TestAddingLargeCommunities(t *testing.T) {
 			expected: "(1,2,3)",
 		},
 		{
-			name:    "add one to existing",
-			current: "(5,6,7)",
-			communities: []*packet.LargeCommunity{
-				&packet.LargeCommunity{
+			name: "add one to existing",
+			current: []packet.LargeCommunity{
+				packet.LargeCommunity{
+					GlobalAdministrator: 5,
+					DataPart1:           6,
+					DataPart2:           7,
+				},
+			},
+			communities: []packet.LargeCommunity{
+				packet.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
@@ -40,15 +46,21 @@ func TestAddingLargeCommunities(t *testing.T) {
 			expected: "(5,6,7) (1,2,3)",
 		},
 		{
-			name:    "add two to existing",
-			current: "(5,6,7)",
-			communities: []*packet.LargeCommunity{
-				&packet.LargeCommunity{
+			name: "add two to existing",
+			current: []packet.LargeCommunity{
+				packet.LargeCommunity{
+					GlobalAdministrator: 5,
+					DataPart1:           6,
+					DataPart2:           7,
+				},
+			},
+			communities: []packet.LargeCommunity{
+				packet.LargeCommunity{
 					GlobalAdministrator: 1,
 					DataPart1:           2,
 					DataPart2:           3,
 				},
-				&packet.LargeCommunity{
+				packet.LargeCommunity{
 					GlobalAdministrator: 7,
 					DataPart1:           8,
 					DataPart2:           9,
@@ -69,7 +81,7 @@ func TestAddingLargeCommunities(t *testing.T) {
 			a := NewAddLargeCommunityAction(test.communities)
 			modPath, _ := a.Do(net.Prefix{}, p)
 
-			assert.Equal(te, test.expected, modPath.BGPPath.LargeCommunities)
+			assert.Equal(te, test.expected, modPath.BGPPath.LargeCommunitiesString())
 		})
 	}
 }
diff --git a/routingtable/filter/actions/as_path_prepend_action_test.go b/routingtable/filter/actions/as_path_prepend_action_test.go
index 348116ccb33b7e067e7e50effa438b9eb0d42c3a..253c119ffc91d702e0f4d4cf31e1f88c35d2008c 100644
--- a/routingtable/filter/actions/as_path_prepend_action_test.go
+++ b/routingtable/filter/actions/as_path_prepend_action_test.go
@@ -3,6 +3,8 @@ package actions
 import (
 	"testing"
 
+	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
+
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/route"
 	"github.com/stretchr/testify/assert"
@@ -23,7 +25,13 @@ func TestAppendPath(t *testing.T) {
 			name:  "append 0",
 			times: 0,
 			bgpPath: &route.BGPPath{
-				ASPath:    "12345 12345",
+				ASPath: packet.ASPath{
+					packet.ASPathSegment{
+						Count: 2,
+						Type:  packet.ASSequence,
+						ASNs:  []uint32{12345, 12345},
+					},
+				},
 				ASPathLen: 2,
 			},
 			expectedPath:   "12345 12345",
@@ -33,7 +41,13 @@ func TestAppendPath(t *testing.T) {
 			name:  "append 3",
 			times: 3,
 			bgpPath: &route.BGPPath{
-				ASPath:    "12345 15169",
+				ASPath: packet.ASPath{
+					packet.ASPathSegment{
+						Count: 2,
+						Type:  packet.ASSequence,
+						ASNs:  []uint32{12345, 15169},
+					},
+				},
 				ASPathLen: 2,
 			},
 			expectedPath:   "12345 12345 12345 12345 15169",
@@ -52,7 +66,7 @@ func TestAppendPath(t *testing.T) {
 				return
 			}
 
-			assert.Equal(te, test.expectedPath, p.BGPPath.ASPath, "ASPath")
+			assert.Equal(te, test.expectedPath, p.BGPPath.ASPath.String(), "ASPath")
 			assert.Equal(te, test.expectedLength, p.BGPPath.ASPathLen, "ASPathLen")
 		})
 	}
diff --git a/routingtable/filter/community_filter.go b/routingtable/filter/community_filter.go
index 9d384e54ad0a58ee622e3d2b0bb1b857c867b0c3..b07d4b332349de507ca5591010475ebd920fed94 100644
--- a/routingtable/filter/community_filter.go
+++ b/routingtable/filter/community_filter.go
@@ -1,15 +1,15 @@
 package filter
 
-import (
-	"strings"
-
-	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
-)
-
 type CommunityFilter struct {
 	community uint32
 }
 
-func (f *CommunityFilter) Matches(communityString string) bool {
-	return strings.Contains(communityString, packet.CommunityStringForUint32(f.community))
+func (f *CommunityFilter) Matches(coms []uint32) bool {
+	for _, com := range coms {
+		if com == f.community {
+			return true
+		}
+	}
+
+	return false
 }
diff --git a/routingtable/filter/filter_test.go b/routingtable/filter/filter_test.go
index 2f4aace7637f30095d106520495d12a2d3ad9626..273e986d29dc4e25a6788bfb8f750e54bff6beb4 100644
--- a/routingtable/filter/filter_test.go
+++ b/routingtable/filter/filter_test.go
@@ -1,85 +1,21 @@
 package filter
 
-/*func TestAddPath(t *testing.T) {
-	tests := []struct {
-		name           string
-		prefix         net.Prefix
-		path           *route.Path
-		term           *Term
-		exptectCalled  bool
-		expectModified bool
-	}{
-		{
-			name:   "accept",
-			prefix: net.NewPfx(0, 0),
-			path:   &route.Path{},
-			term: &Term{
-				then: []FilterAction{
-					&actions.AcceptAction{},
-				},
-			},
-			exptectCalled:  true,
-			expectModified: false,
-		},
-		{
-			name:   "reject",
-			prefix: net.NewPfx(0, 0),
-			path:   &route.Path{},
-			term: &Term{
-				then: []FilterAction{
-					&actions.RejectAction{},
-				},
-			},
-			exptectCalled:  false,
-			expectModified: false,
-		},
-		{
-			name:   "modified",
-			prefix: net.NewPfx(0, 0),
-			path:   &route.Path{},
-			term: &Term{
-				then: []FilterAction{
-					&mockAction{},
-					&actions.AcceptAction{},
-				},
-			},
-			exptectCalled:  true,
-			expectModified: true,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(te *testing.T) {
-			m := newClientMock()
-
-			f := NewFilter([]*Term{test.term})
-			f.Register(m)
+import (
+	"testing"
 
-			f.AddPath(test.prefix, test.path)
-			assert.Equal(te, test.exptectCalled, m.addPathCalled, "called")
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable/filter/actions"
+	"github.com/stretchr/testify/assert"
+)
 
-			if !test.exptectCalled {
-				return
-			}
-
-			if m.path != test.path && !test.expectModified {
-				te.Fatal("expected path to be not modified but was")
-			}
-
-			if m.path == test.path && test.expectModified {
-				te.Fatal("expected path to be modified but was same reference")
-			}
-		})
-	}
-}
-
-func TestRemovePath(t *testing.T) {
+func TestProcessTerms(t *testing.T) {
 	tests := []struct {
 		name           string
 		prefix         net.Prefix
 		path           *route.Path
 		term           *Term
-		exptectCalled  bool
+		exptectAccept  bool
 		expectModified bool
 	}{
 		{
@@ -91,7 +27,7 @@ func TestRemovePath(t *testing.T) {
 					&actions.AcceptAction{},
 				},
 			},
-			exptectCalled:  true,
+			exptectAccept:  true,
 			expectModified: false,
 		},
 		{
@@ -103,7 +39,7 @@ func TestRemovePath(t *testing.T) {
 					&actions.RejectAction{},
 				},
 			},
-			exptectCalled:  false,
+			exptectAccept:  false,
 			expectModified: false,
 		},
 		{
@@ -116,32 +52,21 @@ func TestRemovePath(t *testing.T) {
 					&actions.AcceptAction{},
 				},
 			},
-			exptectCalled:  true,
+			exptectAccept:  true,
 			expectModified: true,
 		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(te *testing.T) {
-			m := newClientMock()
-
 			f := NewFilter([]*Term{test.term})
-			f.Register(m)
+			p, reject := f.ProcessTerms(test.prefix, test.path)
 
-			f.RemovePath(test.prefix, test.path)
-			assert.Equal(te, test.exptectCalled, m.removePathCalled, "called")
-
-			if !test.exptectCalled {
-				return
-			}
+			assert.Equal(t, test.exptectAccept, !reject)
 
-			if m.path != test.path && !test.expectModified {
-				te.Fatal("expected path to be not modified but was")
-			}
-
-			if m.path == test.path && test.expectModified {
-				te.Fatal("expected path to be modified but was same reference")
+			if test.expectModified {
+				assert.NotEqual(t, test.path, p)
 			}
 		})
 	}
-}*/
+}
diff --git a/routingtable/filter/helper_test.go b/routingtable/filter/helper_test.go
index 5d82727a9f2b5b5e0f692a980d9d7515ccd4c71d..19a444d4a5c8d656534cfb415f2b401a11ae4c57 100644
--- a/routingtable/filter/helper_test.go
+++ b/routingtable/filter/helper_test.go
@@ -1,27 +1,23 @@
 package filter
 
-/*func TestNewAcceptAllFilter(t *testing.T) {
-	f := NewAcceptAllFilter()
+import (
+	"testing"
 
-	m := &clientMock{}
-	f.Register(m)
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/stretchr/testify/assert"
+)
 
-	f.AddPath(net.NewPfx(0, 0), &route.Path{})
+func TestNewAcceptAllFilter(t *testing.T) {
+	f := NewAcceptAllFilter()
 
-	if !m.addPathCalled {
-		t.Fatalf("expected accepted, but was filtered")
-	}
+	_, reject := f.ProcessTerms(net.NewPfx(0, 0), &route.Path{})
+	assert.Equal(t, false, reject)
 }
 
 func TestNewDrainFilter(t *testing.T) {
 	f := NewDrainFilter()
 
-	m := &clientMock{}
-	f.Register(m)
-
-	f.AddPath(net.NewPfx(0, 0), &route.Path{})
-
-	if m.addPathCalled {
-		t.Fatalf("expected filtered, but was accepted")
-	}
-}*/
+	_, reject := f.ProcessTerms(net.NewPfx(0, 0), &route.Path{})
+	assert.Equal(t, true, reject)
+}
diff --git a/routingtable/filter/large_community_filter.go b/routingtable/filter/large_community_filter.go
index 0f9d474e69728ba682cde441e819a0d08bd7cc77..cb4722bdfe98a68cd6c6e9938f188bbbf606c968 100644
--- a/routingtable/filter/large_community_filter.go
+++ b/routingtable/filter/large_community_filter.go
@@ -1,15 +1,19 @@
 package filter
 
 import (
-	"strings"
-
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 )
 
 type LargeCommunityFilter struct {
-	community *packet.LargeCommunity
+	community packet.LargeCommunity
 }
 
-func (f *LargeCommunityFilter) Matches(communityString string) bool {
-	return strings.Contains(communityString, f.community.String())
+func (f *LargeCommunityFilter) Matches(coms []packet.LargeCommunity) bool {
+	for _, com := range coms {
+		if com == f.community {
+			return true
+		}
+	}
+
+	return false
 }
diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go
index cca90950aa5a443c042c8061f77a08d3f4f7a0c4..e1b3d4bd6173b196ddaca029e768274a08364883 100644
--- a/routingtable/filter/term_condition_test.go
+++ b/routingtable/filter/term_condition_test.go
@@ -107,7 +107,7 @@ func TestMatches(t *testing.T) {
 			name:   "community matches",
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			bgpPath: &route.BGPPath{
-				Communities: "(1,2) (3,4) (5,6)",
+				Communities: []uint32{65538, 196612, 327686}, // (1,2) (3,4) (5,6)
 			},
 			communityFilters: []*CommunityFilter{
 				&CommunityFilter{196612}, // (3,4)
@@ -118,7 +118,7 @@ func TestMatches(t *testing.T) {
 			name:   "community does not match",
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			bgpPath: &route.BGPPath{
-				Communities: "(1,2) (3,4) (5,6)",
+				Communities: []uint32{65538, 196612, 327686}, // (1,2) (3,4) (5,6)
 			},
 			communityFilters: []*CommunityFilter{
 				&CommunityFilter{196608}, // (3,0)
@@ -137,11 +137,22 @@ func TestMatches(t *testing.T) {
 			name:   "large community matches",
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			bgpPath: &route.BGPPath{
-				LargeCommunities: "(1,2,0) (1,2,3)",
+				LargeCommunities: []packet.LargeCommunity{
+					packet.LargeCommunity{
+						GlobalAdministrator: 1,
+						DataPart1:           2,
+						DataPart2:           3,
+					},
+					packet.LargeCommunity{
+						GlobalAdministrator: 1,
+						DataPart1:           2,
+						DataPart2:           0,
+					},
+				},
 			},
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					&packet.LargeCommunity{
+					packet.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
@@ -156,7 +167,7 @@ func TestMatches(t *testing.T) {
 			bgpPath: &route.BGPPath{},
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					&packet.LargeCommunity{
+					packet.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
@@ -170,7 +181,7 @@ func TestMatches(t *testing.T) {
 			prefix: net.NewPfx(strAddr("10.0.0.0"), 24),
 			largeCommunityFilters: []*LargeCommunityFilter{
 				{
-					&packet.LargeCommunity{
+					packet.LargeCommunity{
 						GlobalAdministrator: 1,
 						DataPart1:           2,
 						DataPart2:           3,
diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go
index e8788c5eb3b3c2b1e3e7397d52a4ed6f04feb21b..037565a86e32b77f4344d40724b0781094234f22 100644
--- a/routingtable/locRIB/loc_rib.go
+++ b/routingtable/locRIB/loc_rib.go
@@ -14,19 +14,26 @@ import (
 // LocRIB represents a routing information base
 type LocRIB struct {
 	routingtable.ClientManager
-	rt *routingtable.RoutingTable
-	mu sync.RWMutex
+	rt               *routingtable.RoutingTable
+	mu               sync.RWMutex
+	contributingASNs *routingtable.ContributingASNs
 }
 
 // New creates a new routing information base
 func New() *LocRIB {
 	a := &LocRIB{
-		rt: routingtable.NewRoutingTable(),
+		rt:               routingtable.NewRoutingTable(),
+		contributingASNs: routingtable.NewContributingASNs(),
 	}
 	a.ClientManager = routingtable.NewClientManager(a)
 	return a
 }
 
+// GetContributingASNs returns a pointer to the list of contributing ASNs
+func (a *LocRIB) GetContributingASNs() *routingtable.ContributingASNs {
+	return a.contributingASNs
+}
+
 //Count routes from the LocRIP
 func (a *LocRIB) Count() uint64 {
 	a.mu.RLock()
diff --git a/routingtable/mock_client.go b/routingtable/mock_client.go
new file mode 100644
index 0000000000000000000000000000000000000000..806dd2b3b29865d6821002cda1c2c7dc4c360279
--- /dev/null
+++ b/routingtable/mock_client.go
@@ -0,0 +1,52 @@
+package routingtable
+
+import (
+	"fmt"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type RemovePathParams struct {
+	Pfx  net.Prefix
+	Path *route.Path
+}
+
+type RTMockClient struct {
+	removePathParams RemovePathParams
+}
+
+func NewRTMockClient() *RTMockClient {
+	return &RTMockClient{}
+}
+
+func (m *RTMockClient) GetRemovePathParams() RemovePathParams {
+	return m.removePathParams
+}
+
+func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error {
+	return nil
+}
+
+func (m *RTMockClient) UpdateNewClient(client RouteTableClient) error {
+	return fmt.Errorf("Not implemented")
+}
+
+func (m *RTMockClient) Register(RouteTableClient) {
+	return
+}
+
+func (m *RTMockClient) RegisterWithOptions(RouteTableClient, ClientOptions) {
+	return
+}
+
+func (m *RTMockClient) Unregister(RouteTableClient) {
+	return
+}
+
+// RemovePath removes the path for prefix `pfx`
+func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool {
+	m.removePathParams.Pfx = pfx
+	m.removePathParams.Path = p
+	return true
+}
diff --git a/routingtable/update_helper.go b/routingtable/update_helper.go
index c18936f3a21055678401a9ec4697ab00363fc4f0..6709bd43ab7e2d39914c6ebd1c59abb6518e8189 100644
--- a/routingtable/update_helper.go
+++ b/routingtable/update_helper.go
@@ -1,12 +1,9 @@
 package routingtable
 
 import (
-	"strings"
-
 	"github.com/bio-routing/bio-rd/net"
 	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
 	"github.com/bio-routing/bio-rd/route"
-	log "github.com/sirupsen/logrus"
 )
 
 // ShouldPropagateUpdate performs some default checks and returns if an route update should be propagated to a neighbor
@@ -32,17 +29,7 @@ func isDisallowedByCommunity(p *route.Path, n *Neighbor) bool {
 		return false
 	}
 
-	strs := strings.Split(p.BGPPath.Communities, " ")
-	for _, str := range strs {
-		com, err := packet.ParseCommunityString(str)
-		if err != nil {
-			log.WithField("Sender", "routingtable.ShouldAnnounce()").
-				WithField("community", str).
-				WithError(err).
-				Error("Could not parse community")
-			continue
-		}
-
+	for _, com := range p.BGPPath.Communities {
 		if (com == packet.WellKnownCommunityNoExport && !n.IBGP) || com == packet.WellKnownCommunityNoAdvertise {
 			return true
 		}
diff --git a/routingtable/update_helper_test.go b/routingtable/update_helper_test.go
index 29342f376ead000006039e6aa95caf3399937dc1..452a7e4af04152e21c13631c38190b8d4795649d 100644
--- a/routingtable/update_helper_test.go
+++ b/routingtable/update_helper_test.go
@@ -2,9 +2,11 @@ package routingtable
 
 import (
 	"net"
+	"strings"
 	"testing"
 
 	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/stretchr/testify/assert"
 )
@@ -58,19 +60,31 @@ func TestShouldPropagateUpdate(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		t.Run(test.name, func(te *testing.T) {
-			pfx := bnet.NewPfx(0, 32)
+		t.Run(test.name, func(t *testing.T) {
+			comms := make([]uint32, 0)
+			for _, s := range strings.Split(test.communities, " ") {
+				if s == "" {
+					continue
+				}
+
+				com, err := packet.ParseCommunityString(s)
+				if err != nil {
+					t.Fatalf("test failed: %s", err)
+				}
+				comms = append(comms, com)
+			}
 
+			pfx := bnet.NewPfx(0, 32)
 			pa := &route.Path{
 				Type: route.BGPPathType,
 				BGPPath: &route.BGPPath{
-					Communities: test.communities,
+					Communities: comms,
 					Source:      bnet.IPv4ToUint32(net.ParseIP("192.168.1.1")),
 				},
 			}
 
 			res := ShouldPropagateUpdate(pfx, pa, &test.neighbor)
-			assert.Equal(te, test.expected, res)
+			assert.Equal(t, test.expected, res)
 		})
 	}
 }
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel
new file mode 100644
index 0000000000000000000000000000000000000000..3071b82f935eeb1d0f3f722214b282c98763245d
--- /dev/null
+++ b/testing/BUILD.bazel
@@ -0,0 +1,8 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["conn_mock.go"],
+    importpath = "github.com/bio-routing/bio-rd/testing",
+    visibility = ["//visibility:public"],
+)
diff --git a/testing/conn_mock.go b/testing/conn_mock.go
new file mode 100644
index 0000000000000000000000000000000000000000..86e8e1ae7d1c91eaa6efce317d34e8b08212c825
--- /dev/null
+++ b/testing/conn_mock.go
@@ -0,0 +1,32 @@
+package testing
+
+import "net"
+
+// MockConn mock an connection
+type MockConn struct {
+	net.Conn
+
+	// Bytes are the bytes writen
+	Bytes []byte
+}
+
+func NewMockConn() *MockConn {
+	return &MockConn{
+		Bytes: make([]byte, 0),
+	}
+}
+
+func (m *MockConn) Write(b []byte) (int, error) {
+	m.Bytes = append(m.Bytes, b...)
+	return len(b), nil
+}
+
+func (m *MockConn) Read(b []byte) (n int, err error) {
+	count := len(b)
+	if count > len(m.Bytes) {
+		count = len(m.Bytes)
+	}
+
+	copy(m.Bytes[0:count], b)
+	return count, nil
+}