Skip to content

add inside/outside hot path benchmarks #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions inside_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// +build linux

package nebula

import (
"encoding/hex"
"fmt"
"net"
"testing"
"time"

"github.com/flynn/noise"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/cert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)

func BenchmarkInsideHotPath(b *testing.B) {
l.SetLevel(logrus.WarnLevel)
header, _ := hex.DecodeString(
// IP packet, 192.168.0.120 -> 192.168.0.1
// UDP packet, port 52228 -> 9999
// body: all zeros, total length 1500
"450005dc75ad400040113d9ac0a80078c0a80001" + "cc04270f05c87f80",
)

packet := make([]byte, mtu)
copy(packet[0:], header)

fwPacket := &FirewallPacket{}

out := make([]byte, mtu)
nb := make([]byte, 12, 12)

myIp, myNet, _ := net.ParseCIDR("192.168.0.120/24")
myIpNet := &net.IPNet{
IP: myIp,
Mask: myNet.Mask,
}
_, localToMe, _ := net.ParseCIDR("10.0.0.1/8")
myIpNets := []*net.IPNet{myIpNet}
preferredRanges := []*net.IPNet{localToMe}

c := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "host1",
Ips: myIpNets,
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group": {}},
},
}

fw := NewFirewall(time.Second, time.Minute, time.Hour, &c)
require.NoError(b, fw.AddRule(false, fwProtoAny, 0, 0, []string{"any"}, "", nil, "", ""))

// We have to actually create a udp socket, since udpServer is not an interface
udpServer, err := NewListener("127.0.0.1", 0, false)
require.NoError(b, err)

// We aren't going to read, so set the smallest recv buffer size so everything gets dropped
require.NoError(b, unix.SetsockoptInt(udpServer.sysFd, unix.SOL_SOCKET, unix.SO_RCVBUF, 0))

uPort, err := udpServer.LocalAddr()
require.NoError(b, err)

hostMap := NewHostMap("main", myIpNet, preferredRanges)
// TODO should we send to port 9 (discard protocol) instead of ourselves?
// Sending to :9 seems to slow down the test since another service on the
// box has to recv the messages. If we just send to ourselves, the packets
// just fill the buffer and get thrown away.
hostMap.AddRemote(ip2int(net.ParseIP("192.168.0.1")), NewUDPAddrFromString(fmt.Sprintf("127.0.0.1:%d", uPort.Port)))
info, _ := hostMap.QueryVpnIP(ip2int(net.ParseIP("192.168.0.1")))
var mc uint64
info.ConnectionState = &ConnectionState{
ready: true,
messageCounter: &mc,
}
info.HandshakeReady = true

ifce := &Interface{
hostMap: hostMap,
firewall: fw,
lightHouse: &LightHouse{},
outside: udpServer,
}
ifce.connectionManager = newConnectionManager(ifce, 300, 300)

packet = packet[:1500]

b.Run("AESGCM", func(b *testing.B) {
info.ConnectionState.eKey = testHotPathCipherState(b, noise.CipherAESGCM)

// Prep the hot path, add to conntrack
ifce.consumeInsidePacket(packet, fwPacket, nb, out)

b.ResetTimer()
for n := 0; n < b.N; n++ {
ifce.consumeInsidePacket(packet, fwPacket, nb, out)
}
b.SetBytes(1500)
})
b.Run("ChaChaPoly", func(b *testing.B) {
info.ConnectionState.eKey = testHotPathCipherState(b, noise.CipherChaChaPoly)

// Prep the hot path, add to conntrack
ifce.consumeInsidePacket(packet, fwPacket, nb, out)

b.ResetTimer()
for n := 0; n < b.N; n++ {
ifce.consumeInsidePacket(packet, fwPacket, nb, out)
}
b.SetBytes(1500)
})
}
147 changes: 147 additions & 0 deletions outside_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package nebula

import (
"crypto/rand"
"encoding/hex"
"net"
"testing"
"time"

"github.com/flynn/noise"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/cert"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/ipv4"
)

Expand Down Expand Up @@ -79,3 +86,143 @@ func Test_newPacket(t *testing.T) {
assert.Equal(t, p.RemotePort, uint16(6))
assert.Equal(t, p.LocalPort, uint16(5))
}

func BenchmarkOutsideHotPath(b *testing.B) {
l.SetLevel(logrus.WarnLevel)
pHeader, _ := hex.DecodeString(
// IP packet, 192.168.0.120 -> 192.168.0.1
// UDP packet, port 52228 -> 9999
// body: all zeros, total length 1500
"450005dc75ad400040113d9ac0a80078c0a80001" + "cc04270f05c87f80",
)

packet := make([]byte, mtu)

copy(packet[0:], pHeader)
myIp, myNet, _ := net.ParseCIDR("192.168.0.1/24")
myIpNet := &net.IPNet{
IP: myIp,
Mask: myNet.Mask,
}
_, localToMe, _ := net.ParseCIDR("10.0.0.1/8")
myIpNets := []*net.IPNet{myIpNet}
preferredRanges := []*net.IPNet{localToMe}

c := cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "host1",
Ips: myIpNets,
InvertedGroups: map[string]struct{}{"default-group": {}, "test-group": {}},
},
}
fw := NewFirewall(time.Second, time.Minute, time.Hour, &c)
require.NoError(b, fw.AddRule(true, fwProtoAny, 0, 0, []string{"any"}, "", nil, "", ""))

hostMap := NewHostMap("main", myIpNet, preferredRanges)
hostMap.AddRemote(ip2int(net.ParseIP("192.168.0.120")), NewUDPAddrFromString("127.0.0.1:9"))
info, _ := hostMap.QueryVpnIP(ip2int(net.ParseIP("192.168.0.120")))
var mc uint64
mc = 1
info.ConnectionState = &ConnectionState{
ready: true,
messageCounter: &mc,
window: NewBits(ReplayWindow),
}
info.HandshakeReady = true
// Clear out bit 0, we never transmit it and we don't want it showing as packet loss
info.ConnectionState.window.Update(0)

tun := &dropTun{}

hostMap.AddIndexHostInfo(info.remoteIndexId, info)
ifce := &Interface{
hostMap: hostMap,
firewall: fw,
lightHouse: &LightHouse{},
inside: tun,
}
ifce.connectionManager = newConnectionManager(ifce, 300, 300)

udpAddr := NewUDPAddrFromString("127.0.0.1:9")
plaintext := make([]byte, mtu)
buffer := make([]byte, mtu)
header := &Header{}
fwPacket := &FirewallPacket{}
nb := make([]byte, 12, 12)

b.Run("AESGCM", func(b *testing.B) {
eKey := testHotPathCipherState(b, noise.CipherAESGCM)
info.ConnectionState.dKey = eKey
info.ConnectionState.window.current = 0

var err error

// Encrypt the test payload
buffer = HeaderEncode(buffer, Version, uint8(message), 0, info.remoteIndexId, 1)
buffer, err = eKey.EncryptDanger(buffer, buffer, packet[:1500], 1, nb)
require.NoError(b, err)

// Prep the hot path, add to conntrack
ifce.readOutsidePackets(udpAddr, plaintext[:0], buffer, header, fwPacket, nb)

b.ResetTimer()
for n := 0; n < b.N; n++ {
info.ConnectionState.window.current = 0
ifce.readOutsidePackets(udpAddr, plaintext[:0], buffer, header, fwPacket, nb)
}
b.SetBytes(1500)
})

b.Run("ChaChaPoly", func(b *testing.B) {
eKey := testHotPathCipherState(b, noise.CipherChaChaPoly)
info.ConnectionState.dKey = eKey
info.ConnectionState.window.current = 0

var err error

// Encrypt the test payload
buffer = HeaderEncode(buffer, Version, uint8(message), 0, info.remoteIndexId, 1)
buffer, err = eKey.EncryptDanger(buffer, buffer, packet[:1500], 1, nb)
require.NoError(b, err)

// Prep the hot path, add to conntrack
ifce.readOutsidePackets(udpAddr, plaintext[:0], buffer, header, fwPacket, nb)

b.ResetTimer()
for n := 0; n < b.N; n++ {
info.ConnectionState.window.current = 0
ifce.readOutsidePackets(udpAddr, plaintext[:0], buffer, header, fwPacket, nb)
}
b.SetBytes(1500)
})
}

// Drop all outgoing packets, for Benchmark test
type dropTun struct{}

func (dropTun) Read(p []byte) (n int, err error) { return 0, nil }
func (dropTun) Write(p []byte) (n int, err error) { return len(p), nil }
func (dropTun) Close() error { return nil }
func (dropTun) Activate() error { return nil }
func (dropTun) CidrNet() *net.IPNet { return nil }
func (dropTun) DeviceName() string { return "dropTun" }
func (dropTun) WriteRaw([]byte) error { return nil }

func testHotPathCipherState(t testing.TB, c noise.CipherFunc) *NebulaCipherState {
cs := noise.NewCipherSuite(noise.DH25519, c, noise.HashSHA256)
rng := rand.Reader
staticR, _ := cs.GenerateKeypair(rng)
hs, err := noise.NewHandshakeState(noise.Config{
CipherSuite: cs,
Random: rng,
Pattern: noise.HandshakeN,
Initiator: true,
PeerStatic: staticR.Public,
})
require.NoError(t, err)

_, eKey, _, err := hs.WriteMessage(nil, nil)
require.NoError(t, err)

return NewNebulaCipherState(eKey)
}