Skip to content

Commit

Permalink
apps.ipsec: add tunnel mode esp app
Browse files Browse the repository at this point in the history
This adds a simple ESP 6in6 tunnel app (Tunnel6) to apps.ipsec.esp, and also
renames the transport mode app (AES18GCM -> Transport6) to match the naming.

The apps.ipsec Linux interoperability test is extended to exercise tunnel mode
as well.
  • Loading branch information
eugeneia committed Mar 29, 2018
1 parent 46e0e4a commit 59b1b82
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 137 deletions.
49 changes: 31 additions & 18 deletions src/apps/ipsec/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# IPsec Apps

## AES128gcm (apps.ipsec.esp)

The `AES128gcm` implements ESP in transport mode using the AES-GCM-128
cipher. It encrypts packets received on its `decapsulated` port and transmits
them on its `encapsulated` port, and vice-versa. Packets arriving on the
`decapsulated` port must have an IPv6 header, and packets arriving on the
`encapsulated` port must have an IPv6 header followed by an ESP header,
otherwise they will be discarded.

DIAGRAM: AES128gcm
+-----------+
encapsulated | |
---->* AES128gcm *<----
<----* *---->
| | decapsulated
+-----------+
## ESP Transport6 and Tunnel6 (apps.ipsec.esp)

The `Transport6` and `Tunnel6` apps implement ESP in transport and tunnel mode
respectively. they encrypts packets received on their `decapsulated` port and
transmit them on their `encapsulated` port, and vice-versa. Packets arriving on
the `decapsulated` port must have Ethernet and IPv6 headers, and packets
arriving on the `encapsulated` port must have an Ethernet and IPv6 headers
followed by an ESP header, otherwise they will be discarded.

DIAGRAM: Transport6
+------------+
encapsulated | |
---->* Transport6 *<----
<----* Tunnel6 *---->
| | decapsulated
+------------+

encapsulated
--------\ /----------
Expand All @@ -28,8 +28,21 @@ References:

### Configuration

The `AES128gcm` app accepts a table as its configuration argument. The
following keys are defined:
The `Transport6` and `Tunnel6` apps accepts a table as its configuration
argument. The following keys are defined:

— Key **self_ip** (`Tunnel6` only)

*Required*. Source address of the encapsulating IPv6 header.

— Key **nexthop_ip** (`Tunnel6` only)

*Required*. Destination address of the encapsulating IPv6 header.

— Key **aead**

*Optional*. The AEAD to use for encryption and authentication. For now, only
the default `"aes-gcm-128-12"` is supported.

— Key **spi**

Expand Down
145 changes: 138 additions & 7 deletions src/apps/ipsec/esp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
module(..., package.seeall)
local esp = require("lib.ipsec.esp")
local counter = require("core.counter")
local C = require("ffi").C
local ethernet = require("lib.protocol.ethernet")
local ipv6 = require("lib.protocol.ipv6")

AES128gcm = {
Transport6 = {
config = {
spi = {required=true},
aead = {default="aes-gcm-128-12"},
transmit_key = {required=true},
transmit_salt = {required=true},
receive_key = {required=true},
Expand All @@ -25,28 +27,28 @@ AES128gcm = {
}
}

function AES128gcm:new (conf)
function Transport6:new (conf)
local self = {}
assert(conf.transmit_salt ~= conf.receive_salt,
"Refusing to operate with transmit_salt == receive_salt")
self.encrypt = esp.encrypt:new{
mode = "aes-gcm-128-12",
mode = conf.aead,
spi = conf.spi,
key = conf.transmit_key,
salt = conf.transmit_salt}
self.decrypt = esp.decrypt:new{
mode = "aes-gcm-128-12",
mode = conf.aead,
spi = conf.spi,
key = conf.receive_key,
salt = conf.receive_salt,
window_size = conf.receive_window,
resync_threshold = conf.resync_threshold,
resync_attempts = conf.resync_attempts,
auditing = conf.auditing}
return setmetatable(self, {__index = AES128gcm})
return setmetatable(self, {__index = Transport6})
end

function AES128gcm:push ()
function Transport6:push ()
-- Encapsulation path
local input = self.input.decapsulated
local output = self.output.encapsulated
Expand Down Expand Up @@ -74,3 +76,132 @@ function AES128gcm:push ()
end
end
end

Tunnel6 = {
config = {
self_ip = {required=true},
nexthop_ip = {required=true},
spi = {required=true},
aead = {default="aes-gcm-128-12"},
transmit_key = {required=true},
transmit_salt = {required=true},
receive_key = {required=true},
receive_salt = {required=true},
receive_window = {},
resync_threshold = {},
resync_attempts = {},
auditing = {},
selftest = {default=false}
},
shm = {
txerrors = {counter}, rxerrors = {counter}
},
-- https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
NextHeaderIPv6 = 41
}

function Tunnel6:new (conf)
local self = {}
assert(conf.selftest or conf.transmit_salt ~= conf.receive_salt,
"Refusing to operate with transmit_salt == receive_salt")
self.encrypt = esp.encrypt:new{
mode = conf.aead,
spi = conf.spi,
key = conf.transmit_key,
salt = conf.transmit_salt
}
self.decrypt = esp.decrypt:new{
mode = conf.aead,
spi = conf.spi,
key = conf.receive_key,
salt = conf.receive_salt,
window_size = conf.receive_window,
resync_threshold = conf.resync_threshold,
resync_attempts = conf.resync_attempts,
auditing = conf.auditing
}
self.eth = ethernet:new{
type = 0x86dd -- IPv6
}
self.ip = ipv6:new{
src = ipv6:pton(conf.self_ip),
dst = ipv6:pton(conf.nexthop_ip),
next_header = esp.PROTOCOL,
hop_limit = 64
}
return setmetatable(self, {__index = Tunnel6})
end

function Tunnel6:push ()
-- Encapsulation path
local input = self.input.decapsulated
local output = self.output.encapsulated
while not link.empty(input) do
local p = link.receive(input)
if p.length >= ethernet:sizeof() then
-- Strip Ethernet header
p = packet.shiftleft(p, ethernet:sizeof())
-- Encrypt payload
local p_enc = self.encrypt:encapsulate_tunnel(p, self.NextHeaderIPv6)
-- Slap on IPv6 and Ethernet headers
self.ip:payload_length(p_enc.length)
p_enc = packet.prepend(p_enc, self.ip:header(), ipv6:sizeof())
p_enc = packet.prepend(p_enc, self.eth:header(), ethernet:sizeof())
link.transmit(output, p_enc)
else
packet.free(p)
counter.add(self.shm.txerrors)
end
end
-- Decapsulation path
local input = self.input.encapsulated
local output = self.output.decapsulated
while not link.empty(input) do
local p = link.receive(input)
if p.length >= ethernet:sizeof() + ipv6:sizeof() then
-- Strip Ethernet and IPv6 headers
p = packet.shiftleft(p, ethernet:sizeof() + ipv6:sizeof())
-- Decrypt payload
local p_dec, nh = self.decrypt:decapsulate_tunnel(p)
if p_dec and nh == self.NextHeaderIPv6 then
-- Slap on new Ethernet header
p_dec = packet.prepend(p_dec, self.eth:header(), ethernet:sizeof())
link.transmit(output, p_dec)
goto next
end
end
-- Handle error
packet.free(p)
counter.add(self.shm.rxerrors)
::next::
end
end

function selftest ()
-- Only testing Tunnel6 because Transport6 is mostly covered in the selftest
-- of lib.ipsec.esp.
local basic_apps = require("apps.basic.basic_apps")
local c = config.new()
config.app(c, "source", basic_apps.Source)
config.app(c, "sink", basic_apps.Sink)
config.app(c, "tunnel", Tunnel6, {
self_ip = "fc00::1",
nexthop_ip = "fc00::2",
spi = 0xdeadbeef,
transmit_key = "00112233445566778899AABBCCDDEEFF",
transmit_salt = "00112233",
receive_key = "00112233445566778899AABBCCDDEEFF",
receive_salt = "00112233",
auditing = true,
selftest = true
})
config.link(c, "source.output -> tunnel.decapsulated")
config.link(c, "tunnel.encapsulated -> tunnel.encapsulated")
config.link(c, "tunnel.decapsulated -> sink.input")
engine.configure(c)
engine.main{duration=0.0001}
engine.report_links()
assert(counter.read(engine.app_table.tunnel.shm.rxerrors) == 0,
"Decapsulation error!")
print("OK")
end
81 changes: 6 additions & 75 deletions src/apps/ipsec/selftest.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env bash
#set -x
set -e

SKIPPED_CODE=43

# Requires test_env with Linux guest featuring ipsec/ESN support.
if [ "$SNABB_IPSEC_SKIP_E2E_TEST" = yes ]; then
exit $SKIPPED_CODE
fi
Expand All @@ -12,77 +13,7 @@ if [ -z "$SNABB_TELNET0" ]; then
echo "Defaulting to SNABB_TELNET0=$SNABB_TELNET0"
fi

SPI=2953575118

SRC=fc00:feed:face:dead::1
DST=fc00:feed:face:dead::2

IP=$DST
MAC=52:54:01:00:00:

TKEY=d4d61fec2861b3b806d0654eeea02ede
TSALT=df3ddb99
TKS=$TKEY$TSALT

RKEY=c4d61fec2861b3b806d0654eeea02ede
RSALT=cf3ddb99
RKS=$RKEY$RSALT

SPORT=60122
DPORT=60123

if ! source program/snabbnfv/test_env/test_env.sh; then
echo "Could not load test_env."; exit 1
fi

./snabb snsh apps/ipsec/selftest.lua ${MAC}01 ${MAC}00 $SRC $DST $SPORT $DPORT $SPI $TKEY $TSALT $RKEY $RSALT 3 ping &
snabb_pid=$!

if ! qemu soft esp.sock $SNABB_TELNET0; then
echo "Could not start qemu."; exit $SKIPPED_CODE
fi

wait_vm_up $SNABB_TELNET0
run_telnet $SNABB_TELNET0 "systemctl stop dhcpcd.service &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ifconfig eth0 down &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ip -6 neigh flush dev eth0 &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ip -6 neigh add $SRC lladdr ${MAC}01 dev eth0 &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ip -6 addr add $DST/7 dev eth0 &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ifconfig eth0 up &>/dev/console" >/dev/null
run_telnet $SNABB_TELNET0 "ncat -lkuc cat $DPORT &>/dev/console &" >/dev/null


#---------------------------------------------------------

# |-------------key--------------||-SALT-|
#KEY=0x`dd if=/dev/urandom count=32 bs=1 2> /dev/null| xxd -p -c 64`
SPI_ID=0xb00bface
#SPI_ID=0x`dd if=/dev/urandom count=4 bs=1 2> /dev/null| xxd -p -c 8`

SPISTR="spi $SPI_ID"
PROTO="proto esp"

SD_FWD="src $SRC dst $DST"
SD_REV="src $DST dst $SRC"

SDP_FWD="$SD_FWD $PROTO"
SDP_REV="$SD_REV $PROTO"

ID_FWD="$SDP_FWD $SPISTR"
ID_REV="$SDP_REV $SPISTR"

MODE="mode transport"
REPLAY="replay-window 128"
FLAG="flag esn"
RALGO="aead rfc4106\(gcm\(aes\)\) 0x$RKS 96"
TALGO="aead rfc4106\(gcm\(aes\)\) 0x$TKS 96"

cmd="echo 'spdflush; flush;' | setkey -c"
cmd="$cmd; ip xfrm state add $ID_FWD $MODE $REPLAY $FLAG $TALGO"
cmd="$cmd; ip xfrm state add $ID_REV $MODE $REPLAY $FLAG $RALGO "
cmd="$cmd; ip xfrm policy add $SD_REV dir out tmpl $SDP_REV $MODE"
cmd="$cmd; ip xfrm policy add $SD_FWD dir in tmpl $SDP_FWD $MODE"
run_telnet $SNABB_TELNET0 "$cmd &>/dev/console" >/dev/null
#---------------------------------------------------------

wait $snabb_pid
echo "Probing a Linux guest through ESP in transport mode..."
apps/ipsec/test-linux-compat.sh transport
echo "Probing a Linux guest through ESP in tunnel mode..."
apps/ipsec/test-linux-compat.sh tunnel
Loading

0 comments on commit 59b1b82

Please sign in to comment.