diff --git a/BUILD.bazel b/BUILD.bazel
index f04b329b3f7bba0a788dc6fec24c1988c50d594e..ae2f1b1751ac95160a2e2097c520c3f99339211f 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -10,7 +10,10 @@ gazelle(
 
 go_library(
     name = "go_default_library",
-    srcs = ["main.go"],
+    srcs = [
+        "main.go",
+        "main_ipv4.go",
+    ],
     importpath = "github.com/bio-routing/bio-rd",
     visibility = ["//visibility:private"],
     deps = [
diff --git a/config/peer.go b/config/peer.go
index 5f151fe56c59ffa4ca410eb174745e755b15c09d..a192e317d3225560a7076232fac6d217ba4b790c 100644
--- a/config/peer.go
+++ b/config/peer.go
@@ -25,4 +25,5 @@ type Peer struct {
 	ImportFilter      *filter.Filter
 	ExportFilter      *filter.Filter
 	RouteServerClient bool
+	IPv6              bool
 }
diff --git a/main.go b/main.go
index 6b924601418c4335d5c7ccb7878eb4281d29710c..11c2a6895ade4237dbf48249437c2d202bb8aa58 100644
--- a/main.go
+++ b/main.go
@@ -2,15 +2,11 @@ package main
 
 import (
 	"fmt"
-	"net"
 	"time"
 
 	"github.com/sirupsen/logrus"
 
-	"github.com/bio-routing/bio-rd/config"
 	"github.com/bio-routing/bio-rd/protocols/bgp/server"
-	"github.com/bio-routing/bio-rd/routingtable"
-	"github.com/bio-routing/bio-rd/routingtable/filter"
 	"github.com/bio-routing/bio-rd/routingtable/locRIB"
 
 	bnet "github.com/bio-routing/bio-rd/net"
@@ -26,56 +22,7 @@ func main() {
 
 	rib := locRIB.New()
 	b := server.NewBgpServer()
-
-	err := b.Start(&config.Global{
-		Listen: true,
-		LocalAddressList: []net.IP{
-			net.IPv4(169, 254, 100, 1),
-			net.IPv4(169, 254, 200, 0),
-		},
-	})
-	if err != nil {
-		logrus.Fatalf("Unable to start BGP server: %v", err)
-	}
-
-	b.AddPeer(config.Peer{
-		AdminEnabled:      true,
-		LocalAS:           65200,
-		PeerAS:            65300,
-		PeerAddress:       bnet.IPv4FromOctets(172, 17, 0, 3),
-		LocalAddress:      bnet.IPv4FromOctets(169, 254, 200, 0),
-		ReconnectInterval: time.Second * 15,
-		HoldTime:          time.Second * 90,
-		KeepAlive:         time.Second * 30,
-		Passive:           true,
-		RouterID:          b.RouterID(),
-		AddPathSend: routingtable.ClientOptions{
-			MaxPaths: 10,
-		},
-		ImportFilter:      filter.NewAcceptAllFilter(),
-		ExportFilter:      filter.NewAcceptAllFilter(),
-		RouteServerClient: true,
-	}, rib)
-
-	b.AddPeer(config.Peer{
-		AdminEnabled:      true,
-		LocalAS:           65200,
-		PeerAS:            65100,
-		PeerAddress:       bnet.IPv4FromOctets(172, 17, 0, 2),
-		LocalAddress:      bnet.IPv4FromOctets(169, 254, 100, 1),
-		ReconnectInterval: time.Second * 15,
-		HoldTime:          time.Second * 90,
-		KeepAlive:         time.Second * 30,
-		Passive:           true,
-		RouterID:          b.RouterID(),
-		AddPathSend: routingtable.ClientOptions{
-			MaxPaths: 10,
-		},
-		AddPathRecv:       true,
-		ImportFilter:      filter.NewAcceptAllFilter(),
-		ExportFilter:      filter.NewAcceptAllFilter(),
-		RouteServerClient: true,
-	}, rib)
+	startServer(b, rib)
 
 	go func() {
 		for {
diff --git a/main_ipv4.go b/main_ipv4.go
new file mode 100644
index 0000000000000000000000000000000000000000..65293f07853d5819abdb96a6ab5def218f0c2320
--- /dev/null
+++ b/main_ipv4.go
@@ -0,0 +1,70 @@
+// +build !ipv6
+
+package main
+
+import (
+	"net"
+	"time"
+
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+
+	"github.com/bio-routing/bio-rd/config"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/filter"
+	"github.com/sirupsen/logrus"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+)
+
+func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
+	err := b.Start(&config.Global{
+		Listen: true,
+		LocalAddressList: []net.IP{
+			net.IPv4(169, 254, 100, 1),
+			net.IPv4(169, 254, 200, 0),
+		},
+	})
+	if err != nil {
+		logrus.Fatalf("Unable to start BGP server: %v", err)
+	}
+
+	b.AddPeer(config.Peer{
+		AdminEnabled:      true,
+		LocalAS:           65200,
+		PeerAS:            65300,
+		PeerAddress:       bnet.IPv4FromOctets(172, 17, 0, 3),
+		LocalAddress:      bnet.IPv4FromOctets(169, 254, 200, 0),
+		ReconnectInterval: time.Second * 15,
+		HoldTime:          time.Second * 90,
+		KeepAlive:         time.Second * 30,
+		Passive:           true,
+		RouterID:          b.RouterID(),
+		AddPathSend: routingtable.ClientOptions{
+			MaxPaths: 10,
+		},
+		ImportFilter:      filter.NewAcceptAllFilter(),
+		ExportFilter:      filter.NewAcceptAllFilter(),
+		RouteServerClient: true,
+	}, rib)
+
+	b.AddPeer(config.Peer{
+		AdminEnabled:      true,
+		LocalAS:           65200,
+		PeerAS:            65100,
+		PeerAddress:       bnet.IPv4FromOctets(172, 17, 0, 2),
+		LocalAddress:      bnet.IPv4FromOctets(169, 254, 100, 1),
+		ReconnectInterval: time.Second * 15,
+		HoldTime:          time.Second * 90,
+		KeepAlive:         time.Second * 30,
+		Passive:           true,
+		RouterID:          b.RouterID(),
+		AddPathSend: routingtable.ClientOptions{
+			MaxPaths: 10,
+		},
+		AddPathRecv:       true,
+		ImportFilter:      filter.NewAcceptAllFilter(),
+		ExportFilter:      filter.NewAcceptAllFilter(),
+		RouteServerClient: true,
+	}, rib)
+}
diff --git a/main_ipv6.go b/main_ipv6.go
new file mode 100644
index 0000000000000000000000000000000000000000..9afc7e903d6e186bb386f64b00d268acde967140
--- /dev/null
+++ b/main_ipv6.go
@@ -0,0 +1,48 @@
+// +build ipv6
+
+package main
+
+import (
+	"net"
+	"time"
+
+	"github.com/bio-routing/bio-rd/config"
+	"github.com/bio-routing/bio-rd/protocols/bgp/server"
+	"github.com/bio-routing/bio-rd/routingtable"
+	"github.com/bio-routing/bio-rd/routingtable/filter"
+	"github.com/bio-routing/bio-rd/routingtable/locRIB"
+	"github.com/sirupsen/logrus"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+)
+
+func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
+	err := b.Start(&config.Global{
+		Listen: true,
+		LocalAddressList: []net.IP{
+			net.IP{0x20, 0x01, 0x6, 0x78, 0x1, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 0xfe},
+		},
+	})
+	if err != nil {
+		logrus.Fatalf("Unable to start BGP server: %v", err)
+	}
+
+	b.AddPeer(config.Peer{
+		AdminEnabled:      true,
+		LocalAS:           65200,
+		PeerAS:            202739,
+		PeerAddress:       bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1),
+		LocalAddress:      bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe),
+		ReconnectInterval: time.Second * 15,
+		HoldTime:          time.Second * 90,
+		KeepAlive:         time.Second * 30,
+		Passive:           true,
+		RouterID:          b.RouterID(),
+		AddPathSend: routingtable.ClientOptions{
+			BestOnly: true,
+		},
+		ImportFilter: filter.NewAcceptAllFilter(),
+		ExportFilter: filter.NewDrainFilter(),
+		IPv6:         true,
+	}, rib)
+}
diff --git a/net/ip.go b/net/ip.go
index 2855235eae0e6c44753f351f2363ed790f2ed9e3..8a532a2c09f36033fbb27d0e8aa69a9e10942492 100644
--- a/net/ip.go
+++ b/net/ip.go
@@ -41,6 +41,27 @@ func IPv6FromBlocks(b1, b2, b3, b4, b5, b6, b7, b8 uint16) IP {
 		uint64(uint64(b5)<<48+uint64(b6)<<32+uint64(b7)<<16+uint64(b8)))
 }
 
+// IPFromBytes returns an IP address for a byte slice
+func IPFromBytes(b []byte) (IP, error) {
+	if len(b) == 4 {
+		return IPv4FromOctets(b[0], b[1], b[2], b[3]), nil
+	}
+
+	if len(b) == 16 {
+		return IPv6FromBlocks(
+			uint16(b[0])<<8+uint16(b[1]),
+			uint16(b[2])<<8+uint16(b[3]),
+			uint16(b[4])<<8+uint16(b[5]),
+			uint16(b[6])<<8+uint16(b[7]),
+			uint16(b[8])<<8+uint16(b[9]),
+			uint16(b[10])<<8+uint16(b[11]),
+			uint16(b[12])<<8+uint16(b[13]),
+			uint16(b[14])<<8+uint16(b[15])), nil
+	}
+
+	return IP{}, fmt.Errorf("byte slice has an invalid legth. Expected either 4 (IPv4) or 16 (IPv6) bytes but got: %d", len(b))
+}
+
 // Equal returns true if ip is equal to other
 func (ip IP) Equal(other IP) bool {
 	return ip == other
@@ -161,5 +182,13 @@ func (ip IP) bitAtPositionIPv4(pos uint8) bool {
 }
 
 func (ip IP) bitAtPositionIPv6(pos uint8) bool {
-	panic("No IPv6 support yet!")
+	if pos > 128 {
+		return false
+	}
+
+	if pos <= 64 {
+		return (ip.higher & (1 << (64 - pos))) != 0
+	}
+
+	return (ip.lower & (1 << (128 - pos))) != 0
 }
diff --git a/net/ip_test.go b/net/ip_test.go
index 613e342d7ffe46460007aeec692da8c39286258f..4720985f825ba242b1c78cf75773499d55bdd9bf 100644
--- a/net/ip_test.go
+++ b/net/ip_test.go
@@ -228,6 +228,50 @@ func TestIPv6FromBlocks(t *testing.T) {
 	}
 }
 
+func TestIPFromBytes(t *testing.T) {
+	tests := []struct {
+		name     string
+		bytes    []byte
+		expected IP
+		wantFail bool
+	}{
+		{
+			name:  "IPV4: 172.217.16.195",
+			bytes: []byte{172, 217, 16, 195},
+			expected: IP{
+				higher:    0,
+				lower:     2899906755,
+				ipVersion: 4,
+			},
+		},
+		{
+			name:  "IPV6: IPv6 2001:678:1E0:1234:5678:DEAD:BEEF:CAFE",
+			bytes: []byte{0x20, 0x01, 0x06, 0x78, 0x01, 0xE0, 0x12, 0x34, 0x56, 0x78, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE},
+			expected: IP{
+				higher:    2306131596687708724,
+				lower:     6230974922281175806,
+				ipVersion: 6,
+			},
+		},
+		{
+			name:     "invalid length",
+			bytes:    []byte{172, 217, 123, 231, 95},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			ip, err := IPFromBytes(test.bytes)
+			if err == nil && test.wantFail {
+				t.Fatalf("Expected test to fail, but did not")
+			}
+
+			assert.Equal(t, test.expected, ip)
+		})
+	}
+}
+
 func TestToNetIP(t *testing.T) {
 	tests := []struct {
 		name     string
@@ -269,17 +313,53 @@ func TestBitAtPosition(t *testing.T) {
 		expected bool
 	}{
 		{
-			name:     "Bit 8 from 1.0.0.0 -> 0",
+			name:     "IPv4: all ones -> 0",
+			input:    IPv4FromOctets(255, 255, 255, 255),
+			position: 1,
+			expected: true,
+		},
+		{
+			name:     "IPv4: Bit 8 from 1.0.0.0 -> 0",
 			input:    IPv4FromOctets(10, 0, 0, 0),
 			position: 8,
 			expected: false,
 		},
 		{
-			name:     "Bit 8 from 11.0.0.0 -> 1",
+			name:     "IPv4: Bit 8 from 11.0.0.0 -> 1",
 			input:    IPv4FromOctets(11, 0, 0, 0),
 			position: 8,
 			expected: true,
 		},
+		{
+			name:     "IPv6: Bit 16 from 2001:678:1e0:: -> 1",
+			input:    IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+			position: 16,
+			expected: true,
+		},
+		{
+			name:     "IPv6: Bit 17 from 2001:678:1e0:: -> 0",
+			input:    IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+			position: 17,
+			expected: false,
+		},
+		{
+			name:     "IPv6: Bit 113 from 2001:678:1e0::cafe -> 1",
+			input:    IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe),
+			position: 113,
+			expected: true,
+		},
+		{
+			name:     "IPv6: Bit 115 from 2001:678:1e0::cafe -> 0",
+			input:    IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe),
+			position: 115,
+			expected: false,
+		},
+		{
+			name:     "IPv6: all ones -> 1",
+			input:    IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF),
+			position: 1,
+			expected: true,
+		},
 	}
 
 	for _, test := range tests {
diff --git a/net/prefix.go b/net/prefix.go
index c40420247dfbfb11e8e05c01bae2fa1ed7d5e183..5abd32f141f8ae5cfb9c107d2bc5a7ce98bddf4e 100644
--- a/net/prefix.go
+++ b/net/prefix.go
@@ -70,7 +70,7 @@ func (pfx Prefix) Contains(x Prefix) bool {
 		return pfx.containsIPv4(x)
 	}
 
-	panic("No IPv6 support yet!")
+	return pfx.containsIPv6(x)
 }
 
 func (pfx Prefix) containsIPv4(x Prefix) bool {
@@ -78,6 +78,20 @@ func (pfx Prefix) containsIPv4(x Prefix) bool {
 	return (pfx.addr.ToUint32() & mask) == (x.addr.ToUint32() & mask)
 }
 
+func (pfx Prefix) containsIPv6(x Prefix) bool {
+	var maskHigh, maskLow uint64
+	if pfx.pfxlen <= 64 {
+		maskHigh = math.MaxUint32 << (64 - pfx.pfxlen)
+		maskLow = uint64(0)
+	} else {
+		maskHigh = math.MaxUint32
+		maskLow = math.MaxUint32 << (128 - pfx.pfxlen)
+	}
+
+	return pfx.addr.higher&maskHigh&maskHigh == x.addr.higher&maskHigh&maskHigh &&
+		pfx.addr.lower&maskHigh&maskLow == x.addr.lower&maskHigh&maskLow
+}
+
 // Equal checks if pfx and x are equal
 func (pfx Prefix) Equal(x Prefix) bool {
 	return pfx == x
@@ -89,7 +103,7 @@ func (pfx Prefix) GetSupernet(x Prefix) Prefix {
 		return pfx.supernetIPv4(x)
 	}
 
-	panic("No IPv6 support yet!")
+	return pfx.supernetIPv6(x)
 }
 
 func (pfx Prefix) supernetIPv4(x Prefix) Prefix {
@@ -109,6 +123,37 @@ func (pfx Prefix) supernetIPv4(x Prefix) Prefix {
 	}
 }
 
+func (pfx Prefix) supernetIPv6(x Prefix) Prefix {
+	maxPfxLen := min(pfx.pfxlen, x.pfxlen)
+
+	a := pfx.addr.BitAtPosition(1)
+	b := x.addr.BitAtPosition(1)
+	pfxLen := uint8(0)
+	mask := uint64(0)
+	for a == b && pfxLen < maxPfxLen {
+		a = pfx.addr.BitAtPosition(pfxLen + 2)
+		b = x.addr.BitAtPosition(pfxLen + 2)
+		pfxLen++
+
+		if pfxLen == 64 {
+			mask = 0
+		}
+
+		m := pfxLen % 64
+		mask = mask + uint64(1)<<(64-m)
+	}
+
+	if pfxLen == 0 {
+		return NewPfx(IPv6(0, 0), pfxLen)
+	}
+
+	if pfxLen > 64 {
+		return NewPfx(IPv6(pfx.addr.higher, pfx.addr.lower&mask), pfxLen)
+	}
+
+	return NewPfx(IPv6(pfx.addr.higher&mask, 0), pfxLen)
+}
+
 func min(a uint8, b uint8) uint8 {
 	if a < b {
 		return a
diff --git a/net/prefix_test.go b/net/prefix_test.go
index d886a49e4ae1ed49fede133df33ac4d90331dd16..ccb05a7d3d753d61d6389d9ba6363da7562317f2 100644
--- a/net/prefix_test.go
+++ b/net/prefix_test.go
@@ -63,7 +63,7 @@ func TestGetSupernet(t *testing.T) {
 		expected Prefix
 	}{
 		{
-			name: "Test 1",
+			name: "Supernet of 10.0.0.0 and 11.100.123.0 -> 10.0.0.0/7",
 			a: Prefix{
 				addr:   IPv4FromOctets(10, 0, 0, 0),
 				pfxlen: 8,
@@ -78,7 +78,7 @@ func TestGetSupernet(t *testing.T) {
 			},
 		},
 		{
-			name: "Test 2",
+			name: "Supernet of 10.0.0.0 and 192.168.0.0 -> 0.0.0.0/0",
 			a: Prefix{
 				addr:   IPv4FromOctets(10, 0, 0, 0),
 				pfxlen: 8,
@@ -88,15 +88,64 @@ func TestGetSupernet(t *testing.T) {
 				pfxlen: 24,
 			},
 			expected: Prefix{
-				addr:   IPv4(0), // 0.0.0.0/0
+				addr:   IPv4(0),
+				pfxlen: 0,
+			},
+		},
+		{
+			name: "Supernet of 2001:678:1e0:100:23::/64 and 2001:678:1e0:1ff::/64 -> 2001:678:1e0:100::/56",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0x23, 0, 0, 0),
+				pfxlen: 64,
+			},
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x1ff, 0, 0, 0, 0),
+				pfxlen: 64,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0),
+				pfxlen: 56,
+			},
+		},
+		{
+			name: "Supernet of 2001:678:1e0::/128 and 2001:678:1e0::1/128 -> 2001:678:1e0:100::/127",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+				pfxlen: 128,
+			},
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1),
+				pfxlen: 128,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+				pfxlen: 127,
+			},
+		},
+		{
+			name: "Supernet of all ones and all zeros -> ::/0",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF),
+				pfxlen: 128,
+			},
+			b: Prefix{
+				addr:   IPv6(0, 0),
+				pfxlen: 128,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0),
 				pfxlen: 0,
 			},
 		},
 	}
 
+	t.Parallel()
+
 	for _, test := range tests {
-		s := test.a.GetSupernet(test.b)
-		assert.Equal(t, s, test.expected)
+		t.Run(test.name, func(t *testing.T) {
+			s := test.a.GetSupernet(test.b)
+			assert.Equal(t, test.expected, s)
+		})
 	}
 }
 
@@ -191,6 +240,30 @@ func TestContains(t *testing.T) {
 			},
 			expected: false,
 		},
+		{
+			name: "IPv6: 2001:678:1e0:100::/56 is subnet of 2001:678:1e0::/48",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+				pfxlen: 48,
+			},
+			expected: true,
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0),
+				pfxlen: 56,
+			},
+		},
+		{
+			name: "IPv6: 2001:678:1e0:100::/56 is subnet of 2001:678:1e0::/48",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x200, 0, 0, 0, 0),
+				pfxlen: 56,
+			},
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0),
+				pfxlen: 64,
+			},
+			expected: false,
+		},
 	}
 
 	for _, test := range tests {
diff --git a/protocols/bgp/packet/BUILD.bazel b/protocols/bgp/packet/BUILD.bazel
index d6f4d73f49b4a5ec4eaf7b1d288a1e32bd2eb88d..f9886e41d0eea9ee21bac03dc902ad7f4218b131 100644
--- a/protocols/bgp/packet/BUILD.bazel
+++ b/protocols/bgp/packet/BUILD.bazel
@@ -6,6 +6,9 @@ go_library(
         "bgp.go",
         "decoder.go",
         "encoder.go",
+        "helper.go",
+        "mp_reach_nlri.go",
+        "mp_unreach_nlri.go",
         "nlri.go",
         "parameters.go",
         "path_attribute_flags.go",
@@ -27,6 +30,8 @@ go_test(
     srcs = [
         "decoder_test.go",
         "encoder_test.go",
+        "mp_reach_nlri_test.go",
+        "mp_unreach_nlri_test.go",
         "nlri_test.go",
         "path_attributes_test.go",
     ],
diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go
index c6776ace6c41f6ccbfd8e2b126f38fc5c16d4798..e008205a2ad95e97872c0a691a479efe42888c24 100644
--- a/protocols/bgp/packet/bgp.go
+++ b/protocols/bgp/packet/bgp.go
@@ -85,15 +85,26 @@ const (
 	ConnectionCollisionResolution = 7
 	OutOfResoutces                = 8
 
-	IPv4AFI               = 1
-	UnicastSAFI           = 1
-	CapabilitiesParamType = 2
-	AddPathCapabilityCode = 69
-	ASN4CapabilityCode    = 65
-	AddPathReceive        = 1
-	AddPathSend           = 2
-	AddPathSendReceive    = 3
-	ASTransASN            = 23456
+	IPv4AFI                      = 1
+	IPv6AFI                      = 2
+	UnicastSAFI                  = 1
+	CapabilitiesParamType        = 2
+	MultiProtocolCapabilityCode  = 1
+	MultiProtocolReachNLRICode   = 14
+	MultiProtocolUnreachNLRICode = 15
+	AddPathCapabilityCode        = 69
+	ASN4CapabilityCode           = 65
+	AddPathReceive               = 1
+	AddPathSend                  = 2
+	AddPathSendReceive           = 3
+	ASTransASN                   = 23456
+)
+
+var (
+	afiAddrLenBytes = map[uint16]uint8{
+		1: 4,
+		2: 16,
+	}
 )
 
 type BGPError struct {
diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go
index ca717c781194adf20d2d5c08f332c03f060be849..cd951463a15215bd6aaa43e8799e59c95cee549b 100644
--- a/protocols/bgp/packet/decoder.go
+++ b/protocols/bgp/packet/decoder.go
@@ -233,6 +233,12 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) {
 	}
 
 	switch cap.Code {
+	case MultiProtocolCapabilityCode:
+		mpCap, err := decodeMultiProtocolCapability(buf)
+		if err != nil {
+			return cap, fmt.Errorf("Unable to decode multi protocol capability")
+		}
+		cap.Value = mpCap
 	case AddPathCapabilityCode:
 		addPathCap, err := decodeAddPathCapability(buf)
 		if err != nil {
@@ -257,6 +263,21 @@ func decodeCapability(buf *bytes.Buffer) (Capability, error) {
 	return cap, nil
 }
 
+func decodeMultiProtocolCapability(buf *bytes.Buffer) (MultiProtocolCapability, error) {
+	mpCap := MultiProtocolCapability{}
+	reserved := uint8(0)
+	fields := []interface{}{
+		&mpCap.AFI, &reserved, &mpCap.SAFI,
+	}
+
+	err := decode(buf, fields)
+	if err != nil {
+		return mpCap, err
+	}
+
+	return mpCap, nil
+}
+
 func decodeAddPathCapability(buf *bytes.Buffer) (AddPathCapability, error) {
 	addPathCap := AddPathCapability{}
 	fields := []interface{}{
diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go
index 5613bbcb6c46a385492f5c38f5a837ae5e287ee7..ac611ceff133e4375e70995a2b564900671ed8aa 100644
--- a/protocols/bgp/packet/decoder_test.go
+++ b/protocols/bgp/packet/decoder_test.go
@@ -1736,6 +1736,19 @@ func TestDecodeCapability(t *testing.T) {
 			},
 			wantFail: false,
 		},
+		{
+			name:  "MP Capability (IPv6)",
+			input: []byte{1, 4, 0, 2, 0, 1},
+			expected: Capability{
+				Code:   MultiProtocolCapabilityCode,
+				Length: 4,
+				Value: MultiProtocolCapability{
+					AFI:  IPv6AFI,
+					SAFI: UnicastSAFI,
+				},
+			},
+			wantFail: false,
+		},
 		{
 			name:     "Fail",
 			input:    []byte{69, 4, 0, 1},
diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go
index fd7405d735a5d11a4117d144e7086828c86f5628..1b0b60d5c919227acc5418dfef3e5802aa92b8d7 100644
--- a/protocols/bgp/packet/encoder_test.go
+++ b/protocols/bgp/packet/encoder_test.go
@@ -106,30 +106,61 @@ func TestSerializeOptParams(t *testing.T) {
 			expected:  []byte{},
 		},
 		{
-			name: "1 Option",
+			name: "AddPath",
 			optParams: []OptParam{
-				{
+				OptParam{
 					Type:   2,
 					Length: 6,
-					Value: Capability{
-						Code:   69,
-						Length: 4,
-						Value: AddPathCapability{
-							AFI:         1,
-							SAFI:        1,
-							SendReceive: 3,
+					Value: Capabilities{
+						Capability{
+							Code:   69,
+							Length: 4,
+							Value: AddPathCapability{
+								AFI:         1,
+								SAFI:        1,
+								SendReceive: 3,
+							},
 						},
 					},
 				},
 			},
 			expected: []byte{2, 6, 69, 4, 0, 1, 1, 3},
 		},
+		{
+			name: "Multi Protocol Support (IPv6), 32 bit ASNs",
+			optParams: []OptParam{
+				OptParam{
+					Length: 12,
+					Type:   CapabilitiesParamType,
+					Value: Capabilities{
+						Capability{
+							Code:   MultiProtocolCapabilityCode,
+							Length: 4,
+							Value: MultiProtocolCapability{
+								AFI:  2,
+								SAFI: 1,
+							},
+						},
+						Capability{
+							Code:   ASN4CapabilityCode,
+							Length: 4,
+							Value: ASN4Capability{
+								ASN4: 202739,
+							},
+						},
+					},
+				},
+			},
+			expected: []byte{2, 12, 1, 4, 0, 2, 0, 1, 65, 4, 0x00, 0x03, 0x17, 0xf3},
+		},
 	}
 
 	for _, test := range tests {
-		buf := bytes.NewBuffer(make([]byte, 0))
-		serializeOptParams(buf, test.optParams)
-		assert.Equal(t, test.expected, buf.Bytes())
+		t.Run(test.name, func(t *testing.T) {
+			buf := bytes.NewBuffer(make([]byte, 0))
+			serializeOptParams(buf, test.optParams)
+			assert.Equal(t, test.expected, buf.Bytes())
+		})
 	}
 }
 
diff --git a/protocols/bgp/packet/helper.go b/protocols/bgp/packet/helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..991af5a5eb9410c6875400c3b88ed3ee2e7c8de4
--- /dev/null
+++ b/protocols/bgp/packet/helper.go
@@ -0,0 +1,44 @@
+package packet
+
+import (
+	"fmt"
+	"math"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+)
+
+func serializePrefix(pfx bnet.Prefix) []byte {
+	if pfx.Pfxlen() == 0 {
+		return []byte{}
+	}
+
+	numBytes := numberOfBytesForPrefixLength(pfx.Pfxlen())
+
+	b := make([]byte, numBytes+1)
+	b[0] = pfx.Pfxlen()
+	copy(b[1:numBytes+1], pfx.Addr().Bytes()[0:numBytes])
+
+	return b
+}
+
+func deserializePrefix(b []byte, pfxLen uint8, afi uint16) (bnet.Prefix, error) {
+	numBytes := numberOfBytesForPrefixLength(pfxLen)
+
+	if numBytes != uint8(len(b)) {
+		return bnet.Prefix{}, fmt.Errorf("could not parse prefix of length %d. Expected %d bytes, got %d", pfxLen, numBytes, len(b))
+	}
+
+	ipBytes := make([]byte, afiAddrLenBytes[afi])
+	copy(ipBytes, b)
+
+	ip, err := bnet.IPFromBytes(ipBytes)
+	if err != nil {
+		return bnet.Prefix{}, err
+	}
+
+	return bnet.NewPfx(ip, pfxLen), nil
+}
+
+func numberOfBytesForPrefixLength(pfxLen uint8) uint8 {
+	return uint8(math.Ceil(float64(pfxLen) / 8))
+}
diff --git a/protocols/bgp/packet/mp_reach_nlri.go b/protocols/bgp/packet/mp_reach_nlri.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec18df05c242e714951ff433fad194e8e24db80b
--- /dev/null
+++ b/protocols/bgp/packet/mp_reach_nlri.go
@@ -0,0 +1,90 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/taktv6/tflow2/convert"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+)
+
+// MultiProtocolReachNLRI represents network layer reachability information for one prefix of an IP address family (rfc4760)
+type MultiProtocolReachNLRI struct {
+	AFI      uint16
+	SAFI     uint8
+	NextHop  bnet.IP
+	Prefixes []bnet.Prefix
+}
+
+func (n *MultiProtocolReachNLRI) serialize(buf *bytes.Buffer) uint8 {
+	nextHop := n.NextHop.Bytes()
+
+	tempBuf := bytes.NewBuffer(nil)
+	tempBuf.Write(convert.Uint16Byte(n.AFI))
+	tempBuf.WriteByte(n.SAFI)
+	tempBuf.WriteByte(uint8(len(nextHop)))
+	tempBuf.Write(nextHop)
+	tempBuf.WriteByte(0) // RESERVED
+	for _, pfx := range n.Prefixes {
+		tempBuf.Write(serializePrefix(pfx))
+	}
+
+	buf.Write(tempBuf.Bytes())
+
+	return uint8(tempBuf.Len())
+}
+
+func deserializeMultiProtocolReachNLRI(b []byte) (MultiProtocolReachNLRI, error) {
+	n := MultiProtocolReachNLRI{}
+	nextHopLength := uint8(0)
+	variable := make([]byte, len(b)-4)
+
+	fields := []interface{}{
+		&n.AFI,
+		&n.SAFI,
+		&nextHopLength,
+		&variable,
+	}
+	err := decode(bytes.NewBuffer(b), fields)
+	if err != nil {
+		return MultiProtocolReachNLRI{}, err
+	}
+
+	n.NextHop, err = bnet.IPFromBytes(variable[:nextHopLength])
+	if err != nil {
+		return MultiProtocolReachNLRI{}, fmt.Errorf("Failed to decode next hop IP: %v", err)
+	}
+
+	variable = variable[1+nextHopLength:]
+
+	n.Prefixes = make([]bnet.Prefix, 0)
+
+	if len(variable) == 0 {
+		return n, nil
+	}
+
+	idx := uint16(0)
+	for idx < uint16(len(variable)) {
+		pfxLen := variable[idx]
+		numBytes := uint16(numberOfBytesForPrefixLength(pfxLen))
+		idx++
+
+		r := uint16(len(variable)) - idx
+		if r < numBytes {
+			return MultiProtocolReachNLRI{}, fmt.Errorf("expected %d bytes for NLRI, only %d remaining", numBytes, r)
+		}
+
+		start := idx
+		end := idx + numBytes
+		pfx, err := deserializePrefix(variable[start:end], pfxLen, n.AFI)
+		if err != nil {
+			return MultiProtocolReachNLRI{}, err
+		}
+		n.Prefixes = append(n.Prefixes, pfx)
+
+		idx = idx + numBytes
+	}
+
+	return n, nil
+}
diff --git a/protocols/bgp/packet/mp_reach_nlri_test.go b/protocols/bgp/packet/mp_reach_nlri_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f16ac3c4a181eb3bd0534f0e831bbb00eb09c8cf
--- /dev/null
+++ b/protocols/bgp/packet/mp_reach_nlri_test.go
@@ -0,0 +1,44 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSerializeMultiProtocolReachNLRI(t *testing.T) {
+	tests := []struct {
+		name     string
+		nlri     MultiProtocolReachNLRI
+		expected []byte
+	}{
+		{
+			name: "Simple IPv6 prefix",
+			nlri: MultiProtocolReachNLRI{
+				AFI:     IPv6AFI,
+				SAFI:    UnicastSAFI,
+				NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2),
+				Prefixes: []bnet.Prefix{
+					bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+				},
+			},
+			expected: []byte{
+				0x00, 0x02, // AFI
+				0x01,                                                                                                 // SAFI
+				0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop
+				0x00,                                     // RESERVED
+				0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			buf := &bytes.Buffer{}
+			test.nlri.serialize(buf)
+			assert.Equal(t, test.expected, buf.Bytes())
+		})
+	}
+}
diff --git a/protocols/bgp/packet/mp_unreach_nlri.go b/protocols/bgp/packet/mp_unreach_nlri.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7835cd11a24da70d811c1653f3c93cb13b824a3
--- /dev/null
+++ b/protocols/bgp/packet/mp_unreach_nlri.go
@@ -0,0 +1,72 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+	"github.com/taktv6/tflow2/convert"
+)
+
+// MultiProtocolUnreachNLRI represents network layer withdraw information for one prefix of an IP address family (rfc4760)
+type MultiProtocolUnreachNLRI struct {
+	AFI      uint16
+	SAFI     uint8
+	Prefixes []bnet.Prefix
+}
+
+func (n *MultiProtocolUnreachNLRI) serialize(buf *bytes.Buffer) uint8 {
+	tempBuf := bytes.NewBuffer(nil)
+	tempBuf.Write(convert.Uint16Byte(n.AFI))
+	tempBuf.WriteByte(n.SAFI)
+	for _, pfx := range n.Prefixes {
+		tempBuf.Write(serializePrefix(pfx))
+	}
+
+	buf.Write(tempBuf.Bytes())
+
+	return uint8(tempBuf.Len())
+}
+
+func deserializeMultiProtocolUnreachNLRI(b []byte) (MultiProtocolUnreachNLRI, error) {
+	n := MultiProtocolUnreachNLRI{}
+	prefix := make([]byte, len(b)-3)
+
+	fields := []interface{}{
+		&n.AFI,
+		&n.SAFI,
+		&prefix,
+	}
+	err := decode(bytes.NewBuffer(b), fields)
+	if err != nil {
+		return MultiProtocolUnreachNLRI{}, err
+	}
+
+	if len(prefix) == 0 {
+		return n, nil
+	}
+
+	idx := uint16(0)
+	for idx < uint16(len(prefix)) {
+		pfxLen := prefix[idx]
+		numBytes := uint16(numberOfBytesForPrefixLength(pfxLen))
+		idx++
+
+		r := uint16(len(prefix)) - idx
+		if r < numBytes {
+			return MultiProtocolUnreachNLRI{}, fmt.Errorf("expected %d bytes for NLRI, only %d remaining", numBytes, r)
+		}
+
+		start := idx
+		end := idx + numBytes
+		pfx, err := deserializePrefix(prefix[start:end], pfxLen, n.AFI)
+		if err != nil {
+			return MultiProtocolUnreachNLRI{}, err
+		}
+		n.Prefixes = append(n.Prefixes, pfx)
+
+		idx = idx + numBytes
+	}
+
+	return n, nil
+}
diff --git a/protocols/bgp/packet/mp_unreach_nlri_test.go b/protocols/bgp/packet/mp_unreach_nlri_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..02e1b00fff6d161b37509e1748d20bd04dcb3d35
--- /dev/null
+++ b/protocols/bgp/packet/mp_unreach_nlri_test.go
@@ -0,0 +1,41 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSerializeMultiProtocolUnreachNLRI(t *testing.T) {
+	tests := []struct {
+		name     string
+		nlri     MultiProtocolUnreachNLRI
+		expected []byte
+	}{
+		{
+			name: "Simple IPv6 prefix",
+			nlri: MultiProtocolUnreachNLRI{
+				AFI:  IPv6AFI,
+				SAFI: UnicastSAFI,
+				Prefixes: []bnet.Prefix{
+					bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
+				},
+			},
+			expected: []byte{
+				0x00, 0x02, // AFI
+				0x01,                                     // SAFI
+				0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			buf := &bytes.Buffer{}
+			test.nlri.serialize(buf)
+			assert.Equal(t, test.expected, buf.Bytes())
+		})
+	}
+}
diff --git a/protocols/bgp/packet/parameters.go b/protocols/bgp/packet/parameters.go
index 0ed9af66fe01f96600c02eeb2c00b69b11edc76c..2f99f773af6e2659beb1c4585cb7c2b27ed5ff01 100644
--- a/protocols/bgp/packet/parameters.go
+++ b/protocols/bgp/packet/parameters.go
@@ -29,6 +29,8 @@ func (c Capabilities) serialize(buf *bytes.Buffer) {
 	for _, cap := range c {
 		cap.serialize(tmpBuf)
 	}
+
+	buf.Write(tmpBuf.Bytes())
 }
 
 func (c Capability) serialize(buf *bytes.Buffer) {
@@ -60,3 +62,14 @@ type ASN4Capability struct {
 func (a ASN4Capability) serialize(buf *bytes.Buffer) {
 	buf.Write(convert.Uint32Byte(a.ASN4))
 }
+
+type MultiProtocolCapability struct {
+	AFI  uint16
+	SAFI uint8
+}
+
+func (a MultiProtocolCapability) serialize(buf *bytes.Buffer) {
+	buf.Write(convert.Uint16Byte(a.AFI))
+	buf.WriteByte(0) // RESERVED
+	buf.WriteByte(a.SAFI)
+}
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
index f1aae242153e09da32de808fabe40efbcc719456..77025f38ef976917f088509a8481aa3e53a363a5 100644
--- a/protocols/bgp/packet/path_attributes.go
+++ b/protocols/bgp/packet/path_attributes.go
@@ -99,6 +99,14 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c
 		if err := pa.decodeCommunities(buf); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err)
 		}
+	case MultiProtocolReachNLRICode:
+		if err := pa.decodeMultiProtocolReachNLRI(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to multi protocol reachable NLRI: %v", err)
+		}
+	case MultiProtocolUnreachNLRICode:
+		if err := pa.decodeMultiProtocolUnreachNLRI(buf); err != nil {
+			return nil, consumed, fmt.Errorf("Failed to multi protocol unreachable NLRI: %v", err)
+		}
 	case AS4AggregatorAttr:
 		if err := pa.decodeAS4Aggregator(buf); err != nil {
 			return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err)
@@ -116,18 +124,53 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c
 	return pa, consumed + pa.Length, nil
 }
 
+func (pa *PathAttribute) decodeMultiProtocolReachNLRI(buf *bytes.Buffer) error {
+	b := make([]byte, pa.Length)
+	n, err := buf.Read(b)
+	if err != nil {
+		return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err)
+	}
+	if n != int(pa.Length) {
+		return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n)
+	}
+
+	nlri, err := deserializeMultiProtocolReachNLRI(b)
+	if err != nil {
+		return fmt.Errorf("Unable to decode MP_REACH_NLRI: %v", err)
+	}
+
+	pa.Value = nlri
+	return nil
+}
+
+func (pa *PathAttribute) decodeMultiProtocolUnreachNLRI(buf *bytes.Buffer) error {
+	b := make([]byte, pa.Length)
+	n, err := buf.Read(b)
+	if err != nil {
+		return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err)
+	}
+	if n != int(pa.Length) {
+		return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n)
+	}
+
+	nlri, err := deserializeMultiProtocolUnreachNLRI(b)
+	if err != nil {
+		return fmt.Errorf("Unable to decode MP_UNREACH_NLRI: %v", err)
+	}
+
+	pa.Value = nlri
+	return nil
+}
+
 func (pa *PathAttribute) decodeUnknown(buf *bytes.Buffer) error {
 	u := make([]byte, pa.Length)
 
-	p := uint16(0)
 	err := decode(buf, []interface{}{&u})
 	if err != nil {
 		return fmt.Errorf("Unable to decode: %v", err)
 	}
 
 	pa.Value = u
-	p += pa.Length
-
 	return nil
 }
 
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
index 4a0966e6b3dee2a6bbbf3bb4712247eefc7696e1..3bd225984559e952a1a151c56834d15876a264ab 100644
--- a/protocols/bgp/packet/path_attributes_test.go
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -746,6 +746,137 @@ func TestDecodeCommunity(t *testing.T) {
 	}
 }
 
+func TestDecodeMultiProtocolReachNLRI(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name:           "incomplete",
+			input:          []byte{0, 0, 0, 0},
+			wantFail:       true,
+			explicitLength: 32,
+		},
+		{
+			name: "valid MP_REACH_NLRI",
+			input: []byte{
+				0x00, 0x02, // AFI
+				0x01,                                                                                                 // SAFI
+				0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop
+				0x00,                                     // RESERVED
+				0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix
+			},
+			expected: &PathAttribute{
+				Length: 28,
+				Value: MultiProtocolReachNLRI{
+					AFI:     IPv6AFI,
+					SAFI:    UnicastSAFI,
+					NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2),
+					Prefixes: []bnet.Prefix{
+						bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
+					},
+				},
+			},
+		},
+	}
+
+	t.Parallel()
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			l := uint16(len(test.input))
+			if test.explicitLength != 0 {
+				l = test.explicitLength
+			}
+
+			pa := &PathAttribute{
+				Length: l,
+			}
+			err := pa.decodeMultiProtocolReachNLRI(bytes.NewBuffer(test.input))
+
+			if test.wantFail {
+				if err != nil {
+					return
+				}
+				t.Fatalf("Expected error did not happen for test %q", test.name)
+			}
+
+			if err != nil {
+				t.Fatalf("Unexpected failure for test %q: %v", test.name, err)
+			}
+
+			assert.Equal(t, test.expected, pa)
+		})
+	}
+}
+
+func TestDecodeMultiProtocolUnreachNLRI(t *testing.T) {
+	tests := []struct {
+		name           string
+		input          []byte
+		wantFail       bool
+		explicitLength uint16
+		expected       *PathAttribute
+	}{
+		{
+			name:           "incomplete",
+			input:          []byte{0, 0, 0, 0},
+			wantFail:       true,
+			explicitLength: 32,
+		},
+		{
+			name: "valid MP_UNREACH_NLRI",
+			input: []byte{
+				0x00, 0x02, // AFI
+				0x01,                                     // SAFI
+				0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix
+			},
+			expected: &PathAttribute{
+				Length: 10,
+				Value: MultiProtocolUnreachNLRI{
+					AFI:  IPv6AFI,
+					SAFI: UnicastSAFI,
+					Prefixes: []bnet.Prefix{
+						bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
+					},
+				},
+			},
+		},
+	}
+
+	t.Parallel()
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			l := uint16(len(test.input))
+			if test.explicitLength != 0 {
+				l = test.explicitLength
+			}
+
+			pa := &PathAttribute{
+				Length: l,
+			}
+			err := pa.decodeMultiProtocolUnreachNLRI(bytes.NewBuffer(test.input))
+
+			if test.wantFail {
+				if err != nil {
+					return
+				}
+				t.Fatalf("Expected error did not happen for test %q", test.name)
+			}
+
+			if err != nil {
+				t.Fatalf("Unexpected failure for test %q: %v", test.name, err)
+			}
+
+			assert.Equal(t, test.expected, pa)
+		})
+	}
+}
+
 func TestSetLength(t *testing.T) {
 	tests := []struct {
 		name             string
diff --git a/protocols/bgp/server/fsm_established.go b/protocols/bgp/server/fsm_established.go
index b5740c57b9d32ef299d2e783c87b16d06a6e0b14..3c5d96055663e167fc3e451ce03bed2340c757b7 100644
--- a/protocols/bgp/server/fsm_established.go
+++ b/protocols/bgp/server/fsm_established.go
@@ -69,6 +69,10 @@ func (s *establishedState) init() error {
 	}
 	hostIP := net.ParseIP(host)
 	if hostIP == nil {
+		return fmt.Errorf("Unable to parse address")
+	}
+	localAddr, err := bnet.IPFromBytes(hostIP)
+	if err != nil {
 		return fmt.Errorf("Unable to parse address: %v", err)
 	}
 
@@ -78,7 +82,7 @@ func (s *establishedState) init() error {
 		IBGP:              s.fsm.peer.localASN == s.fsm.peer.peerASN,
 		LocalASN:          s.fsm.peer.localASN,
 		RouteServerClient: s.fsm.peer.routeServerClient,
-		LocalAddress:      bnet.IPv4(bnet.IPv4ToUint32(hostIP)),
+		LocalAddress:      localAddr,
 		CapAddPathRX:      s.fsm.options.AddPathRX,
 	}
 
@@ -201,6 +205,7 @@ func (s *establishedState) update(msg *packet.BGPMessage) (state, string) {
 	u := msg.Body.(*packet.BGPUpdate)
 	s.withdraws(u)
 	s.updates(u)
+	s.multiProtocolUpdates(u)
 
 	return newEstablishedState(s.fsm), s.fsm.reason
 }
@@ -216,20 +221,53 @@ func (s *establishedState) updates(u *packet.BGPUpdate) {
 	for r := u.NLRI; r != nil; r = r.Next {
 		pfx := bnet.NewPfx(bnet.IPv4(r.IP), r.Pfxlen)
 
-		path := &route.Path{
-			Type: route.BGPPathType,
-			BGPPath: &route.BGPPath{
-				Source: s.fsm.peer.addr,
-				EBGP:   s.fsm.peer.localASN != s.fsm.peer.peerASN,
-			},
+		path := s.newRoutePath()
+		s.processAttributes(u.PathAttributes, path)
+
+		s.fsm.adjRIBIn.AddPath(pfx, path)
+	}
+}
+
+func (s *establishedState) multiProtocolUpdates(u *packet.BGPUpdate) {
+	if !s.fsm.options.SupportsMultiProtocol {
+		return
+	}
+
+	path := s.newRoutePath()
+	s.processAttributes(u.PathAttributes, path)
+
+	for pa := u.PathAttributes; pa != nil; pa = pa.Next {
+		switch pa.TypeCode {
+		case packet.MultiProtocolReachNLRICode:
+			s.multiProtocolUpdate(path, pa.Value.(packet.MultiProtocolReachNLRI))
+		case packet.MultiProtocolUnreachNLRICode:
+			s.multiProtocolWithdraw(path, pa.Value.(packet.MultiProtocolUnreachNLRI))
 		}
+	}
+}
 
-		s.processAttributes(u.PathAttributes, path)
+func (s *establishedState) newRoutePath() *route.Path {
+	return &route.Path{
+		Type: route.BGPPathType,
+		BGPPath: &route.BGPPath{
+			Source: s.fsm.peer.addr,
+			EBGP:   s.fsm.peer.localASN != s.fsm.peer.peerASN,
+		},
+	}
+}
 
+func (s *establishedState) multiProtocolUpdate(path *route.Path, nlri packet.MultiProtocolReachNLRI) {
+	for _, pfx := range nlri.Prefixes {
 		s.fsm.adjRIBIn.AddPath(pfx, path)
 	}
 }
 
+func (s *establishedState) multiProtocolWithdraw(path *route.Path, nlri packet.MultiProtocolUnreachNLRI) {
+	for _, pfx := range nlri.Prefixes {
+		s.fsm.adjRIBIn.RemovePath(pfx, path)
+	}
+}
+
 func (s *establishedState) processAttributes(attrs *packet.PathAttribute, path *route.Path) {
 	for pa := attrs; pa != nil; pa = pa.Next {
 		switch pa.TypeCode {
diff --git a/protocols/bgp/server/fsm_open_sent.go b/protocols/bgp/server/fsm_open_sent.go
index 928ed450b7b2a4cb230acaca5db418af80923c11..3ece04475f645340a60e814e3452110ce56ab5ff 100644
--- a/protocols/bgp/server/fsm_open_sent.go
+++ b/protocols/bgp/server/fsm_open_sent.go
@@ -169,9 +169,15 @@ func (s *openSentState) processCapability(cap packet.Capability) {
 		s.processAddPathCapability(cap.Value.(packet.AddPathCapability))
 	case packet.ASN4CapabilityCode:
 		s.processASN4Capability(cap.Value.(packet.ASN4Capability))
+	case packet.MultiProtocolCapabilityCode:
+		s.processMultiProtocolCapability(cap.Value.(packet.MultiProtocolCapability))
 	}
 }
 
+func (s *openSentState) processMultiProtocolCapability(cap packet.MultiProtocolCapability) {
+	s.fsm.options.SupportsMultiProtocol = true
+}
+
 func (s *openSentState) processAddPathCapability(addPathCap packet.AddPathCapability) {
 	if addPathCap.AFI != 1 {
 		return
diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go
index 2772903061d6180c6d49ff48c70e3f96af4dfc33..638d8b0314c9b06fa8c65c7bcee24a117ee69388 100644
--- a/protocols/bgp/server/peer.go
+++ b/protocols/bgp/server/peer.go
@@ -124,7 +124,7 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error
 	}
 	p.fsms = append(p.fsms, NewActiveFSM2(p))
 
-	caps := make([]packet.Capability, 0)
+	caps := make(packet.Capabilities, 0)
 
 	addPathEnabled, addPathCap := handleAddPathCapability(c)
 	if addPathEnabled {
@@ -133,13 +133,15 @@ func newPeer(c config.Peer, rib *locRIB.LocRIB, server *bgpServer) (*peer, error
 
 	caps = append(caps, asn4Capability(c))
 
-	for _, cap := range caps {
-		p.optOpenParams = append(p.optOpenParams, packet.OptParam{
-			Type:  packet.CapabilitiesParamType,
-			Value: cap,
-		})
+	if c.IPv6 {
+		caps = append(caps, multiProtocolCapability(packet.IPv6AFI))
 	}
 
+	p.optOpenParams = append(p.optOpenParams, packet.OptParam{
+		Type:  packet.CapabilitiesParamType,
+		Value: caps,
+	})
+
 	return p, nil
 }
 
@@ -152,6 +154,16 @@ func asn4Capability(c config.Peer) packet.Capability {
 	}
 }
 
+func multiProtocolCapability(afi uint16) packet.Capability {
+	return packet.Capability{
+		Code: packet.MultiProtocolCapabilityCode,
+		Value: packet.MultiProtocolCapability{
+			AFI:  afi,
+			SAFI: packet.UnicastSAFI,
+		},
+	}
+}
+
 func handleAddPathCapability(c config.Peer) (bool, packet.Capability) {
 	addPath := uint8(0)
 	if c.AddPathRecv {
diff --git a/protocols/bgp/types/options.go b/protocols/bgp/types/options.go
index 19b580d2829b96d8f974311536b299211a98ca95..8b96d6697b7262b53954a12663dff872fd3492b2 100644
--- a/protocols/bgp/types/options.go
+++ b/protocols/bgp/types/options.go
@@ -2,6 +2,7 @@ package types
 
 // Options represents options to the update sender, decoder and encoder
 type Options struct {
-	Supports4OctetASN bool
-	AddPathRX         bool
+	Supports4OctetASN     bool
+	SupportsMultiProtocol bool
+	AddPathRX             bool
 }