You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
905 lines
37 KiB
905 lines
37 KiB
#!/usr/bin/python
|
|
#
|
|
# Copyright 2017 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# pylint: disable=g-bad-todo,g-bad-file-header,wildcard-import
|
|
from errno import * # pylint: disable=wildcard-import
|
|
from scapy import all as scapy
|
|
from socket import * # pylint: disable=wildcard-import
|
|
import struct
|
|
import subprocess
|
|
import threading
|
|
import unittest
|
|
|
|
import csocket
|
|
import cstruct
|
|
import multinetwork_base
|
|
import net_test
|
|
import packets
|
|
import xfrm
|
|
import xfrm_base
|
|
|
|
ENCRYPTED_PAYLOAD = ("b1c74998efd6326faebe2061f00f2c750e90e76001664a80c287b150"
|
|
"59e74bf949769cc6af71e51b539e7de3a2a14cb05a231b969e035174"
|
|
"d98c5aa0cef1937db98889ec0d08fa408fecf616")
|
|
|
|
TEST_ADDR1 = "2001:4860:4860::8888"
|
|
TEST_ADDR2 = "2001:4860:4860::8844"
|
|
|
|
XFRM_STATS_PROCFILE = "/proc/net/xfrm_stat"
|
|
XFRM_STATS_OUT_NO_STATES = "XfrmOutNoStates"
|
|
|
|
# IP addresses to use for tunnel endpoints. For generality, these should be
|
|
# different from the addresses we send packets to.
|
|
TUNNEL_ENDPOINTS = {4: "8.8.4.4", 6: TEST_ADDR2}
|
|
|
|
TEST_SPI = 0x1234
|
|
TEST_SPI2 = 0x1235
|
|
|
|
|
|
|
|
class XfrmFunctionalTest(xfrm_base.XfrmLazyTest):
|
|
|
|
def assertIsUdpEncapEsp(self, packet, spi, seq, length):
|
|
self.assertEqual(IPPROTO_UDP, packet.proto)
|
|
udp_hdr = packet[scapy.UDP]
|
|
self.assertEqual(4500, udp_hdr.dport)
|
|
self.assertEqual(length, len(udp_hdr))
|
|
esp_hdr, _ = cstruct.Read(str(udp_hdr.payload), xfrm.EspHdr)
|
|
# FIXME: this file currently swaps SPI byte order manually, so SPI needs to
|
|
# be double-swapped here.
|
|
self.assertEqual(xfrm.EspHdr((spi, seq)), esp_hdr)
|
|
|
|
def CreateNewSa(self, localAddr, remoteAddr, spi, reqId, encap_tmpl,
|
|
null_auth=False):
|
|
auth_algo = (
|
|
xfrm_base._ALGO_AUTH_NULL if null_auth else xfrm_base._ALGO_HMAC_SHA1)
|
|
self.xfrm.AddSaInfo(localAddr, remoteAddr, spi, xfrm.XFRM_MODE_TRANSPORT,
|
|
reqId, xfrm_base._ALGO_CBC_AES_256, auth_algo, None,
|
|
encap_tmpl, None, None)
|
|
|
|
def testAddSa(self):
|
|
self.CreateNewSa("::", TEST_ADDR1, TEST_SPI, 3320, None)
|
|
expected = (
|
|
"src :: dst 2001:4860:4860::8888\n"
|
|
"\tproto esp spi 0x00001234 reqid 3320 mode transport\n"
|
|
"\treplay-window 4 \n"
|
|
"\tauth-trunc hmac(sha1) 0x%s 96\n"
|
|
"\tenc cbc(aes) 0x%s\n"
|
|
"\tsel src ::/0 dst ::/0 \n" % (
|
|
xfrm_base._AUTHENTICATION_KEY_128.encode("hex"),
|
|
xfrm_base._ENCRYPTION_KEY_256.encode("hex")))
|
|
|
|
actual = subprocess.check_output("ip xfrm state".split())
|
|
# Newer versions of IP also show anti-replay context. Don't choke if it's
|
|
# missing.
|
|
actual = actual.replace(
|
|
"\tanti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000\n", "")
|
|
try:
|
|
self.assertMultiLineEqual(expected, actual)
|
|
finally:
|
|
self.xfrm.DeleteSaInfo(TEST_ADDR1, TEST_SPI, IPPROTO_ESP)
|
|
|
|
def testFlush(self):
|
|
self.assertEqual(0, len(self.xfrm.DumpSaInfo()))
|
|
self.CreateNewSa("::", "2000::", TEST_SPI, 1234, None)
|
|
self.CreateNewSa("0.0.0.0", "192.0.2.1", TEST_SPI, 4321, None)
|
|
self.assertEqual(2, len(self.xfrm.DumpSaInfo()))
|
|
self.xfrm.FlushSaInfo()
|
|
self.assertEqual(0, len(self.xfrm.DumpSaInfo()))
|
|
|
|
def _TestSocketPolicy(self, version):
|
|
# Open a UDP socket and connect it.
|
|
family = net_test.GetAddressFamily(version)
|
|
s = socket(family, SOCK_DGRAM, 0)
|
|
netid = self.RandomNetid()
|
|
self.SelectInterface(s, netid, "mark")
|
|
|
|
remotesockaddr = self.GetRemoteSocketAddress(version)
|
|
s.connect((remotesockaddr, 53))
|
|
saddr, sport = s.getsockname()[:2]
|
|
daddr, dport = s.getpeername()[:2]
|
|
if version == 5:
|
|
saddr = saddr.replace("::ffff:", "")
|
|
daddr = daddr.replace("::ffff:", "")
|
|
|
|
reqid = 0
|
|
|
|
desc, pkt = packets.UDP(version, saddr, daddr, sport=sport)
|
|
s.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
self.ExpectPacketOn(netid, "Send after socket, expected %s" % desc, pkt)
|
|
|
|
# Using IPv4 XFRM on a dual-stack socket requires setting an AF_INET policy
|
|
# that's written in terms of IPv4 addresses.
|
|
xfrm_version = 4 if version == 5 else version
|
|
xfrm_family = net_test.GetAddressFamily(xfrm_version)
|
|
xfrm_base.ApplySocketPolicy(s, xfrm_family, xfrm.XFRM_POLICY_OUT,
|
|
TEST_SPI, reqid, None)
|
|
|
|
# Because the policy has level set to "require" (the default), attempting
|
|
# to send a packet results in an error, because there is no SA that
|
|
# matches the socket policy we set.
|
|
self.assertRaisesErrno(
|
|
EAGAIN,
|
|
s.sendto, net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
|
|
# If there is a user space key manager, calling sendto() after applying the socket policy
|
|
# creates an SA whose state is XFRM_STATE_ACQ. So this just deletes it.
|
|
# If there is no user space key manager, deleting SA returns ESRCH as the error code.
|
|
try:
|
|
self.xfrm.DeleteSaInfo(self.GetRemoteAddress(xfrm_version), TEST_SPI, IPPROTO_ESP)
|
|
except IOError as e:
|
|
self.assertEqual(ESRCH, e.errno, "Unexpected error when deleting ACQ SA")
|
|
|
|
# Adding a matching SA causes the packet to go out encrypted. The SA's
|
|
# SPI must match the one in our template, and the destination address must
|
|
# match the packet's destination address (in tunnel mode, it has to match
|
|
# the tunnel destination).
|
|
self.CreateNewSa(
|
|
net_test.GetWildcardAddress(xfrm_version),
|
|
self.GetRemoteAddress(xfrm_version), TEST_SPI, reqid, None)
|
|
|
|
s.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
expected_length = xfrm_base.GetEspPacketLength(xfrm.XFRM_MODE_TRANSPORT,
|
|
version, False,
|
|
net_test.UDP_PAYLOAD,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
xfrm_base._ALGO_CBC_AES_256)
|
|
self._ExpectEspPacketOn(netid, TEST_SPI, 1, expected_length, None, None)
|
|
|
|
# Sending to another destination doesn't work: again, no matching SA.
|
|
remoteaddr2 = self.GetOtherRemoteSocketAddress(version)
|
|
self.assertRaisesErrno(
|
|
EAGAIN,
|
|
s.sendto, net_test.UDP_PAYLOAD, (remoteaddr2, 53))
|
|
|
|
# Sending on another socket without the policy applied results in an
|
|
# unencrypted packet going out.
|
|
s2 = socket(family, SOCK_DGRAM, 0)
|
|
self.SelectInterface(s2, netid, "mark")
|
|
s2.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
pkts = self.ReadAllPacketsOn(netid)
|
|
self.assertEqual(1, len(pkts))
|
|
packet = pkts[0]
|
|
|
|
protocol = packet.nh if version == 6 else packet.proto
|
|
self.assertEqual(IPPROTO_UDP, protocol)
|
|
|
|
# Deleting the SA causes the first socket to return errors again.
|
|
self.xfrm.DeleteSaInfo(self.GetRemoteAddress(xfrm_version), TEST_SPI,
|
|
IPPROTO_ESP)
|
|
self.assertRaisesErrno(
|
|
EAGAIN,
|
|
s.sendto, net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
|
|
# Clear the socket policy and expect a cleartext packet.
|
|
xfrm_base.SetPolicySockopt(s, family, None)
|
|
s.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
self.ExpectPacketOn(netid, "Send after clear, expected %s" % desc, pkt)
|
|
|
|
# Clearing the policy twice is safe.
|
|
xfrm_base.SetPolicySockopt(s, family, None)
|
|
s.sendto(net_test.UDP_PAYLOAD, (remotesockaddr, 53))
|
|
self.ExpectPacketOn(netid, "Send after clear 2, expected %s" % desc, pkt)
|
|
|
|
# Clearing if a policy was never set is safe.
|
|
s = socket(AF_INET6, SOCK_DGRAM, 0)
|
|
xfrm_base.SetPolicySockopt(s, family, None)
|
|
|
|
def testSocketPolicyIPv4(self):
|
|
self._TestSocketPolicy(4)
|
|
|
|
def testSocketPolicyIPv6(self):
|
|
self._TestSocketPolicy(6)
|
|
|
|
def testSocketPolicyMapped(self):
|
|
self._TestSocketPolicy(5)
|
|
|
|
# Sets up sockets and marks to correct netid
|
|
def _SetupUdpEncapSockets(self):
|
|
netid = self.RandomNetid()
|
|
myaddr = self.MyAddress(4, netid)
|
|
remoteaddr = self.GetRemoteAddress(4)
|
|
|
|
# Reserve a port on which to receive UDP encapsulated packets. Sending
|
|
# packets works without this (and potentially can send packets with a source
|
|
# port belonging to another application), but receiving requires the port to
|
|
# be bound and the encapsulation socket option enabled.
|
|
encap_sock = net_test.Socket(AF_INET, SOCK_DGRAM, 0)
|
|
encap_sock.bind((myaddr, 0))
|
|
encap_port = encap_sock.getsockname()[1]
|
|
encap_sock.setsockopt(IPPROTO_UDP, xfrm.UDP_ENCAP, xfrm.UDP_ENCAP_ESPINUDP)
|
|
|
|
# Open a socket to send traffic.
|
|
s = socket(AF_INET, SOCK_DGRAM, 0)
|
|
self.SelectInterface(s, netid, "mark")
|
|
s.connect((remoteaddr, 53))
|
|
|
|
return netid, myaddr, remoteaddr, encap_sock, encap_port, s
|
|
|
|
# Sets up SAs and applies socket policy to given socket
|
|
def _SetupUdpEncapSaPair(self, myaddr, remoteaddr, in_spi, out_spi,
|
|
encap_port, s, use_null_auth):
|
|
in_reqid = 123
|
|
out_reqid = 456
|
|
|
|
# Create inbound and outbound SAs that specify UDP encapsulation.
|
|
encaptmpl = xfrm.XfrmEncapTmpl((xfrm.UDP_ENCAP_ESPINUDP, htons(encap_port),
|
|
htons(4500), 16 * "\x00"))
|
|
self.CreateNewSa(myaddr, remoteaddr, out_spi, out_reqid, encaptmpl,
|
|
use_null_auth)
|
|
|
|
# Add an encap template that's the mirror of the outbound one.
|
|
encaptmpl.sport, encaptmpl.dport = encaptmpl.dport, encaptmpl.sport
|
|
self.CreateNewSa(remoteaddr, myaddr, in_spi, in_reqid, encaptmpl,
|
|
use_null_auth)
|
|
|
|
# Apply socket policies to s.
|
|
xfrm_base.ApplySocketPolicy(s, AF_INET, xfrm.XFRM_POLICY_OUT, out_spi,
|
|
out_reqid, None)
|
|
|
|
# TODO: why does this work without a per-socket policy applied?
|
|
# The received packet obviously matches an SA, but don't inbound packets
|
|
# need to match a policy as well? (b/71541609)
|
|
xfrm_base.ApplySocketPolicy(s, AF_INET, xfrm.XFRM_POLICY_IN, in_spi,
|
|
in_reqid, None)
|
|
|
|
# Uncomment for debugging.
|
|
# subprocess.call("ip xfrm state".split())
|
|
|
|
# Check that packets can be sent and received.
|
|
def _VerifyUdpEncapSocket(self, netid, remoteaddr, myaddr, encap_port, sock,
|
|
in_spi, out_spi, null_auth, seq_num):
|
|
# Now send a packet.
|
|
sock.sendto(net_test.UDP_PAYLOAD, (remoteaddr, 53))
|
|
srcport = sock.getsockname()[1]
|
|
|
|
# Expect to see an UDP encapsulated packet.
|
|
pkts = self.ReadAllPacketsOn(netid)
|
|
self.assertEqual(1, len(pkts))
|
|
packet = pkts[0]
|
|
|
|
auth_algo = (
|
|
xfrm_base._ALGO_AUTH_NULL if null_auth else xfrm_base._ALGO_HMAC_SHA1)
|
|
expected_len = xfrm_base.GetEspPacketLength(
|
|
xfrm.XFRM_MODE_TRANSPORT, 4, True, net_test.UDP_PAYLOAD, auth_algo,
|
|
xfrm_base._ALGO_CBC_AES_256)
|
|
self.assertIsUdpEncapEsp(packet, out_spi, seq_num, expected_len)
|
|
|
|
# Now test the receive path. Because we don't know how to decrypt packets,
|
|
# we just play back the encrypted packet that kernel sent earlier. We swap
|
|
# the addresses in the IP header to make the packet look like it's bound for
|
|
# us, but we can't do that for the port numbers because the UDP header is
|
|
# part of the integrity protected payload, which we can only replay as is.
|
|
# So the source and destination ports are swapped and the packet appears to
|
|
# be sent from srcport to port 53. Open another socket on that port, and
|
|
# apply the inbound policy to it.
|
|
twisted_socket = socket(AF_INET, SOCK_DGRAM, 0)
|
|
csocket.SetSocketTimeout(twisted_socket, 100)
|
|
twisted_socket.bind(("0.0.0.0", 53))
|
|
|
|
# Save the payload of the packet so we can replay it back to ourselves, and
|
|
# replace the SPI with our inbound SPI.
|
|
payload = str(packet.payload)[8:]
|
|
spi_seq = xfrm.EspHdr((in_spi, seq_num)).Pack()
|
|
payload = spi_seq + payload[len(spi_seq):]
|
|
|
|
sainfo = self.xfrm.FindSaInfo(in_spi)
|
|
start_integrity_failures = sainfo.stats.integrity_failed
|
|
|
|
# Now play back the valid packet and check that we receive it.
|
|
incoming = (scapy.IP(src=remoteaddr, dst=myaddr) /
|
|
scapy.UDP(sport=4500, dport=encap_port) / payload)
|
|
incoming = scapy.IP(str(incoming))
|
|
self.ReceivePacketOn(netid, incoming)
|
|
|
|
sainfo = self.xfrm.FindSaInfo(in_spi)
|
|
|
|
# TODO: break this out into a separate test
|
|
# If our SPIs are different, and we aren't using null authentication,
|
|
# we expect the packet to be dropped. We also expect that the integrity
|
|
# failure counter to increase, as SPIs are part of the authenticated or
|
|
# integrity-verified portion of the packet.
|
|
if not null_auth and in_spi != out_spi:
|
|
self.assertRaisesErrno(EAGAIN, twisted_socket.recv, 4096)
|
|
self.assertEqual(start_integrity_failures + 1,
|
|
sainfo.stats.integrity_failed)
|
|
else:
|
|
data, src = twisted_socket.recvfrom(4096)
|
|
self.assertEqual(net_test.UDP_PAYLOAD, data)
|
|
self.assertEqual((remoteaddr, srcport), src)
|
|
self.assertEqual(start_integrity_failures, sainfo.stats.integrity_failed)
|
|
|
|
# Check that unencrypted packets on twisted_socket are not received.
|
|
unencrypted = (
|
|
scapy.IP(src=remoteaddr, dst=myaddr) / scapy.UDP(
|
|
sport=srcport, dport=53) / net_test.UDP_PAYLOAD)
|
|
self.assertRaisesErrno(EAGAIN, twisted_socket.recv, 4096)
|
|
|
|
def _RunEncapSocketPolicyTest(self, in_spi, out_spi, use_null_auth):
|
|
netid, myaddr, remoteaddr, encap_sock, encap_port, s = \
|
|
self._SetupUdpEncapSockets()
|
|
|
|
self._SetupUdpEncapSaPair(myaddr, remoteaddr, in_spi, out_spi, encap_port,
|
|
s, use_null_auth)
|
|
|
|
# Check that UDP encap sockets work with socket policy and given SAs
|
|
self._VerifyUdpEncapSocket(netid, remoteaddr, myaddr, encap_port, s, in_spi,
|
|
out_spi, use_null_auth, 1)
|
|
|
|
# TODO: Add tests for ESP (non-encap) sockets.
|
|
def testUdpEncapSameSpisNullAuth(self):
|
|
# Use the same SPI both inbound and outbound because this lets us receive
|
|
# encrypted packets by simply replaying the packets the kernel sends
|
|
# without having to disable authentication
|
|
self._RunEncapSocketPolicyTest(TEST_SPI, TEST_SPI, True)
|
|
|
|
def testUdpEncapSameSpis(self):
|
|
self._RunEncapSocketPolicyTest(TEST_SPI, TEST_SPI, False)
|
|
|
|
def testUdpEncapDifferentSpisNullAuth(self):
|
|
self._RunEncapSocketPolicyTest(TEST_SPI, TEST_SPI2, True)
|
|
|
|
def testUdpEncapDifferentSpis(self):
|
|
self._RunEncapSocketPolicyTest(TEST_SPI, TEST_SPI2, False)
|
|
|
|
def testUdpEncapRekey(self):
|
|
# Select the two SPIs that will be used
|
|
start_spi = TEST_SPI
|
|
rekey_spi = TEST_SPI2
|
|
|
|
# Setup sockets
|
|
netid, myaddr, remoteaddr, encap_sock, encap_port, s = \
|
|
self._SetupUdpEncapSockets()
|
|
|
|
# The SAs must use null authentication, since we change SPIs on the fly
|
|
# Without null authentication, this would result in an ESP authentication
|
|
# error since the SPI is part of the authenticated section. The packet
|
|
# would then be dropped
|
|
self._SetupUdpEncapSaPair(myaddr, remoteaddr, start_spi, start_spi,
|
|
encap_port, s, True)
|
|
|
|
# Check that UDP encap sockets work with socket policy and given SAs
|
|
self._VerifyUdpEncapSocket(netid, remoteaddr, myaddr, encap_port, s,
|
|
start_spi, start_spi, True, 1)
|
|
|
|
# Rekey this socket using the make-before-break paradigm. First we create
|
|
# new SAs, update the per-socket policies, and only then remove the old SAs
|
|
#
|
|
# This allows us to switch to the new SA without breaking the outbound path.
|
|
self._SetupUdpEncapSaPair(myaddr, remoteaddr, rekey_spi, rekey_spi,
|
|
encap_port, s, True)
|
|
|
|
# Check that UDP encap socket works with updated socket policy, sending
|
|
# using new SA, but receiving on both old and new SAs
|
|
self._VerifyUdpEncapSocket(netid, remoteaddr, myaddr, encap_port, s,
|
|
rekey_spi, rekey_spi, True, 1)
|
|
self._VerifyUdpEncapSocket(netid, remoteaddr, myaddr, encap_port, s,
|
|
start_spi, rekey_spi, True, 2)
|
|
|
|
# Delete old SAs
|
|
self.xfrm.DeleteSaInfo(remoteaddr, start_spi, IPPROTO_ESP)
|
|
self.xfrm.DeleteSaInfo(myaddr, start_spi, IPPROTO_ESP)
|
|
|
|
# Check that UDP encap socket works with updated socket policy and new SAs
|
|
self._VerifyUdpEncapSocket(netid, remoteaddr, myaddr, encap_port, s,
|
|
rekey_spi, rekey_spi, True, 3)
|
|
|
|
def testAllocSpecificSpi(self):
|
|
spi = 0xABCD
|
|
new_sa = self.xfrm.AllocSpi("::", IPPROTO_ESP, spi, spi)
|
|
self.assertEqual(spi, new_sa.id.spi)
|
|
|
|
def testAllocSpecificSpiUnavailable(self):
|
|
"""Attempt to allocate the same SPI twice."""
|
|
spi = 0xABCD
|
|
new_sa = self.xfrm.AllocSpi("::", IPPROTO_ESP, spi, spi)
|
|
self.assertEqual(spi, new_sa.id.spi)
|
|
with self.assertRaisesErrno(ENOENT):
|
|
new_sa = self.xfrm.AllocSpi("::", IPPROTO_ESP, spi, spi)
|
|
|
|
def testAllocRangeSpi(self):
|
|
start, end = 0xABCD0, 0xABCDF
|
|
new_sa = self.xfrm.AllocSpi("::", IPPROTO_ESP, start, end)
|
|
spi = new_sa.id.spi
|
|
self.assertGreaterEqual(spi, start)
|
|
self.assertLessEqual(spi, end)
|
|
|
|
def testAllocRangeSpiUnavailable(self):
|
|
"""Attempt to allocate N+1 SPIs from a range of size N."""
|
|
start, end = 0xABCD0, 0xABCDF
|
|
range_size = end - start + 1
|
|
spis = set()
|
|
# Assert that allocating SPI fails when none are available.
|
|
with self.assertRaisesErrno(ENOENT):
|
|
# Allocating range_size + 1 SPIs is guaranteed to fail. Due to the way
|
|
# kernel picks random SPIs, this has a high probability of failing before
|
|
# reaching that limit.
|
|
for i in range(range_size + 1):
|
|
new_sa = self.xfrm.AllocSpi("::", IPPROTO_ESP, start, end)
|
|
spi = new_sa.id.spi
|
|
self.assertNotIn(spi, spis)
|
|
spis.add(spi)
|
|
|
|
def testSocketPolicyDstCacheV6(self):
|
|
self._TestSocketPolicyDstCache(6)
|
|
|
|
def testSocketPolicyDstCacheV4(self):
|
|
self._TestSocketPolicyDstCache(4)
|
|
|
|
def _TestSocketPolicyDstCache(self, version):
|
|
"""Test that destination cache is cleared with socket policy.
|
|
|
|
This relies on the fact that connect() on a UDP socket populates the
|
|
destination cache.
|
|
"""
|
|
|
|
# Create UDP socket.
|
|
family = net_test.GetAddressFamily(version)
|
|
netid = self.RandomNetid()
|
|
s = socket(family, SOCK_DGRAM, 0)
|
|
self.SelectInterface(s, netid, "mark")
|
|
|
|
# Populate the socket's destination cache.
|
|
remote = self.GetRemoteAddress(version)
|
|
s.connect((remote, 53))
|
|
|
|
# Apply a policy to the socket. Should clear dst cache.
|
|
reqid = 123
|
|
xfrm_base.ApplySocketPolicy(s, family, xfrm.XFRM_POLICY_OUT,
|
|
TEST_SPI, reqid, None)
|
|
|
|
# Policy with no matching SA should result in EAGAIN. If destination cache
|
|
# failed to clear, then the UDP packet will be sent normally.
|
|
with self.assertRaisesErrno(EAGAIN):
|
|
s.send(net_test.UDP_PAYLOAD)
|
|
self.ExpectNoPacketsOn(netid, "Packet not blocked by policy")
|
|
|
|
def _CheckNullEncryptionTunnelMode(self, version):
|
|
family = net_test.GetAddressFamily(version)
|
|
netid = self.RandomNetid()
|
|
local_addr = self.MyAddress(version, netid)
|
|
remote_addr = self.GetRemoteAddress(version)
|
|
|
|
# Borrow the address of another netId as the source address of the tunnel
|
|
tun_local = self.MyAddress(version, self.RandomNetid(netid))
|
|
# For generality, pick a tunnel endpoint that's not the address we
|
|
# connect the socket to.
|
|
tun_remote = TUNNEL_ENDPOINTS[version]
|
|
|
|
# Output
|
|
self.xfrm.AddSaInfo(
|
|
tun_local, tun_remote, 0xABCD, xfrm.XFRM_MODE_TUNNEL, 123,
|
|
xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL,
|
|
None, None, None, netid)
|
|
# Input
|
|
self.xfrm.AddSaInfo(
|
|
tun_remote, tun_local, 0x9876, xfrm.XFRM_MODE_TUNNEL, 456,
|
|
xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL,
|
|
None, None, None, None)
|
|
|
|
sock = net_test.UDPSocket(family)
|
|
self.SelectInterface(sock, netid, "mark")
|
|
sock.bind((local_addr, 0))
|
|
local_port = sock.getsockname()[1]
|
|
remote_port = 5555
|
|
|
|
xfrm_base.ApplySocketPolicy(
|
|
sock, family, xfrm.XFRM_POLICY_OUT, 0xABCD, 123,
|
|
(tun_local, tun_remote))
|
|
xfrm_base.ApplySocketPolicy(
|
|
sock, family, xfrm.XFRM_POLICY_IN, 0x9876, 456,
|
|
(tun_remote, tun_local))
|
|
|
|
# Create and receive an ESP packet.
|
|
IpType = {4: scapy.IP, 6: scapy.IPv6}[version]
|
|
input_pkt = (IpType(src=remote_addr, dst=local_addr) /
|
|
scapy.UDP(sport=remote_port, dport=local_port) /
|
|
"input hello")
|
|
input_pkt = IpType(str(input_pkt)) # Compute length, checksum.
|
|
input_pkt = xfrm_base.EncryptPacketWithNull(input_pkt, 0x9876,
|
|
1, (tun_remote, tun_local))
|
|
|
|
self.ReceivePacketOn(netid, input_pkt)
|
|
msg, addr = sock.recvfrom(1024)
|
|
self.assertEqual("input hello", msg)
|
|
self.assertEqual((remote_addr, remote_port), addr[:2])
|
|
|
|
# Send and capture a packet.
|
|
sock.sendto("output hello", (remote_addr, remote_port))
|
|
packets = self.ReadAllPacketsOn(netid)
|
|
self.assertEqual(1, len(packets))
|
|
output_pkt = packets[0]
|
|
output_pkt, esp_hdr = xfrm_base.DecryptPacketWithNull(output_pkt)
|
|
self.assertEqual(output_pkt[scapy.UDP].len, len("output_hello") + 8)
|
|
self.assertEqual(remote_addr, output_pkt.dst)
|
|
self.assertEqual(remote_port, output_pkt[scapy.UDP].dport)
|
|
# length of the payload plus the UDP header
|
|
self.assertEqual("output hello", str(output_pkt[scapy.UDP].payload))
|
|
self.assertEqual(0xABCD, esp_hdr.spi)
|
|
|
|
def testNullEncryptionTunnelMode(self):
|
|
"""Verify null encryption in tunnel mode.
|
|
|
|
This test verifies both manual assembly and disassembly of UDP packets
|
|
with ESP in IPsec tunnel mode.
|
|
"""
|
|
for version in [4, 6]:
|
|
self._CheckNullEncryptionTunnelMode(version)
|
|
|
|
def _CheckNullEncryptionTransportMode(self, version):
|
|
family = net_test.GetAddressFamily(version)
|
|
netid = self.RandomNetid()
|
|
local_addr = self.MyAddress(version, netid)
|
|
remote_addr = self.GetRemoteAddress(version)
|
|
|
|
# Output
|
|
self.xfrm.AddSaInfo(
|
|
local_addr, remote_addr, 0xABCD, xfrm.XFRM_MODE_TRANSPORT, 123,
|
|
xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL,
|
|
None, None, None, None)
|
|
# Input
|
|
self.xfrm.AddSaInfo(
|
|
remote_addr, local_addr, 0x9876, xfrm.XFRM_MODE_TRANSPORT, 456,
|
|
xfrm_base._ALGO_CRYPT_NULL, xfrm_base._ALGO_AUTH_NULL,
|
|
None, None, None, None)
|
|
|
|
sock = net_test.UDPSocket(family)
|
|
self.SelectInterface(sock, netid, "mark")
|
|
sock.bind((local_addr, 0))
|
|
local_port = sock.getsockname()[1]
|
|
remote_port = 5555
|
|
|
|
xfrm_base.ApplySocketPolicy(
|
|
sock, family, xfrm.XFRM_POLICY_OUT, 0xABCD, 123, None)
|
|
xfrm_base.ApplySocketPolicy(
|
|
sock, family, xfrm.XFRM_POLICY_IN, 0x9876, 456, None)
|
|
|
|
# Create and receive an ESP packet.
|
|
IpType = {4: scapy.IP, 6: scapy.IPv6}[version]
|
|
input_pkt = (IpType(src=remote_addr, dst=local_addr) /
|
|
scapy.UDP(sport=remote_port, dport=local_port) /
|
|
"input hello")
|
|
input_pkt = IpType(str(input_pkt)) # Compute length, checksum.
|
|
input_pkt = xfrm_base.EncryptPacketWithNull(input_pkt, 0x9876, 1, None)
|
|
|
|
self.ReceivePacketOn(netid, input_pkt)
|
|
msg, addr = sock.recvfrom(1024)
|
|
self.assertEqual("input hello", msg)
|
|
self.assertEqual((remote_addr, remote_port), addr[:2])
|
|
|
|
# Send and capture a packet.
|
|
sock.sendto("output hello", (remote_addr, remote_port))
|
|
packets = self.ReadAllPacketsOn(netid)
|
|
self.assertEqual(1, len(packets))
|
|
output_pkt = packets[0]
|
|
output_pkt, esp_hdr = xfrm_base.DecryptPacketWithNull(output_pkt)
|
|
# length of the payload plus the UDP header
|
|
self.assertEqual(output_pkt[scapy.UDP].len, len("output_hello") + 8)
|
|
self.assertEqual(remote_addr, output_pkt.dst)
|
|
self.assertEqual(remote_port, output_pkt[scapy.UDP].dport)
|
|
self.assertEqual("output hello", str(output_pkt[scapy.UDP].payload))
|
|
self.assertEqual(0xABCD, esp_hdr.spi)
|
|
|
|
def testNullEncryptionTransportMode(self):
|
|
"""Verify null encryption in transport mode.
|
|
|
|
This test verifies both manual assembly and disassembly of UDP packets
|
|
with ESP in IPsec transport mode.
|
|
"""
|
|
for version in [4, 6]:
|
|
self._CheckNullEncryptionTransportMode(version)
|
|
|
|
def _CheckGlobalPoliciesByMark(self, version):
|
|
"""Tests that global policies may differ by only the mark."""
|
|
family = net_test.GetAddressFamily(version)
|
|
sel = xfrm.EmptySelector(family)
|
|
# Pick 2 arbitrary mark values.
|
|
mark1 = xfrm.XfrmMark(mark=0xf00, mask=xfrm_base.MARK_MASK_ALL)
|
|
mark2 = xfrm.XfrmMark(mark=0xf00d, mask=xfrm_base.MARK_MASK_ALL)
|
|
# Create a global policy.
|
|
policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
|
|
tmpl = xfrm.UserTemplate(AF_UNSPEC, 0xfeed, 0, None)
|
|
# Create the policy with the first mark.
|
|
self.xfrm.AddPolicyInfo(policy, tmpl, mark1)
|
|
# Create the same policy but with the second (different) mark.
|
|
self.xfrm.AddPolicyInfo(policy, tmpl, mark2)
|
|
# Delete the policies individually
|
|
self.xfrm.DeletePolicyInfo(sel, xfrm.XFRM_POLICY_OUT, mark1)
|
|
self.xfrm.DeletePolicyInfo(sel, xfrm.XFRM_POLICY_OUT, mark2)
|
|
|
|
def testGlobalPoliciesByMarkV4(self):
|
|
self._CheckGlobalPoliciesByMark(4)
|
|
|
|
def testGlobalPoliciesByMarkV6(self):
|
|
self._CheckGlobalPoliciesByMark(6)
|
|
|
|
def _CheckUpdatePolicy(self, version):
|
|
"""Tests that we can can update the template on a policy."""
|
|
family = net_test.GetAddressFamily(version)
|
|
tmpl1 = xfrm.UserTemplate(family, 0xdead, 0, None)
|
|
tmpl2 = xfrm.UserTemplate(family, 0xbeef, 0, None)
|
|
sel = xfrm.EmptySelector(family)
|
|
policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
|
|
mark = xfrm.XfrmMark(mark=0xf00, mask=xfrm_base.MARK_MASK_ALL)
|
|
|
|
def _CheckTemplateMatch(tmpl):
|
|
"""Dump the SPD and match a single template on a single policy."""
|
|
dump = self.xfrm.DumpPolicyInfo()
|
|
self.assertEqual(1, len(dump))
|
|
_, attributes = dump[0]
|
|
self.assertEqual(attributes['XFRMA_TMPL'], tmpl)
|
|
|
|
# Create a new policy using update.
|
|
self.xfrm.UpdatePolicyInfo(policy, tmpl1, mark, None)
|
|
# NEWPOLICY will not update the existing policy. This checks both that
|
|
# UPDPOLICY created a policy and that NEWPOLICY will not perform updates.
|
|
_CheckTemplateMatch(tmpl1)
|
|
with self.assertRaisesErrno(EEXIST):
|
|
self.xfrm.AddPolicyInfo(policy, tmpl2, mark, None)
|
|
# Update the policy using UPDPOLICY.
|
|
self.xfrm.UpdatePolicyInfo(policy, tmpl2, mark, None)
|
|
# There should only be one policy after update, and it should have the
|
|
# updated template.
|
|
_CheckTemplateMatch(tmpl2)
|
|
|
|
def testUpdatePolicyV4(self):
|
|
self._CheckUpdatePolicy(4)
|
|
|
|
def testUpdatePolicyV6(self):
|
|
self._CheckUpdatePolicy(6)
|
|
|
|
def _CheckPolicyDifferByDirection(self,version):
|
|
"""Tests that policies can differ only by direction."""
|
|
family = net_test.GetAddressFamily(version)
|
|
tmpl = xfrm.UserTemplate(family, 0xdead, 0, None)
|
|
sel = xfrm.EmptySelector(family)
|
|
mark = xfrm.XfrmMark(mark=0xf00, mask=xfrm_base.MARK_MASK_ALL)
|
|
policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
|
|
self.xfrm.AddPolicyInfo(policy, tmpl, mark)
|
|
policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_IN, sel)
|
|
self.xfrm.AddPolicyInfo(policy, tmpl, mark)
|
|
|
|
def testPolicyDifferByDirectionV4(self):
|
|
self._CheckPolicyDifferByDirection(4)
|
|
|
|
def testPolicyDifferByDirectionV6(self):
|
|
self._CheckPolicyDifferByDirection(6)
|
|
|
|
class XfrmOutputMarkTest(xfrm_base.XfrmLazyTest):
|
|
|
|
def _CheckTunnelModeOutputMark(self, version, tunsrc, mark, expected_netid):
|
|
"""Tests sending UDP packets to tunnel mode SAs with output marks.
|
|
|
|
Opens a UDP socket and binds it to a random netid, then sets up tunnel mode
|
|
SAs with an output_mark of mark and sets a socket policy to use the SA.
|
|
Then checks that sending on those SAs sends a packet on expected_netid,
|
|
or, if expected_netid is zero, checks that sending returns ENETUNREACH.
|
|
|
|
Args:
|
|
version: 4 or 6.
|
|
tunsrc: A string, the source address of the tunnel.
|
|
mark: An integer, the output_mark to set in the SA.
|
|
expected_netid: An integer, the netid to expect the kernel to send the
|
|
packet on. If None, expect that sendto will fail with ENETUNREACH.
|
|
"""
|
|
# Open a UDP socket and bind it to a random netid.
|
|
family = net_test.GetAddressFamily(version)
|
|
s = socket(family, SOCK_DGRAM, 0)
|
|
self.SelectInterface(s, self.RandomNetid(), "mark")
|
|
|
|
# For generality, pick a tunnel endpoint that's not the address we
|
|
# connect the socket to.
|
|
tundst = TUNNEL_ENDPOINTS[version]
|
|
tun_addrs = (tunsrc, tundst)
|
|
|
|
# Create a tunnel mode SA and use XFRM_OUTPUT_MARK to bind it to netid.
|
|
spi = TEST_SPI * mark
|
|
reqid = 100 + spi
|
|
self.xfrm.AddSaInfo(tunsrc, tundst, spi, xfrm.XFRM_MODE_TUNNEL, reqid,
|
|
xfrm_base._ALGO_CBC_AES_256, xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, None, mark)
|
|
|
|
# Set a socket policy to use it.
|
|
xfrm_base.ApplySocketPolicy(s, family, xfrm.XFRM_POLICY_OUT, spi, reqid,
|
|
tun_addrs)
|
|
|
|
# Send a packet and check that we see it on the wire.
|
|
remoteaddr = self.GetRemoteAddress(version)
|
|
|
|
packetlen = xfrm_base.GetEspPacketLength(xfrm.XFRM_MODE_TUNNEL, version,
|
|
False, net_test.UDP_PAYLOAD,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
xfrm_base._ALGO_CBC_AES_256)
|
|
|
|
if expected_netid is not None:
|
|
s.sendto(net_test.UDP_PAYLOAD, (remoteaddr, 53))
|
|
self._ExpectEspPacketOn(expected_netid, spi, 1, packetlen, tunsrc, tundst)
|
|
else:
|
|
with self.assertRaisesErrno(ENETUNREACH):
|
|
s.sendto(net_test.UDP_PAYLOAD, (remoteaddr, 53))
|
|
|
|
def testTunnelModeOutputMarkIPv4(self):
|
|
for netid in self.NETIDS:
|
|
tunsrc = self.MyAddress(4, netid)
|
|
self._CheckTunnelModeOutputMark(4, tunsrc, netid, netid)
|
|
|
|
def testTunnelModeOutputMarkIPv6(self):
|
|
for netid in self.NETIDS:
|
|
tunsrc = self.MyAddress(6, netid)
|
|
self._CheckTunnelModeOutputMark(6, tunsrc, netid, netid)
|
|
|
|
def testTunnelModeOutputNoMarkIPv4(self):
|
|
tunsrc = self.MyAddress(4, self.RandomNetid())
|
|
self._CheckTunnelModeOutputMark(4, tunsrc, 0, None)
|
|
|
|
def testTunnelModeOutputNoMarkIPv6(self):
|
|
tunsrc = self.MyAddress(6, self.RandomNetid())
|
|
self._CheckTunnelModeOutputMark(6, tunsrc, 0, None)
|
|
|
|
def testTunnelModeOutputInvalidMarkIPv4(self):
|
|
tunsrc = self.MyAddress(4, self.RandomNetid())
|
|
self._CheckTunnelModeOutputMark(4, tunsrc, 9999, None)
|
|
|
|
def testTunnelModeOutputInvalidMarkIPv6(self):
|
|
tunsrc = self.MyAddress(6, self.RandomNetid())
|
|
self._CheckTunnelModeOutputMark(6, tunsrc, 9999, None)
|
|
|
|
def testTunnelModeOutputMarkAttributes(self):
|
|
mark = 1234567
|
|
self.xfrm.AddSaInfo(TEST_ADDR1, TUNNEL_ENDPOINTS[6], 0x1234,
|
|
xfrm.XFRM_MODE_TUNNEL, 100, xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1, None, None, None, mark)
|
|
dump = self.xfrm.DumpSaInfo()
|
|
self.assertEqual(1, len(dump))
|
|
sainfo, attributes = dump[0]
|
|
self.assertEqual(mark, attributes["XFRMA_OUTPUT_MARK"])
|
|
|
|
def testInvalidAlgorithms(self):
|
|
key = "af442892cdcd0ef650e9c299f9a8436a".decode("hex")
|
|
invalid_auth = (xfrm.XfrmAlgoAuth(("invalid(algo)", 128, 96)), key)
|
|
invalid_crypt = (xfrm.XfrmAlgo(("invalid(algo)", 128)), key)
|
|
with self.assertRaisesErrno(ENOSYS):
|
|
self.xfrm.AddSaInfo(TEST_ADDR1, TEST_ADDR2, 0x1234,
|
|
xfrm.XFRM_MODE_TRANSPORT, 0, xfrm_base._ALGO_CBC_AES_256,
|
|
invalid_auth, None, None, None, 0)
|
|
with self.assertRaisesErrno(ENOSYS):
|
|
self.xfrm.AddSaInfo(TEST_ADDR1, TEST_ADDR2, 0x1234,
|
|
xfrm.XFRM_MODE_TRANSPORT, 0, invalid_crypt,
|
|
xfrm_base._ALGO_HMAC_SHA1, None, None, None, 0)
|
|
|
|
def testUpdateSaAddMark(self):
|
|
"""Test that an embryonic SA can be updated to add a mark."""
|
|
for version in [4, 6]:
|
|
spi = 0xABCD
|
|
# Test that an SA created with ALLOCSPI can be updated with the mark.
|
|
new_sa = self.xfrm.AllocSpi(net_test.GetWildcardAddress(version),
|
|
IPPROTO_ESP, spi, spi)
|
|
mark = xfrm.ExactMatchMark(0xf00d)
|
|
self.xfrm.AddSaInfo(net_test.GetWildcardAddress(version),
|
|
net_test.GetWildcardAddress(version),
|
|
spi, xfrm.XFRM_MODE_TUNNEL, 0,
|
|
xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, mark, 0, is_update=True)
|
|
dump = self.xfrm.DumpSaInfo()
|
|
self.assertEqual(1, len(dump)) # check that update updated
|
|
sainfo, attributes = dump[0]
|
|
self.assertEqual(mark, attributes["XFRMA_MARK"])
|
|
self.xfrm.DeleteSaInfo(net_test.GetWildcardAddress(version),
|
|
spi, IPPROTO_ESP, mark)
|
|
|
|
def getXfrmStat(self, statName):
|
|
stateVal = 0
|
|
with open(XFRM_STATS_PROCFILE, 'r') as f:
|
|
for line in f:
|
|
if statName in line:
|
|
stateVal = int(line.split()[1])
|
|
break
|
|
f.close()
|
|
return stateVal
|
|
|
|
def testUpdateActiveSaMarks(self):
|
|
"""Test that the OUTPUT_MARK can be updated on an ACTIVE SA."""
|
|
for version in [4, 6]:
|
|
family = net_test.GetAddressFamily(version)
|
|
netid = self.RandomNetid()
|
|
remote = self.GetRemoteAddress(version)
|
|
local = self.MyAddress(version, netid)
|
|
s = socket(family, SOCK_DGRAM, 0)
|
|
self.SelectInterface(s, netid, "mark")
|
|
# Create a mark that we will apply to the policy and later the SA
|
|
mark = xfrm.ExactMatchMark(netid)
|
|
|
|
# Create a global policy that selects using the mark.
|
|
sel = xfrm.EmptySelector(family)
|
|
policy = xfrm.UserPolicy(xfrm.XFRM_POLICY_OUT, sel)
|
|
tmpl = xfrm.UserTemplate(family, 0, 0, (local, remote))
|
|
self.xfrm.AddPolicyInfo(policy, tmpl, mark)
|
|
|
|
# Pull /proc/net/xfrm_stats for baseline
|
|
outNoStateCount = self.getXfrmStat(XFRM_STATS_OUT_NO_STATES);
|
|
|
|
# should increment XfrmOutNoStates
|
|
s.sendto(net_test.UDP_PAYLOAD, (remote, 53))
|
|
|
|
# Check to make sure XfrmOutNoStates is incremented by exactly 1
|
|
self.assertEqual(outNoStateCount + 1,
|
|
self.getXfrmStat(XFRM_STATS_OUT_NO_STATES))
|
|
|
|
length = xfrm_base.GetEspPacketLength(xfrm.XFRM_MODE_TUNNEL,
|
|
version, False,
|
|
net_test.UDP_PAYLOAD,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
xfrm_base._ALGO_CBC_AES_256)
|
|
|
|
# Add a default SA with no mark that routes to nowhere.
|
|
try:
|
|
self.xfrm.AddSaInfo(local,
|
|
remote,
|
|
TEST_SPI, xfrm.XFRM_MODE_TUNNEL, 0,
|
|
xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, mark, 0, is_update=False)
|
|
except IOError as e:
|
|
self.assertEqual(EEXIST, e.errno, "SA exists")
|
|
self.xfrm.AddSaInfo(local,
|
|
remote,
|
|
TEST_SPI, xfrm.XFRM_MODE_TUNNEL, 0,
|
|
xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, mark, 0, is_update=True)
|
|
|
|
self.assertRaisesErrno(
|
|
ENETUNREACH,
|
|
s.sendto, net_test.UDP_PAYLOAD, (remote, 53))
|
|
|
|
# Update the SA to route to a valid netid.
|
|
self.xfrm.AddSaInfo(local,
|
|
remote,
|
|
TEST_SPI, xfrm.XFRM_MODE_TUNNEL, 0,
|
|
xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, mark, netid, is_update=True)
|
|
|
|
# Now the payload routes to the updated netid.
|
|
s.sendto(net_test.UDP_PAYLOAD, (remote, 53))
|
|
self._ExpectEspPacketOn(netid, TEST_SPI, 1, length, None, None)
|
|
|
|
# Get a new netid and reroute the packets to the new netid.
|
|
reroute_netid = self.RandomNetid(netid)
|
|
# Update the SA to change the output mark.
|
|
self.xfrm.AddSaInfo(local,
|
|
remote,
|
|
TEST_SPI, xfrm.XFRM_MODE_TUNNEL, 0,
|
|
xfrm_base._ALGO_CBC_AES_256,
|
|
xfrm_base._ALGO_HMAC_SHA1,
|
|
None, None, mark, reroute_netid, is_update=True)
|
|
|
|
s.sendto(net_test.UDP_PAYLOAD, (remote, 53))
|
|
self._ExpectEspPacketOn(reroute_netid, TEST_SPI, 2, length, None, None)
|
|
|
|
dump = self.xfrm.DumpSaInfo()
|
|
|
|
self.assertEqual(1, len(dump)) # check that update updated
|
|
sainfo, attributes = dump[0]
|
|
self.assertEqual(reroute_netid, attributes["XFRMA_OUTPUT_MARK"])
|
|
|
|
self.xfrm.DeleteSaInfo(remote, TEST_SPI, IPPROTO_ESP, mark)
|
|
self.xfrm.DeletePolicyInfo(sel, xfrm.XFRM_POLICY_OUT, mark)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|