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.
1315 lines
50 KiB
1315 lines
50 KiB
#!/usr/bin/python
|
|
#
|
|
# Copyright 2014 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.
|
|
|
|
import cstruct
|
|
import ctypes
|
|
import errno
|
|
import os
|
|
import random
|
|
from socket import * # pylint: disable=wildcard-import
|
|
import struct
|
|
import time # pylint: disable=unused-import
|
|
import unittest
|
|
|
|
from scapy import all as scapy
|
|
|
|
import csocket
|
|
import iproute
|
|
import multinetwork_base
|
|
import net_test
|
|
import netlink
|
|
import packets
|
|
|
|
# For brevity.
|
|
UDP_PAYLOAD = net_test.UDP_PAYLOAD
|
|
|
|
IPV6_FLOWINFO = 11
|
|
|
|
SYNCOOKIES_SYSCTL = "/proc/sys/net/ipv4/tcp_syncookies"
|
|
TCP_MARK_ACCEPT_SYSCTL = "/proc/sys/net/ipv4/tcp_fwmark_accept"
|
|
|
|
# The IP[V6]UNICAST_IF socket option was added between 3.1 and 3.4.
|
|
HAVE_UNICAST_IF = net_test.LINUX_VERSION >= (3, 4, 0)
|
|
|
|
# RTPROT_RA is working properly with 4.14
|
|
HAVE_RTPROT_RA = net_test.LINUX_VERSION >= (4, 14, 0)
|
|
|
|
class ConfigurationError(AssertionError):
|
|
pass
|
|
|
|
|
|
class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
|
|
|
|
# How many times to run outgoing packet tests.
|
|
ITERATIONS = 5
|
|
|
|
def CheckPingPacket(self, version, netid, routing_mode, packet):
|
|
s = self.BuildSocket(version, net_test.PingSocket, netid, routing_mode)
|
|
|
|
myaddr = self.MyAddress(version, netid)
|
|
mysockaddr = self.MySocketAddress(version, netid)
|
|
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
|
s.bind((mysockaddr, packets.PING_IDENT))
|
|
net_test.SetSocketTos(s, packets.PING_TOS)
|
|
|
|
dstaddr = self.GetRemoteAddress(version)
|
|
dstsockaddr = self.GetRemoteSocketAddress(version)
|
|
desc, expected = packets.ICMPEcho(version, myaddr, dstaddr)
|
|
msg = "IPv%d ping: expected %s on %s" % (
|
|
version, desc, self.GetInterfaceName(netid))
|
|
|
|
s.sendto(packet + packets.PING_PAYLOAD, (dstsockaddr, 19321))
|
|
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
def CheckTCPSYNPacket(self, version, netid, routing_mode):
|
|
s = self.BuildSocket(version, net_test.TCPSocket, netid, routing_mode)
|
|
|
|
myaddr = self.MyAddress(version, netid)
|
|
dstaddr = self.GetRemoteAddress(version)
|
|
dstsockaddr = self.GetRemoteSocketAddress(version)
|
|
desc, expected = packets.SYN(53, version, myaddr, dstaddr,
|
|
sport=None, seq=None)
|
|
|
|
|
|
# Non-blocking TCP connects always return EINPROGRESS.
|
|
self.assertRaisesErrno(errno.EINPROGRESS, s.connect, (dstsockaddr, 53))
|
|
msg = "IPv%s TCP connect: expected %s on %s" % (
|
|
version, desc, self.GetInterfaceName(netid))
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
s.close()
|
|
|
|
def CheckUDPPacket(self, version, netid, routing_mode):
|
|
s = self.BuildSocket(version, net_test.UDPSocket, netid, routing_mode)
|
|
|
|
myaddr = self.MyAddress(version, netid)
|
|
dstaddr = self.GetRemoteAddress(version)
|
|
dstsockaddr = self.GetRemoteSocketAddress(version)
|
|
|
|
desc, expected = packets.UDP(version, myaddr, dstaddr, sport=None)
|
|
msg = "IPv%s UDP %%s: expected %s on %s" % (
|
|
version, desc, self.GetInterfaceName(netid))
|
|
|
|
s.sendto(UDP_PAYLOAD, (dstsockaddr, 53))
|
|
self.ExpectPacketOn(netid, msg % "sendto", expected)
|
|
|
|
# IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
|
|
if routing_mode != "ucast_oif":
|
|
s.connect((dstsockaddr, 53))
|
|
s.send(UDP_PAYLOAD)
|
|
self.ExpectPacketOn(netid, msg % "connect/send", expected)
|
|
s.close()
|
|
|
|
def CheckRawGrePacket(self, version, netid, routing_mode):
|
|
s = self.BuildSocket(version, net_test.RawGRESocket, netid, routing_mode)
|
|
|
|
inner_version = {4: 6, 6: 4}[version]
|
|
inner_src = self.MyAddress(inner_version, netid)
|
|
inner_dst = self.GetRemoteAddress(inner_version)
|
|
inner = str(packets.UDP(inner_version, inner_src, inner_dst, sport=None)[1])
|
|
|
|
ethertype = {4: net_test.ETH_P_IP, 6: net_test.ETH_P_IPV6}[inner_version]
|
|
# A GRE header can be as simple as two zero bytes and the ethertype.
|
|
packet = struct.pack("!i", ethertype) + inner
|
|
myaddr = self.MyAddress(version, netid)
|
|
dstaddr = self.GetRemoteAddress(version)
|
|
|
|
s.sendto(packet, (dstaddr, IPPROTO_GRE))
|
|
desc, expected = packets.GRE(version, myaddr, dstaddr, ethertype, inner)
|
|
msg = "Raw IPv%d GRE with inner IPv%d UDP: expected %s on %s" % (
|
|
version, inner_version, desc, self.GetInterfaceName(netid))
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
def CheckOutgoingPackets(self, routing_mode):
|
|
for _ in range(self.ITERATIONS):
|
|
for netid in self.tuns:
|
|
|
|
self.CheckPingPacket(4, netid, routing_mode, self.IPV4_PING)
|
|
# Kernel bug.
|
|
if routing_mode != "oif":
|
|
self.CheckPingPacket(6, netid, routing_mode, self.IPV6_PING)
|
|
|
|
# IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
|
|
if routing_mode != "ucast_oif":
|
|
self.CheckTCPSYNPacket(4, netid, routing_mode)
|
|
self.CheckTCPSYNPacket(6, netid, routing_mode)
|
|
self.CheckTCPSYNPacket(5, netid, routing_mode)
|
|
|
|
self.CheckUDPPacket(4, netid, routing_mode)
|
|
self.CheckUDPPacket(6, netid, routing_mode)
|
|
self.CheckUDPPacket(5, netid, routing_mode)
|
|
|
|
# Creating raw sockets on non-root UIDs requires properly setting
|
|
# capabilities, which is hard to do from Python.
|
|
# IP_UNICAST_IF is not supported on raw sockets.
|
|
if routing_mode not in ["uid", "ucast_oif"]:
|
|
self.CheckRawGrePacket(4, netid, routing_mode)
|
|
self.CheckRawGrePacket(6, netid, routing_mode)
|
|
|
|
def testMarkRouting(self):
|
|
"""Checks that socket marking selects the right outgoing interface."""
|
|
self.CheckOutgoingPackets("mark")
|
|
|
|
def testUidRouting(self):
|
|
"""Checks that UID routing selects the right outgoing interface."""
|
|
self.CheckOutgoingPackets("uid")
|
|
|
|
def testOifRouting(self):
|
|
"""Checks that oif routing selects the right outgoing interface."""
|
|
self.CheckOutgoingPackets("oif")
|
|
|
|
@unittest.skipUnless(HAVE_UNICAST_IF, "no support for UNICAST_IF")
|
|
def testUcastOifRouting(self):
|
|
"""Checks that ucast oif routing selects the right outgoing interface."""
|
|
self.CheckOutgoingPackets("ucast_oif")
|
|
|
|
def CheckRemarking(self, version, use_connect):
|
|
modes = ["mark", "oif", "uid"]
|
|
# Setting UNICAST_IF on connected sockets does not work.
|
|
if not use_connect and HAVE_UNICAST_IF:
|
|
modes += ["ucast_oif"]
|
|
|
|
for mode in modes:
|
|
s = net_test.UDPSocket(self.GetProtocolFamily(version))
|
|
|
|
# Figure out what packets to expect.
|
|
sport = net_test.BindRandomPort(version, s)
|
|
dstaddr = {4: self.IPV4_ADDR, 6: self.IPV6_ADDR}[version]
|
|
unspec = {4: "0.0.0.0", 6: "::"}[version] # Placeholder.
|
|
desc, expected = packets.UDP(version, unspec, dstaddr, sport)
|
|
|
|
# If we're testing connected sockets, connect the socket on the first
|
|
# netid now.
|
|
if use_connect:
|
|
netid = list(self.tuns.keys())[0]
|
|
self.SelectInterface(s, netid, mode)
|
|
s.connect((dstaddr, 53))
|
|
expected.src = self.MyAddress(version, netid)
|
|
|
|
# For each netid, select that network without closing the socket, and
|
|
# check that the packets sent on that socket go out on the right network.
|
|
#
|
|
# For connected sockets, routing is cached in the socket's destination
|
|
# cache entry. In this case, we check that selecting the network a second
|
|
# time on the same socket (except via SO_BINDTODEVICE, or SO_MARK on 5.0+
|
|
# kernels) does not change routing, but that subsequently invalidating the
|
|
# destination cache entry does. This is a bug in the kernel because
|
|
# re-selecting the netid should cause routing to change, and future
|
|
# kernels may fix this bug for per-UID routing and ucast_oif routing like
|
|
# they already have for mark-based routing. But until they do, this
|
|
# behaviour provides a convenient way to check that InvalidateDstCache
|
|
# actually works.
|
|
prevnetid = None
|
|
for netid in self.tuns:
|
|
self.SelectInterface(s, netid, mode)
|
|
if not use_connect:
|
|
expected.src = self.MyAddress(version, netid)
|
|
|
|
def ExpectSendUsesNetid(netid):
|
|
connected_str = "Connected" if use_connect else "Unconnected"
|
|
msg = "%s UDPv%d socket remarked using %s: expecting %s on %s" % (
|
|
connected_str, version, mode, desc, self.GetInterfaceName(netid))
|
|
if use_connect:
|
|
s.send(UDP_PAYLOAD)
|
|
else:
|
|
s.sendto(UDP_PAYLOAD, (dstaddr, 53))
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
# Does this socket have a stale dst cache entry that we need to clear?
|
|
def SocketHasStaleDstCacheEntry():
|
|
if not prevnetid:
|
|
# This is the first time we're marking the socket.
|
|
return False
|
|
if not use_connect:
|
|
# Non-connected sockets never have dst cache entries.
|
|
return False
|
|
if mode in ["uid", "ucast_oif"]:
|
|
# No kernel invalidates the dst cache entry if the UID or the
|
|
# UCAST_OIF socket option changes.
|
|
return True
|
|
if mode == "oif":
|
|
# Changing SO_BINDTODEVICE always invalidates the dst cache entry.
|
|
return False
|
|
if mode == "mark":
|
|
# Changing the mark invalidates the dst cache entry in 5.0+.
|
|
return net_test.LINUX_VERSION < (5, 0, 0)
|
|
raise AssertionError("%s must be one of %s" % (mode, modes))
|
|
|
|
if SocketHasStaleDstCacheEntry():
|
|
ExpectSendUsesNetid(prevnetid)
|
|
# ... until we invalidate it.
|
|
self.InvalidateDstCache(version, prevnetid)
|
|
|
|
# In any case, future sends must be correct.
|
|
ExpectSendUsesNetid(netid)
|
|
|
|
self.SelectInterface(s, None, mode)
|
|
prevnetid = netid
|
|
|
|
def testIPv4Remarking(self):
|
|
"""Checks that updating the mark on an IPv4 socket changes routing."""
|
|
self.CheckRemarking(4, False)
|
|
self.CheckRemarking(4, True)
|
|
|
|
def testIPv6Remarking(self):
|
|
"""Checks that updating the mark on an IPv6 socket changes routing."""
|
|
self.CheckRemarking(6, False)
|
|
self.CheckRemarking(6, True)
|
|
|
|
def testIPv6StickyPktinfo(self):
|
|
for _ in range(self.ITERATIONS):
|
|
for netid in self.tuns:
|
|
s = net_test.UDPSocket(AF_INET6)
|
|
|
|
# Set a flowlabel.
|
|
net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
|
|
s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
|
|
|
|
# Set some destination options.
|
|
nonce = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
|
|
dstopts = "".join([
|
|
"\x11\x02", # Next header=UDP, 24 bytes of options.
|
|
"\x01\x06", "\x00" * 6, # PadN, 6 bytes of padding.
|
|
"\x8b\x0c", # ILNP nonce, 12 bytes.
|
|
nonce
|
|
])
|
|
s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, dstopts)
|
|
s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 255)
|
|
|
|
pktinfo = multinetwork_base.MakePktInfo(6, None, self.ifindices[netid])
|
|
|
|
# Set the sticky pktinfo option.
|
|
s.setsockopt(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)
|
|
|
|
# Specify the flowlabel in the destination address.
|
|
s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 53, 0xdead, 0))
|
|
|
|
sport = s.getsockname()[1]
|
|
srcaddr = self.MyAddress(6, netid)
|
|
expected = (scapy.IPv6(src=srcaddr, dst=net_test.IPV6_ADDR,
|
|
fl=0xdead, hlim=255) /
|
|
scapy.IPv6ExtHdrDestOpt(
|
|
options=[scapy.PadN(optdata="\x00\x00\x00\x00\x00\x00"),
|
|
scapy.HBHOptUnknown(otype=0x8b,
|
|
optdata=nonce)]) /
|
|
scapy.UDP(sport=sport, dport=53) /
|
|
UDP_PAYLOAD)
|
|
msg = "IPv6 UDP using sticky pktinfo: expected UDP packet on %s" % (
|
|
self.GetInterfaceName(netid))
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
def CheckPktinfoRouting(self, version):
|
|
for _ in range(self.ITERATIONS):
|
|
for netid in self.tuns:
|
|
family = self.GetProtocolFamily(version)
|
|
s = net_test.UDPSocket(family)
|
|
|
|
if version == 6:
|
|
# Create a flowlabel so we can use it.
|
|
net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xbeef)
|
|
|
|
# Specify some arbitrary options.
|
|
# We declare the flowlabel as ctypes.c_uint32 because on a 32-bit
|
|
# Python interpreter an integer greater than 0x7fffffff (such as our
|
|
# chosen flowlabel after being passed through htonl) is converted to
|
|
# long, and _MakeMsgControl doesn't know what to do with longs.
|
|
cmsgs = [
|
|
(net_test.SOL_IPV6, IPV6_HOPLIMIT, 39),
|
|
(net_test.SOL_IPV6, IPV6_TCLASS, 0x83),
|
|
(net_test.SOL_IPV6, IPV6_FLOWINFO, ctypes.c_uint(htonl(0xbeef))),
|
|
]
|
|
else:
|
|
# Support for setting IPv4 TOS and TTL via cmsg only appeared in 3.13.
|
|
cmsgs = []
|
|
s.setsockopt(net_test.SOL_IP, IP_TTL, 39)
|
|
s.setsockopt(net_test.SOL_IP, IP_TOS, 0x83)
|
|
|
|
dstaddr = self.GetRemoteAddress(version)
|
|
self.SendOnNetid(version, s, dstaddr, 53, netid, UDP_PAYLOAD, cmsgs)
|
|
|
|
sport = s.getsockname()[1]
|
|
srcaddr = self.MyAddress(version, netid)
|
|
|
|
desc, expected = packets.UDPWithOptions(version, srcaddr, dstaddr,
|
|
sport=sport)
|
|
|
|
msg = "IPv%d UDP using pktinfo routing: expected %s on %s" % (
|
|
version, desc, self.GetInterfaceName(netid))
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
def testIPv4PktinfoRouting(self):
|
|
self.CheckPktinfoRouting(4)
|
|
|
|
def testIPv6PktinfoRouting(self):
|
|
self.CheckPktinfoRouting(6)
|
|
|
|
|
|
class MarkTest(multinetwork_base.InboundMarkingTest):
|
|
|
|
def CheckReflection(self, version, gen_packet, gen_reply):
|
|
"""Checks that replies go out on the same interface as the original.
|
|
|
|
For each combination:
|
|
- Calls gen_packet to generate a packet to that IP address.
|
|
- Writes the packet generated by gen_packet on the given tun
|
|
interface, causing the kernel to receive it.
|
|
- Checks that the kernel's reply matches the packet generated by
|
|
gen_reply.
|
|
|
|
Args:
|
|
version: An integer, 4 or 6.
|
|
gen_packet: A function taking an IP version (an integer), a source
|
|
address and a destination address (strings), and returning a scapy
|
|
packet.
|
|
gen_reply: A function taking the same arguments as gen_packet,
|
|
plus a scapy packet, and returning a scapy packet.
|
|
"""
|
|
for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
|
|
# Generate a test packet.
|
|
desc, packet = gen_packet(version, remoteaddr, myaddr)
|
|
|
|
# Test with mark reflection enabled and disabled.
|
|
for reflect in [0, 1]:
|
|
self.SetMarkReflectSysctls(reflect)
|
|
# HACK: IPv6 ping replies always do a routing lookup with the
|
|
# interface the ping came in on. So even if mark reflection is not
|
|
# working, IPv6 ping replies will be properly reflected. Don't
|
|
# fail when that happens.
|
|
if reflect or desc == "ICMPv6 echo":
|
|
reply_desc, reply = gen_reply(version, myaddr, remoteaddr, packet)
|
|
else:
|
|
reply_desc, reply = None, None
|
|
|
|
msg = self._FormatMessage(iif, ip_if, "reflect=%d" % reflect,
|
|
desc, reply_desc)
|
|
self._ReceiveAndExpectResponse(netid, packet, reply, msg)
|
|
|
|
def SYNToClosedPort(self, *args):
|
|
return packets.SYN(999, *args)
|
|
|
|
def testIPv4ICMPErrorsReflectMark(self):
|
|
self.CheckReflection(4, packets.UDP, packets.ICMPPortUnreachable)
|
|
|
|
def testIPv6ICMPErrorsReflectMark(self):
|
|
self.CheckReflection(6, packets.UDP, packets.ICMPPortUnreachable)
|
|
|
|
def testIPv4PingRepliesReflectMarkAndTos(self):
|
|
self.CheckReflection(4, packets.ICMPEcho, packets.ICMPReply)
|
|
|
|
def testIPv6PingRepliesReflectMarkAndTos(self):
|
|
self.CheckReflection(6, packets.ICMPEcho, packets.ICMPReply)
|
|
|
|
def testIPv4RSTsReflectMark(self):
|
|
self.CheckReflection(4, self.SYNToClosedPort, packets.RST)
|
|
|
|
def testIPv6RSTsReflectMark(self):
|
|
self.CheckReflection(6, self.SYNToClosedPort, packets.RST)
|
|
|
|
|
|
class TCPAcceptTest(multinetwork_base.InboundMarkingTest):
|
|
|
|
MODE_BINDTODEVICE = "SO_BINDTODEVICE"
|
|
MODE_INCOMING_MARK = "incoming mark"
|
|
MODE_EXPLICIT_MARK = "explicit mark"
|
|
MODE_UID = "uid"
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TCPAcceptTest, cls).setUpClass()
|
|
|
|
# Open a port so we can observe SYN+ACKs. Since it's a dual-stack socket it
|
|
# will accept both IPv4 and IPv6 connections. We do this here instead of in
|
|
# each test so we can use the same socket every time. That way, if a kernel
|
|
# bug causes incoming packets to mark the listening socket instead of the
|
|
# accepted socket, the test will fail as soon as the next address/interface
|
|
# combination is tried.
|
|
cls.listensocket = net_test.IPv6TCPSocket()
|
|
cls.listenport = net_test.BindRandomPort(6, cls.listensocket)
|
|
|
|
def _SetTCPMarkAcceptSysctl(self, value):
|
|
self.SetSysctl(TCP_MARK_ACCEPT_SYSCTL, value)
|
|
|
|
def CheckTCPConnection(self, mode, listensocket, netid, version,
|
|
myaddr, remoteaddr, packet, reply, msg):
|
|
establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
|
|
|
|
# Attempt to confuse the kernel.
|
|
self.InvalidateDstCache(version, netid)
|
|
|
|
self.ReceivePacketOn(netid, establishing_ack)
|
|
|
|
# If we're using UID routing, the accept() call has to be run as a UID that
|
|
# is routed to the specified netid, because the UID of the socket returned
|
|
# by accept() is the effective UID of the process that calls it. It doesn't
|
|
# need to be the same UID; any UID that selects the same interface will do.
|
|
with net_test.RunAsUid(self.UidForNetid(netid)):
|
|
s, _ = listensocket.accept()
|
|
|
|
try:
|
|
# Check that data sent on the connection goes out on the right interface.
|
|
desc, data = packets.ACK(version, myaddr, remoteaddr, establishing_ack,
|
|
payload=UDP_PAYLOAD)
|
|
s.send(UDP_PAYLOAD)
|
|
self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
|
|
self.InvalidateDstCache(version, netid)
|
|
|
|
# Keep up our end of the conversation.
|
|
ack = packets.ACK(version, remoteaddr, myaddr, data)[1]
|
|
self.InvalidateDstCache(version, netid)
|
|
self.ReceivePacketOn(netid, ack)
|
|
|
|
mark = self.GetSocketMark(s)
|
|
finally:
|
|
self.InvalidateDstCache(version, netid)
|
|
s.close()
|
|
self.InvalidateDstCache(version, netid)
|
|
|
|
if mode == self.MODE_INCOMING_MARK:
|
|
self.assertEqual(netid, mark & self.NETID_FWMASK,
|
|
msg + ": Accepted socket: Expected mark %d, got %d" % (
|
|
netid, mark))
|
|
elif mode != self.MODE_EXPLICIT_MARK:
|
|
self.assertEqual(0, self.GetSocketMark(listensocket))
|
|
|
|
# Check the FIN was sent on the right interface, and ack it. We don't expect
|
|
# this to fail because by the time the connection is established things are
|
|
# likely working, but a) extra tests are always good and b) extra packets
|
|
# like the FIN (and retransmitted FINs) could cause later tests that expect
|
|
# no packets to fail.
|
|
desc, fin = packets.FIN(version, myaddr, remoteaddr, ack)
|
|
self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
|
|
|
|
desc, finack = packets.FIN(version, remoteaddr, myaddr, fin)
|
|
self.ReceivePacketOn(netid, finack)
|
|
|
|
# Since we called close() earlier, the userspace socket object is gone, so
|
|
# the socket has no UID. If we're doing UID routing, the ack might be routed
|
|
# incorrectly. Not much we can do here.
|
|
desc, finackack = packets.ACK(version, myaddr, remoteaddr, finack)
|
|
self.ExpectPacketOn(netid, msg + ": expecting final ack", finackack)
|
|
|
|
def CheckTCP(self, version, modes):
|
|
"""Checks that incoming TCP connections work.
|
|
|
|
Args:
|
|
version: An integer, 4 or 6.
|
|
modes: A list of modes to excercise.
|
|
"""
|
|
for syncookies in [0, 2]:
|
|
for mode in modes:
|
|
for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
|
|
listensocket = self.listensocket
|
|
listenport = listensocket.getsockname()[1]
|
|
|
|
accept_sysctl = 1 if mode == self.MODE_INCOMING_MARK else 0
|
|
self._SetTCPMarkAcceptSysctl(accept_sysctl)
|
|
self.SetMarkReflectSysctls(accept_sysctl)
|
|
|
|
bound_dev = iif if mode == self.MODE_BINDTODEVICE else None
|
|
self.BindToDevice(listensocket, bound_dev)
|
|
|
|
mark = netid if mode == self.MODE_EXPLICIT_MARK else 0
|
|
self.SetSocketMark(listensocket, mark)
|
|
|
|
uid = self.UidForNetid(netid) if mode == self.MODE_UID else 0
|
|
os.fchown(listensocket.fileno(), uid, -1)
|
|
|
|
# Generate the packet here instead of in the outer loop, so
|
|
# subsequent TCP connections use different source ports and
|
|
# retransmissions from old connections don't confuse subsequent
|
|
# tests.
|
|
desc, packet = packets.SYN(listenport, version, remoteaddr, myaddr)
|
|
|
|
if mode:
|
|
reply_desc, reply = packets.SYNACK(version, myaddr, remoteaddr,
|
|
packet)
|
|
else:
|
|
reply_desc, reply = None, None
|
|
|
|
extra = "mode=%s, syncookies=%d" % (mode, syncookies)
|
|
msg = self._FormatMessage(iif, ip_if, extra, desc, reply_desc)
|
|
reply = self._ReceiveAndExpectResponse(netid, packet, reply, msg)
|
|
if reply:
|
|
self.CheckTCPConnection(mode, listensocket, netid, version, myaddr,
|
|
remoteaddr, packet, reply, msg)
|
|
|
|
def testBasicTCP(self):
|
|
self.CheckTCP(4, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
|
|
self.CheckTCP(6, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
|
|
|
|
def testIPv4MarkAccept(self):
|
|
self.CheckTCP(4, [self.MODE_INCOMING_MARK])
|
|
|
|
def testIPv6MarkAccept(self):
|
|
self.CheckTCP(6, [self.MODE_INCOMING_MARK])
|
|
|
|
def testIPv4UidAccept(self):
|
|
self.CheckTCP(4, [self.MODE_UID])
|
|
|
|
def testIPv6UidAccept(self):
|
|
self.CheckTCP(6, [self.MODE_UID])
|
|
|
|
def testIPv6ExplicitMark(self):
|
|
self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
|
|
|
|
@unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
|
|
"need support for per-table autoconf")
|
|
class RIOTest(multinetwork_base.MultiNetworkBaseTest):
|
|
"""Test for IPv6 RFC 4191 route information option
|
|
|
|
Relevant kernel commits:
|
|
upstream:
|
|
f104a567e673 ipv6: use rt6_get_dflt_router to get default router in rt6_route_rcv
|
|
bbea124bc99d net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
|
|
|
|
android-4.9:
|
|
d860b2e8a7f1 FROMLIST: net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs
|
|
|
|
android-4.4:
|
|
e953f89b8563 net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
|
|
|
|
android-4.1:
|
|
84f2f47716cd net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
|
|
|
|
android-3.18:
|
|
65f8936934fa net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
|
|
|
|
android-3.10:
|
|
161e88ebebc7 net: ipv6: Add sysctl for minimum prefix len acceptable in RIOs.
|
|
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(RIOTest, self).setUp()
|
|
self.NETID = random.choice(self.NETIDS)
|
|
self.IFACE = self.GetInterfaceName(self.NETID)
|
|
# return min/max plen to default values before each test case
|
|
self.SetAcceptRaRtInfoMinPlen(0)
|
|
self.SetAcceptRaRtInfoMaxPlen(0)
|
|
|
|
def GetRoutingTable(self):
|
|
return self._TableForNetid(self.NETID)
|
|
|
|
def SetAcceptRaRtInfoMinPlen(self, plen):
|
|
self.SetSysctl(
|
|
"/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_min_plen"
|
|
% self.IFACE, plen)
|
|
|
|
def GetAcceptRaRtInfoMinPlen(self):
|
|
return int(self.GetSysctl(
|
|
"/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_min_plen" % self.IFACE))
|
|
|
|
def SetAcceptRaRtInfoMaxPlen(self, plen):
|
|
self.SetSysctl(
|
|
"/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_max_plen"
|
|
% self.IFACE, plen)
|
|
|
|
def GetAcceptRaRtInfoMaxPlen(self):
|
|
return int(self.GetSysctl(
|
|
"/proc/sys/net/ipv6/conf/%s/accept_ra_rt_info_max_plen" % self.IFACE))
|
|
|
|
def SendRIO(self, rtlifetime, plen, prefix, prf):
|
|
options = scapy.ICMPv6NDOptRouteInfo(rtlifetime=rtlifetime, plen=plen,
|
|
prefix=prefix, prf=prf)
|
|
self.SendRA(self.NETID, options=(options,))
|
|
|
|
def FindRoutesWithDestination(self, destination):
|
|
canonical = net_test.CanonicalizeIPv6Address(destination)
|
|
return [r for _, r in self.iproute.DumpRoutes(6, self.GetRoutingTable())
|
|
if ('RTA_DST' in r and r['RTA_DST'] == canonical)]
|
|
|
|
def FindRoutesWithGateway(self):
|
|
return [r for _, r in self.iproute.DumpRoutes(6, self.GetRoutingTable())
|
|
if 'RTA_GATEWAY' in r]
|
|
|
|
def CountRoutes(self):
|
|
return len(self.iproute.DumpRoutes(6, self.GetRoutingTable()))
|
|
|
|
def GetRouteExpiration(self, route):
|
|
return float(route['RTA_CACHEINFO'].expires) / 100.0
|
|
|
|
def AssertExpirationInRange(self, routes, lifetime, epsilon):
|
|
self.assertTrue(routes)
|
|
found = False
|
|
# Assert that at least one route in routes has the expected lifetime
|
|
for route in routes:
|
|
expiration = self.GetRouteExpiration(route)
|
|
if expiration < lifetime - epsilon:
|
|
continue
|
|
if expiration > lifetime + epsilon:
|
|
continue
|
|
found = True
|
|
self.assertTrue(found)
|
|
|
|
def DelRA6(self, prefix, plen):
|
|
version = 6
|
|
netid = self.NETID
|
|
table = self._TableForNetid(netid)
|
|
router = self._RouterAddress(netid, version)
|
|
ifindex = self.ifindices[netid]
|
|
# We actually want to specify RTPROT_RA, however an upstream
|
|
# kernel bug causes RAs to be installed with RTPROT_BOOT.
|
|
if HAVE_RTPROT_RA:
|
|
rtprot = iproute.RTPROT_RA
|
|
else:
|
|
rtprot = iproute.RTPROT_BOOT
|
|
self.iproute._Route(version, rtprot, iproute.RTM_DELROUTE,
|
|
table, prefix, plen, router, ifindex, None, None)
|
|
|
|
def testSetAcceptRaRtInfoMinPlen(self):
|
|
for plen in range(-1, 130):
|
|
self.SetAcceptRaRtInfoMinPlen(plen)
|
|
self.assertEqual(plen, self.GetAcceptRaRtInfoMinPlen())
|
|
|
|
def testSetAcceptRaRtInfoMaxPlen(self):
|
|
for plen in range(-1, 130):
|
|
self.SetAcceptRaRtInfoMaxPlen(plen)
|
|
self.assertEqual(plen, self.GetAcceptRaRtInfoMaxPlen())
|
|
|
|
def testZeroRtLifetime(self):
|
|
PREFIX = "2001:db8:8901:2300::"
|
|
RTLIFETIME = 73500
|
|
PLEN = 56
|
|
PRF = 0
|
|
self.SetAcceptRaRtInfoMaxPlen(PLEN)
|
|
self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
self.assertTrue(self.FindRoutesWithDestination(PREFIX))
|
|
# RIO with rtlifetime = 0 should remove from routing table
|
|
self.SendRIO(0, PLEN, PREFIX, PRF)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
self.assertFalse(self.FindRoutesWithDestination(PREFIX))
|
|
|
|
def testMinPrefixLenRejection(self):
|
|
PREFIX = "2001:db8:8902:2345::"
|
|
RTLIFETIME = 70372
|
|
PRF = 0
|
|
# sweep from high to low to avoid spurious failures from late arrivals.
|
|
for plen in range(130, 1, -1):
|
|
self.SetAcceptRaRtInfoMinPlen(plen)
|
|
# RIO with plen < min_plen should be ignored
|
|
self.SendRIO(RTLIFETIME, plen - 1, PREFIX, PRF)
|
|
# Give the kernel time to notice our RAs
|
|
time.sleep(0.1)
|
|
# Expect no routes
|
|
routes = self.FindRoutesWithDestination(PREFIX)
|
|
self.assertFalse(routes)
|
|
|
|
def testMaxPrefixLenRejection(self):
|
|
PREFIX = "2001:db8:8903:2345::"
|
|
RTLIFETIME = 73078
|
|
PRF = 0
|
|
# sweep from low to high to avoid spurious failures from late arrivals.
|
|
for plen in range(-1, 128, 1):
|
|
self.SetAcceptRaRtInfoMaxPlen(plen)
|
|
# RIO with plen > max_plen should be ignored
|
|
self.SendRIO(RTLIFETIME, plen + 1, PREFIX, PRF)
|
|
# Give the kernel time to notice our RAs
|
|
time.sleep(0.1)
|
|
# Expect no routes
|
|
routes = self.FindRoutesWithDestination(PREFIX)
|
|
self.assertFalse(routes)
|
|
|
|
def testSimpleAccept(self):
|
|
PREFIX = "2001:db8:8904:2345::"
|
|
RTLIFETIME = 9993
|
|
PRF = 0
|
|
PLEN = 56
|
|
self.SetAcceptRaRtInfoMinPlen(48)
|
|
self.SetAcceptRaRtInfoMaxPlen(64)
|
|
self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
routes = self.FindRoutesWithGateway()
|
|
self.AssertExpirationInRange(routes, RTLIFETIME, 1)
|
|
self.DelRA6(PREFIX, PLEN)
|
|
|
|
def testEqualMinMaxAccept(self):
|
|
PREFIX = "2001:db8:8905:2345::"
|
|
RTLIFETIME = 6326
|
|
PLEN = 21
|
|
PRF = 0
|
|
self.SetAcceptRaRtInfoMinPlen(PLEN)
|
|
self.SetAcceptRaRtInfoMaxPlen(PLEN)
|
|
self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
routes = self.FindRoutesWithGateway()
|
|
self.AssertExpirationInRange(routes, RTLIFETIME, 1)
|
|
self.DelRA6(PREFIX, PLEN)
|
|
|
|
def testZeroLengthPrefix(self):
|
|
PREFIX = "2001:db8:8906:2345::"
|
|
RTLIFETIME = self.RA_VALIDITY * 2
|
|
PLEN = 0
|
|
PRF = 0
|
|
# Max plen = 0 still allows default RIOs!
|
|
self.SetAcceptRaRtInfoMaxPlen(PLEN)
|
|
self.SendRA(self.NETID)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
default = self.FindRoutesWithGateway()
|
|
self.AssertExpirationInRange(default, self.RA_VALIDITY, 1)
|
|
# RIO with prefix length = 0, should overwrite default route lifetime
|
|
# note that the RIO lifetime overwrites the RA lifetime.
|
|
self.SendRIO(RTLIFETIME, PLEN, PREFIX, PRF)
|
|
# Give the kernel time to notice our RA
|
|
time.sleep(0.01)
|
|
default = self.FindRoutesWithGateway()
|
|
self.AssertExpirationInRange(default, RTLIFETIME, 1)
|
|
self.DelRA6(PREFIX, PLEN)
|
|
|
|
def testManyRIOs(self):
|
|
RTLIFETIME = 68012
|
|
PLEN = 56
|
|
PRF = 0
|
|
COUNT = 1000
|
|
baseline = self.CountRoutes()
|
|
self.SetAcceptRaRtInfoMaxPlen(56)
|
|
# Send many RIOs compared to the expected number on a healthy system.
|
|
for i in range(0, COUNT):
|
|
prefix = "2001:db8:%x:1100::" % i
|
|
self.SendRIO(RTLIFETIME, PLEN, prefix, PRF)
|
|
time.sleep(0.1)
|
|
self.assertEqual(COUNT + baseline, self.CountRoutes())
|
|
for i in range(0, COUNT):
|
|
prefix = "2001:db8:%x:1100::" % i
|
|
self.DelRA6(prefix, PLEN)
|
|
# Expect that we can return to baseline config without lingering routes.
|
|
self.assertEqual(baseline, self.CountRoutes())
|
|
|
|
class RATest(multinetwork_base.MultiNetworkBaseTest):
|
|
|
|
ND_ROUTER_ADVERT = 134
|
|
ND_OPT_PREF64 = 38
|
|
Pref64Option = cstruct.Struct("pref64_option", "!BBH12s",
|
|
"type length lft_plc prefix")
|
|
|
|
def testDoesNotHaveObsoleteSysctl(self):
|
|
self.assertFalse(os.path.isfile(
|
|
"/proc/sys/net/ipv6/route/autoconf_table_offset"))
|
|
|
|
@unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
|
|
"no support for per-table autoconf")
|
|
def testPurgeDefaultRouters(self):
|
|
|
|
def CheckIPv6Connectivity(expect_connectivity):
|
|
for netid in self.NETIDS:
|
|
s = net_test.UDPSocket(AF_INET6)
|
|
self.SetSocketMark(s, netid)
|
|
if expect_connectivity:
|
|
self.assertTrue(s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 1234)))
|
|
else:
|
|
self.assertRaisesErrno(errno.ENETUNREACH, s.sendto, UDP_PAYLOAD,
|
|
(net_test.IPV6_ADDR, 1234))
|
|
|
|
try:
|
|
CheckIPv6Connectivity(True)
|
|
self.SetIPv6SysctlOnAllIfaces("accept_ra", 1)
|
|
self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
|
|
CheckIPv6Connectivity(False)
|
|
finally:
|
|
self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
|
|
for netid in self.NETIDS:
|
|
self.SendRA(netid)
|
|
CheckIPv6Connectivity(True)
|
|
|
|
def testOnlinkCommunication(self):
|
|
"""Checks that on-link communication goes direct and not through routers."""
|
|
for netid in self.tuns:
|
|
# Send a UDP packet to a random on-link destination.
|
|
s = net_test.UDPSocket(AF_INET6)
|
|
iface = self.GetInterfaceName(netid)
|
|
self.BindToDevice(s, iface)
|
|
# dstaddr can never be our address because GetRandomDestination only fills
|
|
# in the lower 32 bits, but our address has 0xff in the byte before that
|
|
# (since it's constructed from the EUI-64 and so has ff:fe in the middle).
|
|
dstaddr = self.GetRandomDestination(self.OnlinkPrefix(6, netid))
|
|
s.sendto(UDP_PAYLOAD, (dstaddr, 53))
|
|
|
|
# Expect an NS for that destination on the interface.
|
|
myaddr = self.MyAddress(6, netid)
|
|
mymac = self.MyMacAddress(netid)
|
|
desc, expected = packets.NS(myaddr, dstaddr, mymac)
|
|
msg = "Sending UDP packet to on-link destination: expecting %s" % desc
|
|
time.sleep(0.0001) # Required to make the test work on kernel 3.1(!)
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
# Send an NA.
|
|
tgtmac = "02:00:00:00:%02x:99" % netid
|
|
_, reply = packets.NA(dstaddr, myaddr, tgtmac)
|
|
# Don't use ReceivePacketOn, since that uses the router's MAC address as
|
|
# the source. Instead, construct our own Ethernet header with source
|
|
# MAC of tgtmac.
|
|
reply = scapy.Ether(src=tgtmac, dst=mymac) / reply
|
|
self.ReceiveEtherPacketOn(netid, reply)
|
|
|
|
# Expect the kernel to send the original UDP packet now that the ND cache
|
|
# entry has been populated.
|
|
sport = s.getsockname()[1]
|
|
desc, expected = packets.UDP(6, myaddr, dstaddr, sport=sport)
|
|
msg = "After NA response, expecting %s" % desc
|
|
self.ExpectPacketOn(netid, msg, expected)
|
|
|
|
# This test documents a known issue: routing tables are never deleted.
|
|
@unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
|
|
"no support for per-table autoconf")
|
|
def testLeftoverRoutes(self):
|
|
def GetNumRoutes():
|
|
return len(open("/proc/net/ipv6_route").readlines())
|
|
|
|
num_routes = GetNumRoutes()
|
|
for i in range(10, 20):
|
|
try:
|
|
self.tuns[i] = self.CreateTunInterface(i)
|
|
self.SendRA(i)
|
|
self.tuns[i].close()
|
|
finally:
|
|
del self.tuns[i]
|
|
self.assertLess(num_routes, GetNumRoutes())
|
|
|
|
def SendNdUseropt(self, option):
|
|
options = scapy.ICMPv6NDOptRouteInfo(rtlifetime=rtlifetime, plen=plen,
|
|
prefix=prefix, prf=prf)
|
|
self.SendRA(self.NETID, options=(options,))
|
|
|
|
def MakePref64Option(self, prefix, lifetime):
|
|
prefix = inet_pton(AF_INET6, prefix)[:12]
|
|
lft_plc = (lifetime & 0xfff8) | 0 # 96-bit prefix length
|
|
return self.Pref64Option((self.ND_OPT_PREF64, 2, lft_plc, prefix))
|
|
|
|
@unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not backported")
|
|
def testPref64UserOption(self):
|
|
# Open a netlink socket to receive RTM_NEWNDUSEROPT messages.
|
|
s = netlink.NetlinkSocket(netlink.NETLINK_ROUTE, iproute.RTMGRP_ND_USEROPT)
|
|
|
|
# Send an RA with the PREF64 option.
|
|
netid = random.choice(self.NETIDS)
|
|
opt = self.MakePref64Option("64:ff9b::", 300)
|
|
self.SendRA(netid, options=(opt.Pack(),))
|
|
|
|
# Check that we get an an RTM_NEWNDUSEROPT message on the socket with the
|
|
# expected option.
|
|
csocket.SetSocketTimeout(s.sock, 100)
|
|
try:
|
|
data = s._Recv()
|
|
except IOError as e:
|
|
self.fail("Should have received an RTM_NEWNDUSEROPT message. "
|
|
"Please ensure the kernel supports receiving the "
|
|
"PREF64 RA option. Error: %s" % e)
|
|
|
|
# Check that the message is received correctly.
|
|
nlmsghdr, data = cstruct.Read(data, netlink.NLMsgHdr)
|
|
self.assertEqual(iproute.RTM_NEWNDUSEROPT, nlmsghdr.type)
|
|
|
|
# Check the option contents.
|
|
ndopthdr, data = cstruct.Read(data, iproute.NdUseroptMsg)
|
|
self.assertEqual(AF_INET6, ndopthdr.family)
|
|
self.assertEqual(self.ND_ROUTER_ADVERT, ndopthdr.icmp_type)
|
|
self.assertEqual(len(opt), ndopthdr.opts_len)
|
|
|
|
actual_opt = self.Pref64Option(data)
|
|
self.assertEqual(opt, actual_opt)
|
|
|
|
|
|
|
|
class PMTUTest(multinetwork_base.InboundMarkingTest):
|
|
|
|
PAYLOAD_SIZE = 1400
|
|
dstaddrs = set()
|
|
|
|
def GetSocketMTU(self, version, s):
|
|
if version == 6:
|
|
ip6_mtuinfo = s.getsockopt(net_test.SOL_IPV6, csocket.IPV6_PATHMTU, 32)
|
|
unused_sockaddr, mtu = struct.unpack("=28sI", ip6_mtuinfo)
|
|
return mtu
|
|
else:
|
|
return s.getsockopt(net_test.SOL_IP, csocket.IP_MTU)
|
|
|
|
def DisableFragmentationAndReportErrors(self, version, s):
|
|
if version == 4:
|
|
s.setsockopt(net_test.SOL_IP, csocket.IP_MTU_DISCOVER,
|
|
csocket.IP_PMTUDISC_DO)
|
|
s.setsockopt(net_test.SOL_IP, net_test.IP_RECVERR, 1)
|
|
else:
|
|
s.setsockopt(net_test.SOL_IPV6, csocket.IPV6_DONTFRAG, 1)
|
|
s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
|
|
|
|
def CheckPMTU(self, version, use_connect, modes):
|
|
|
|
def SendBigPacket(version, s, dstaddr, netid, payload):
|
|
if use_connect:
|
|
s.send(payload)
|
|
else:
|
|
self.SendOnNetid(version, s, dstaddr, 1234, netid, payload, [])
|
|
|
|
for netid in self.tuns:
|
|
for mode in modes:
|
|
s = self.BuildSocket(version, net_test.UDPSocket, netid, mode)
|
|
self.DisableFragmentationAndReportErrors(version, s)
|
|
|
|
srcaddr = self.MyAddress(version, netid)
|
|
dst_prefix, intermediate = {
|
|
4: ("172.19.", "172.16.9.12"),
|
|
6: ("2001:db8::", "2001:db8::1")
|
|
}[version]
|
|
|
|
# Run this test often enough (e.g., in presubmits), and eventually
|
|
# we'll be unlucky enough to pick the same address twice, in which
|
|
# case the test will fail because the kernel will already have seen
|
|
# the lower MTU. Don't do this.
|
|
dstaddr = self.GetRandomDestination(dst_prefix)
|
|
while dstaddr in self.dstaddrs:
|
|
dstaddr = self.GetRandomDestination(dst_prefix)
|
|
self.dstaddrs.add(dstaddr)
|
|
|
|
if use_connect:
|
|
s.connect((dstaddr, 1234))
|
|
|
|
payload = self.PAYLOAD_SIZE * "a"
|
|
|
|
# Send a packet and receive a packet too big.
|
|
SendBigPacket(version, s, dstaddr, netid, payload)
|
|
received = self.ReadAllPacketsOn(netid)
|
|
self.assertEqual(1, len(received),
|
|
"unexpected packets: %s" % received[1:])
|
|
_, toobig = packets.ICMPPacketTooBig(version, intermediate, srcaddr,
|
|
received[0])
|
|
self.ReceivePacketOn(netid, toobig)
|
|
|
|
# Check that another send on the same socket returns EMSGSIZE.
|
|
self.assertRaisesErrno(
|
|
errno.EMSGSIZE,
|
|
SendBigPacket, version, s, dstaddr, netid, payload)
|
|
|
|
# If this is a connected socket, make sure the socket MTU was set.
|
|
# Note that in IPv4 this only started working in Linux 3.6!
|
|
if use_connect and (version == 6 or net_test.LINUX_VERSION >= (3, 6)):
|
|
self.assertEqual(packets.PTB_MTU, self.GetSocketMTU(version, s))
|
|
|
|
s.close()
|
|
|
|
# Check that other sockets pick up the PMTU we have been told about by
|
|
# connecting another socket to the same destination and getting its MTU.
|
|
# This new socket can use any method to select its outgoing interface;
|
|
# here we use a mark for simplicity.
|
|
s2 = self.BuildSocket(version, net_test.UDPSocket, netid, "mark")
|
|
s2.connect((dstaddr, 1234))
|
|
self.assertEqual(packets.PTB_MTU, self.GetSocketMTU(version, s2))
|
|
|
|
# Also check the MTU reported by ip route get, this time using the oif.
|
|
routes = self.iproute.GetRoutes(dstaddr, self.ifindices[netid], 0, None)
|
|
self.assertTrue(routes)
|
|
route = routes[0]
|
|
rtmsg, attributes = route
|
|
self.assertEqual(iproute.RTN_UNICAST, rtmsg.type)
|
|
metrics = attributes["RTA_METRICS"]
|
|
self.assertEqual(packets.PTB_MTU, metrics["RTAX_MTU"])
|
|
|
|
def testIPv4BasicPMTU(self):
|
|
"""Tests IPv4 path MTU discovery.
|
|
|
|
Relevant kernel commits:
|
|
upstream net-next:
|
|
6a66271 ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
|
|
|
|
android-3.10:
|
|
4bc64dd ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
|
|
"""
|
|
|
|
self.CheckPMTU(4, True, ["mark", "oif"])
|
|
self.CheckPMTU(4, False, ["mark", "oif"])
|
|
|
|
def testIPv6BasicPMTU(self):
|
|
self.CheckPMTU(6, True, ["mark", "oif"])
|
|
self.CheckPMTU(6, False, ["mark", "oif"])
|
|
|
|
def testIPv4UIDPMTU(self):
|
|
self.CheckPMTU(4, True, ["uid"])
|
|
self.CheckPMTU(4, False, ["uid"])
|
|
|
|
def testIPv6UIDPMTU(self):
|
|
self.CheckPMTU(6, True, ["uid"])
|
|
self.CheckPMTU(6, False, ["uid"])
|
|
|
|
# Making Path MTU Discovery work on unmarked sockets requires that mark
|
|
# reflection be enabled. Otherwise the kernel has no way to know what routing
|
|
# table the original packet used, and thus it won't be able to clone the
|
|
# correct route.
|
|
|
|
def testIPv4UnmarkedSocketPMTU(self):
|
|
self.SetMarkReflectSysctls(1)
|
|
try:
|
|
self.CheckPMTU(4, False, [None])
|
|
finally:
|
|
self.SetMarkReflectSysctls(0)
|
|
|
|
def testIPv6UnmarkedSocketPMTU(self):
|
|
self.SetMarkReflectSysctls(1)
|
|
try:
|
|
self.CheckPMTU(6, False, [None])
|
|
finally:
|
|
self.SetMarkReflectSysctls(0)
|
|
|
|
|
|
class UidRoutingTest(multinetwork_base.MultiNetworkBaseTest):
|
|
"""Tests that per-UID routing works properly.
|
|
|
|
Relevant kernel commits:
|
|
upstream net-next:
|
|
7d99569460 net: ipv4: Don't crash if passing a null sk to ip_do_redirect.
|
|
d109e61bfe net: ipv4: Don't crash if passing a null sk to ip_rt_update_pmtu.
|
|
35b80733b3 net: core: add missing check for uid_range in rule_exists.
|
|
e2d118a1cb net: inet: Support UID-based routing in IP protocols.
|
|
622ec2c9d5 net: core: add UID to flows, rules, and routes
|
|
86741ec254 net: core: Add a UID field to struct sock.
|
|
|
|
android-3.18:
|
|
b004e79504 net: ipv4: Don't crash if passing a null sk to ip_rt_update_pmtu.
|
|
04c0eace81 net: inet: Support UID-based routing in IP protocols.
|
|
18c36d7b71 net: core: add UID to flows, rules, and routes
|
|
80e3440721 net: core: Add a UID field to struct sock.
|
|
fa8cc2c30c Revert "net: core: Support UID-based routing."
|
|
b585141890 Revert "Handle 'sk' being NULL in UID-based routing."
|
|
5115ab7514 Revert "net: core: fix UID-based routing build"
|
|
f9f4281f79 Revert "ANDROID: net: fib: remove duplicate assignment"
|
|
|
|
android-4.4:
|
|
341965cf10 net: ipv4: Don't crash if passing a null sk to ip_rt_update_pmtu.
|
|
344afd627c net: inet: Support UID-based routing in IP protocols.
|
|
03441d56d8 net: core: add UID to flows, rules, and routes
|
|
eb964bdba7 net: core: Add a UID field to struct sock.
|
|
9789b697c6 Revert "net: core: Support UID-based routing."
|
|
"""
|
|
|
|
def GetRulesAtPriority(self, version, priority):
|
|
rules = self.iproute.DumpRules(version)
|
|
out = [(rule, attributes) for rule, attributes in rules
|
|
if attributes.get("FRA_PRIORITY", 0) == priority]
|
|
return out
|
|
|
|
def CheckInitialTablesHaveNoUIDs(self, version):
|
|
rules = []
|
|
for priority in [0, 32766, 32767]:
|
|
rules.extend(self.GetRulesAtPriority(version, priority))
|
|
for _, attributes in rules:
|
|
self.assertNotIn("FRA_UID_RANGE", attributes)
|
|
|
|
def testIPv4InitialTablesHaveNoUIDs(self):
|
|
self.CheckInitialTablesHaveNoUIDs(4)
|
|
|
|
def testIPv6InitialTablesHaveNoUIDs(self):
|
|
self.CheckInitialTablesHaveNoUIDs(6)
|
|
|
|
@staticmethod
|
|
def _Random():
|
|
return random.randint(1000000, 2000000)
|
|
|
|
def CheckGetAndSetRules(self, version):
|
|
start, end = tuple(sorted([self._Random(), self._Random()]))
|
|
table = self._Random()
|
|
priority = self._Random()
|
|
|
|
# Can't create a UID range to UID -1 because -1 is INVALID_UID...
|
|
self.assertRaisesErrno(
|
|
errno.EINVAL,
|
|
self.iproute.UidRangeRule, version, True, 100, 0xffffffff, table,
|
|
priority)
|
|
|
|
# ... but -2 is valid.
|
|
self.iproute.UidRangeRule(version, True, 100, 0xfffffffe, table, priority)
|
|
self.iproute.UidRangeRule(version, False, 100, 0xfffffffe, table, priority)
|
|
|
|
try:
|
|
# Create a UID range rule.
|
|
self.iproute.UidRangeRule(version, True, start, end, table, priority)
|
|
|
|
# Check that deleting the wrong UID range doesn't work.
|
|
self.assertRaisesErrno(
|
|
errno.ENOENT,
|
|
self.iproute.UidRangeRule, version, False, start, end + 1, table,
|
|
priority)
|
|
self.assertRaisesErrno(errno.ENOENT,
|
|
self.iproute.UidRangeRule, version, False, start + 1, end, table,
|
|
priority)
|
|
|
|
# Check that the UID range appears in dumps.
|
|
rules = self.GetRulesAtPriority(version, priority)
|
|
self.assertTrue(rules)
|
|
_, attributes = rules[-1]
|
|
self.assertEqual(priority, attributes["FRA_PRIORITY"])
|
|
uidrange = attributes["FRA_UID_RANGE"]
|
|
self.assertEqual(start, uidrange.start)
|
|
self.assertEqual(end, uidrange.end)
|
|
self.assertEqual(table, attributes["FRA_TABLE"])
|
|
finally:
|
|
self.iproute.UidRangeRule(version, False, start, end, table, priority)
|
|
self.assertRaisesErrno(
|
|
errno.ENOENT,
|
|
self.iproute.UidRangeRule, version, False, start, end, table,
|
|
priority)
|
|
|
|
fwmask = 0xfefefefe
|
|
try:
|
|
# Create a rule without a UID range.
|
|
self.iproute.FwmarkRule(version, True, 300, fwmask, 301, priority + 1)
|
|
|
|
# Check it doesn't have a UID range.
|
|
rules = self.GetRulesAtPriority(version, priority + 1)
|
|
self.assertTrue(rules)
|
|
for _, attributes in rules:
|
|
self.assertIn("FRA_TABLE", attributes)
|
|
self.assertNotIn("FRA_UID_RANGE", attributes)
|
|
finally:
|
|
self.iproute.FwmarkRule(version, False, 300, fwmask, 301, priority + 1)
|
|
|
|
# Test that EEXIST worksfor UID range rules too. This behaviour was only
|
|
# added in 4.8.
|
|
if net_test.LINUX_VERSION >= (4, 8, 0):
|
|
ranges = [(100, 101), (100, 102), (99, 101), (1234, 5678)]
|
|
dup = ranges[0]
|
|
try:
|
|
# Check that otherwise identical rules with different UID ranges can be
|
|
# created without EEXIST.
|
|
for start, end in ranges:
|
|
self.iproute.UidRangeRule(version, True, start, end, table, priority)
|
|
# ... but EEXIST is returned if the UID range is identical.
|
|
self.assertRaisesErrno(
|
|
errno.EEXIST,
|
|
self.iproute.UidRangeRule, version, True, dup[0], dup[1], table,
|
|
priority)
|
|
finally:
|
|
# Clean up.
|
|
for start, end in ranges + [dup]:
|
|
try:
|
|
self.iproute.UidRangeRule(version, False, start, end, table,
|
|
priority)
|
|
except IOError:
|
|
pass
|
|
|
|
def testIPv4GetAndSetRules(self):
|
|
self.CheckGetAndSetRules(4)
|
|
|
|
def testIPv6GetAndSetRules(self):
|
|
self.CheckGetAndSetRules(6)
|
|
|
|
@unittest.skipUnless(net_test.LINUX_VERSION >= (4, 9, 0), "not backported")
|
|
def testDeleteErrno(self):
|
|
for version in [4, 6]:
|
|
table = self._Random()
|
|
priority = self._Random()
|
|
self.assertRaisesErrno(
|
|
errno.EINVAL,
|
|
self.iproute.UidRangeRule, version, False, 100, 0xffffffff, table,
|
|
priority)
|
|
|
|
def ExpectNoRoute(self, addr, oif, mark, uid):
|
|
# The lack of a route may be either an error, or an unreachable route.
|
|
try:
|
|
routes = self.iproute.GetRoutes(addr, oif, mark, uid)
|
|
rtmsg, _ = routes[0]
|
|
self.assertEqual(iproute.RTN_UNREACHABLE, rtmsg.type)
|
|
except IOError as e:
|
|
if int(e.errno) != int(errno.ENETUNREACH):
|
|
raise e
|
|
|
|
def ExpectRoute(self, addr, oif, mark, uid):
|
|
routes = self.iproute.GetRoutes(addr, oif, mark, uid)
|
|
rtmsg, _ = routes[0]
|
|
self.assertEqual(iproute.RTN_UNICAST, rtmsg.type)
|
|
|
|
def CheckGetRoute(self, version, addr):
|
|
self.ExpectNoRoute(addr, 0, 0, 0)
|
|
for netid in self.NETIDS:
|
|
uid = self.UidForNetid(netid)
|
|
self.ExpectRoute(addr, 0, 0, uid)
|
|
self.ExpectNoRoute(addr, 0, 0, 0)
|
|
|
|
def testIPv4RouteGet(self):
|
|
self.CheckGetRoute(4, net_test.IPV4_ADDR)
|
|
|
|
def testIPv6RouteGet(self):
|
|
self.CheckGetRoute(6, net_test.IPV6_ADDR)
|
|
|
|
def testChangeFdAttributes(self):
|
|
netid = random.choice(self.NETIDS)
|
|
uid = self._Random()
|
|
table = self._TableForNetid(netid)
|
|
remoteaddr = self.GetRemoteAddress(6)
|
|
s = socket(AF_INET6, SOCK_DGRAM, 0)
|
|
|
|
def CheckSendFails():
|
|
self.assertRaisesErrno(errno.ENETUNREACH,
|
|
s.sendto, "foo", (remoteaddr, 53))
|
|
def CheckSendSucceeds():
|
|
self.assertEqual(len("foo"), s.sendto("foo", (remoteaddr, 53)))
|
|
|
|
CheckSendFails()
|
|
self.iproute.UidRangeRule(6, True, uid, uid, table, self.PRIORITY_UID)
|
|
try:
|
|
CheckSendFails()
|
|
os.fchown(s.fileno(), uid, -1)
|
|
CheckSendSucceeds()
|
|
os.fchown(s.fileno(), -1, -1)
|
|
CheckSendSucceeds()
|
|
os.fchown(s.fileno(), -1, 12345)
|
|
CheckSendSucceeds()
|
|
os.fchmod(s.fileno(), 0o777)
|
|
CheckSendSucceeds()
|
|
os.fchown(s.fileno(), 0, -1)
|
|
CheckSendFails()
|
|
finally:
|
|
self.iproute.UidRangeRule(6, False, uid, uid, table, self.PRIORITY_UID)
|
|
|
|
|
|
class RulesTest(net_test.NetworkTest):
|
|
|
|
RULE_PRIORITY = 99999
|
|
FWMASK = 0xffffffff
|
|
|
|
def setUp(self):
|
|
self.iproute = iproute.IPRoute()
|
|
for version in [4, 6]:
|
|
self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
|
|
|
|
def tearDown(self):
|
|
for version in [4, 6]:
|
|
self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
|
|
|
|
def testRuleDeletionMatchesTable(self):
|
|
for version in [4, 6]:
|
|
# Add rules with mark 300 pointing at tables 301 and 302.
|
|
# This checks for a kernel bug where deletion request for tables > 256
|
|
# ignored the table.
|
|
self.iproute.FwmarkRule(version, True, 300, self.FWMASK, 301,
|
|
priority=self.RULE_PRIORITY)
|
|
self.iproute.FwmarkRule(version, True, 300, self.FWMASK, 302,
|
|
priority=self.RULE_PRIORITY)
|
|
# Delete rule with mark 300 pointing at table 302.
|
|
self.iproute.FwmarkRule(version, False, 300, self.FWMASK, 302,
|
|
priority=self.RULE_PRIORITY)
|
|
# Check that the rule pointing at table 301 is still around.
|
|
attributes = [a for _, a in self.iproute.DumpRules(version)
|
|
if a.get("FRA_PRIORITY", 0) == self.RULE_PRIORITY]
|
|
self.assertEqual(1, len(attributes))
|
|
self.assertEqual(301, attributes[0]["FRA_TABLE"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|