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.

601 lines
22 KiB

#!/usr/bin/python
#
# Copyright 2016 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 ctypes
import errno
import os
import socket
import subprocess
import tempfile
import unittest
import bpf
from bpf import BPF_ADD
from bpf import BPF_AND
from bpf import BPF_CGROUP_INET_EGRESS
from bpf import BPF_CGROUP_INET_INGRESS
from bpf import BPF_CGROUP_INET_SOCK_CREATE
from bpf import BPF_DW
from bpf import BPF_F_RDONLY
from bpf import BPF_F_WRONLY
from bpf import BPF_FUNC_get_current_uid_gid
from bpf import BPF_FUNC_get_socket_cookie
from bpf import BPF_FUNC_get_socket_uid
from bpf import BPF_FUNC_ktime_get_boot_ns
from bpf import BPF_FUNC_ktime_get_ns
from bpf import BPF_FUNC_map_lookup_elem
from bpf import BPF_FUNC_map_update_elem
from bpf import BPF_FUNC_skb_change_head
from bpf import BPF_JNE
from bpf import BPF_MAP_TYPE_HASH
from bpf import BPF_PROG_TYPE_CGROUP_SKB
from bpf import BPF_PROG_TYPE_CGROUP_SOCK
from bpf import BPF_PROG_TYPE_SCHED_CLS
from bpf import BPF_PROG_TYPE_SOCKET_FILTER
from bpf import BPF_REG_0
from bpf import BPF_REG_1
from bpf import BPF_REG_10
from bpf import BPF_REG_2
from bpf import BPF_REG_3
from bpf import BPF_REG_4
from bpf import BPF_REG_6
from bpf import BPF_REG_7
from bpf import BPF_STX
from bpf import BPF_W
from bpf import BPF_XADD
from bpf import BpfAlu64Imm
from bpf import BpfExitInsn
from bpf import BpfFuncCall
from bpf import BpfJumpImm
from bpf import BpfLdxMem
from bpf import BpfLoadMapFd
from bpf import BpfMov64Imm
from bpf import BpfMov64Reg
from bpf import BpfProgAttach
from bpf import BpfProgAttachSocket
from bpf import BpfProgDetach
from bpf import BpfProgLoad
from bpf import BpfRawInsn
from bpf import BpfStMem
from bpf import BpfStxMem
from bpf import CreateMap
from bpf import DeleteMap
from bpf import GetFirstKey
from bpf import GetNextKey
from bpf import LookupMap
from bpf import UpdateMap
import csocket
import net_test
from net_test import LINUX_VERSION
import sock_diag
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
HAVE_EBPF_ACCOUNTING = bpf.HAVE_EBPF_4_9
HAVE_EBPF_SOCKET = bpf.HAVE_EBPF_4_14
# bpf_ktime_get_ns() was made non-GPL requiring in 5.8 and at the same time
# bpf_ktime_get_boot_ns() was added, both of these changes were backported to
# Android Common Kernel in 4.14.221, 4.19.175, 5.4.97.
# As such we require 4.14.222+ 4.19.176+ 5.4.98+ 5.8.0+,
# but since we only really care about LTS releases:
HAVE_EBPF_KTIME_GET_NS_APACHE2 = (
((LINUX_VERSION > (4, 14, 221)) and (LINUX_VERSION < (4, 19, 0))) or
((LINUX_VERSION > (4, 19, 175)) and (LINUX_VERSION < (5, 4, 0))) or
(LINUX_VERSION > (5, 4, 97))
)
HAVE_EBPF_KTIME_GET_BOOT_NS = HAVE_EBPF_KTIME_GET_NS_APACHE2
KEY_SIZE = 8
VALUE_SIZE = 4
TOTAL_ENTRIES = 20
TEST_UID = 54321
TEST_GID = 12345
# Offset to store the map key in stack register REG10
key_offset = -8
# Offset to store the map value in stack register REG10
value_offset = -16
# Debug usage only.
def PrintMapInfo(map_fd):
# A random key that the map does not contain.
key = 10086
while 1:
try:
next_key = GetNextKey(map_fd, key).value
value = LookupMap(map_fd, next_key)
print(repr(next_key) + " : " + repr(value.value)) # pylint: disable=superfluous-parens
key = next_key
except socket.error:
print("no value") # pylint: disable=superfluous-parens
break
# A dummy loopback function that causes a socket to send traffic to itself.
def SocketUDPLoopBack(packet_count, version, prog_fd):
family = {4: socket.AF_INET, 6: socket.AF_INET6}[version]
sock = socket.socket(family, socket.SOCK_DGRAM, 0)
if prog_fd is not None:
BpfProgAttachSocket(sock.fileno(), prog_fd)
net_test.SetNonBlocking(sock)
addr = {4: "127.0.0.1", 6: "::1"}[version]
sock.bind((addr, 0))
addr = sock.getsockname()
sockaddr = csocket.Sockaddr(addr)
for _ in range(packet_count):
sock.sendto("foo", addr)
data, retaddr = csocket.Recvfrom(sock, 4096, 0)
assert "foo" == data
assert sockaddr == retaddr
return sock
# The main code block for eBPF packet counting program. It takes a preloaded
# key from BPF_REG_0 and use it to look up the bpf map, if the element does not
# exist in the map yet, the program will update the map with a new <key, 1>
# pair. Otherwise it will jump to next code block to handle it.
# REG0: regiter storing return value from helper function and the final return
# value of eBPF program.
# REG1 - REG5: temporary register used for storing values and load parameters
# into eBPF helper function. After calling helper function, the value for these
# registers will be reset.
# REG6 - REG9: registers store values that will not be cleared when calling
# eBPF helper function.
# REG10: A stack stores values need to be accessed by the address. Program can
# retrieve the address of a value by specifying the position of the value in
# the stack.
def BpfFuncCountPacketInit(map_fd):
key_pos = BPF_REG_7
return [
# Get a preloaded key from BPF_REG_0 and store it at BPF_REG_7
BpfMov64Reg(key_pos, BPF_REG_10),
BpfAlu64Imm(BPF_ADD, key_pos, key_offset),
# Load map fd and look up the key in the map
BpfLoadMapFd(map_fd, BPF_REG_1),
BpfMov64Reg(BPF_REG_2, key_pos),
BpfFuncCall(BPF_FUNC_map_lookup_elem),
# if the map element already exist, jump out of this
# code block and let next part to handle it
BpfJumpImm(BPF_AND, BPF_REG_0, 0, 10),
BpfLoadMapFd(map_fd, BPF_REG_1),
BpfMov64Reg(BPF_REG_2, key_pos),
# Initial a new <key, value> pair with value equal to 1 and update to map
BpfStMem(BPF_W, BPF_REG_10, value_offset, 1),
BpfMov64Reg(BPF_REG_3, BPF_REG_10),
BpfAlu64Imm(BPF_ADD, BPF_REG_3, value_offset),
BpfMov64Imm(BPF_REG_4, 0),
BpfFuncCall(BPF_FUNC_map_update_elem)
]
INS_BPF_EXIT_BLOCK = [
BpfMov64Imm(BPF_REG_0, 0),
BpfExitInsn()
]
# Bpf instruction for cgroup bpf filter to accept a packet and exit.
INS_CGROUP_ACCEPT = [
# Set return value to 1 and exit.
BpfMov64Imm(BPF_REG_0, 1),
BpfExitInsn()
]
# Bpf instruction for socket bpf filter to accept a packet and exit.
INS_SK_FILTER_ACCEPT = [
# Precondition: BPF_REG_6 = sk_buff context
# Load the packet length from BPF_REG_6 and store it in BPF_REG_0 as the
# return value.
BpfLdxMem(BPF_W, BPF_REG_0, BPF_REG_6, 0),
BpfExitInsn()
]
# Update a existing map element with +1.
INS_PACK_COUNT_UPDATE = [
# Precondition: BPF_REG_0 = Value retrieved from BPF maps
# Add one to the corresponding eBPF value field for a specific eBPF key.
BpfMov64Reg(BPF_REG_2, BPF_REG_0),
BpfMov64Imm(BPF_REG_1, 1),
BpfRawInsn(BPF_STX | BPF_XADD | BPF_W, BPF_REG_2, BPF_REG_1, 0, 0),
]
INS_BPF_PARAM_STORE = [
BpfStxMem(BPF_DW, BPF_REG_10, BPF_REG_0, key_offset),
]
@unittest.skipUnless(HAVE_EBPF_ACCOUNTING,
"BPF helper function is not fully supported")
class BpfTest(net_test.NetworkTest):
def setUp(self):
super(BpfTest, self).setUp()
self.map_fd = -1
self.prog_fd = -1
self.sock = None
def tearDown(self):
if self.prog_fd >= 0:
os.close(self.prog_fd)
if self.map_fd >= 0:
os.close(self.map_fd)
if self.sock:
self.sock.close()
super(BpfTest, self).tearDown()
def testCreateMap(self):
key, value = 1, 1
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
UpdateMap(self.map_fd, key, value)
self.assertEqual(value, LookupMap(self.map_fd, key).value)
DeleteMap(self.map_fd, key)
self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, key)
def CheckAllMapEntry(self, nonexistent_key, total_entries, value):
count = 0
key = nonexistent_key
while True:
if count == total_entries:
self.assertRaisesErrno(errno.ENOENT, GetNextKey, self.map_fd, key)
break
else:
result = GetNextKey(self.map_fd, key)
key = result.value
self.assertGreaterEqual(key, 0)
self.assertEqual(value, LookupMap(self.map_fd, key).value)
count += 1
def testIterateMap(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
value = 1024
for key in range(0, TOTAL_ENTRIES):
UpdateMap(self.map_fd, key, value)
for key in range(0, TOTAL_ENTRIES):
self.assertEqual(value, LookupMap(self.map_fd, key).value)
self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, 101)
nonexistent_key = -1
self.CheckAllMapEntry(nonexistent_key, TOTAL_ENTRIES, value)
def testFindFirstMapKey(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
value = 1024
for key in range(0, TOTAL_ENTRIES):
UpdateMap(self.map_fd, key, value)
first_key = GetFirstKey(self.map_fd)
key = first_key.value
self.CheckAllMapEntry(key, TOTAL_ENTRIES - 1, value)
def testRdOnlyMap(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES, map_flags=BPF_F_RDONLY)
value = 1024
key = 1
self.assertRaisesErrno(errno.EPERM, UpdateMap, self.map_fd, key, value)
self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, key)
def testWrOnlyMap(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES, map_flags=BPF_F_WRONLY)
value = 1024
key = 1
UpdateMap(self.map_fd, key, value)
self.assertRaisesErrno(errno.EPERM, LookupMap, self.map_fd, key)
def testProgLoad(self):
# Move skb to BPF_REG_6 for further usage
instructions = [
BpfMov64Reg(BPF_REG_6, BPF_REG_1)
]
instructions += INS_SK_FILTER_ACCEPT
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, instructions)
SocketUDPLoopBack(1, 4, self.prog_fd)
SocketUDPLoopBack(1, 6, self.prog_fd)
def testPacketBlock(self):
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, INS_BPF_EXIT_BLOCK)
self.assertRaisesErrno(errno.EAGAIN, SocketUDPLoopBack, 1, 4, self.prog_fd)
self.assertRaisesErrno(errno.EAGAIN, SocketUDPLoopBack, 1, 6, self.prog_fd)
def testPacketCount(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
key = 0xf0f0
# Set up instruction block with key loaded at BPF_REG_0.
instructions = [
BpfMov64Reg(BPF_REG_6, BPF_REG_1),
BpfMov64Imm(BPF_REG_0, key)
]
# Concatenate the generic packet count bpf program to it.
instructions += (INS_BPF_PARAM_STORE + BpfFuncCountPacketInit(self.map_fd)
+ INS_SK_FILTER_ACCEPT + INS_PACK_COUNT_UPDATE
+ INS_SK_FILTER_ACCEPT)
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, instructions)
packet_count = 10
SocketUDPLoopBack(packet_count, 4, self.prog_fd)
SocketUDPLoopBack(packet_count, 6, self.prog_fd)
self.assertEqual(packet_count * 2, LookupMap(self.map_fd, key).value)
##############################################################################
#
# Test for presence of kernel patch:
#
# ANDROID: net: bpf: Allow TC programs to call BPF_FUNC_skb_change_head
#
# 4.14: https://android-review.googlesource.com/c/kernel/common/+/1237789
# commit fe82848d9c1c887d2a84d3738c13e644d01b6d6f
#
# 4.19: https://android-review.googlesource.com/c/kernel/common/+/1237788
# commit 6e04d94ab72435b45c413daff63520fd724e260e
#
# 5.4: https://android-review.googlesource.com/c/kernel/common/+/1237787
# commit d730995e7bc5b4c10cc176235b704a274e6ec16f
#
# Upstream in Linux v5.8:
# net: bpf: Allow TC programs to call BPF_FUNC_skb_change_head
# commit 6f3f65d80dac8f2bafce2213005821fccdce194c
#
@unittest.skipUnless(bpf.HAVE_EBPF_4_14,
"no bpf_skb_change_head() support for pre-4.14 kernels")
def testSkbChangeHead(self):
# long bpf_skb_change_head(struct sk_buff *skb, u32 len, u64 flags)
instructions = [
BpfMov64Imm(BPF_REG_2, 14), # u32 len
BpfMov64Imm(BPF_REG_3, 0), # u64 flags
BpfFuncCall(BPF_FUNC_skb_change_head),
] + INS_BPF_EXIT_BLOCK
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions,
b"Apache 2.0")
# No exceptions? Good.
def testKtimeGetNsGPL(self):
instructions = [BpfFuncCall(BPF_FUNC_ktime_get_ns)] + INS_BPF_EXIT_BLOCK
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions)
# No exceptions? Good.
##############################################################################
#
# Test for presence of kernel patch:
#
# UPSTREAM: net: bpf: Make bpf_ktime_get_ns() available to non GPL programs
#
# 4.14: https://android-review.googlesource.com/c/kernel/common/+/1585269
# commit cbb4c73f9eab8f3c8ac29175d45c99ccba382e15
#
# 4.19: https://android-review.googlesource.com/c/kernel/common/+/1355243
# commit 272e21ccc9a92feeee80aff0587410a314b73c5b
#
# 5.4: https://android-review.googlesource.com/c/kernel/common/+/1355422
# commit 45217b91eaaa3a563247c4f470f4cb785de6b1c6
#
@unittest.skipUnless(HAVE_EBPF_KTIME_GET_NS_APACHE2,
"no bpf_ktime_get_ns() support for non-GPL programs")
def testKtimeGetNsApache2(self):
instructions = [BpfFuncCall(BPF_FUNC_ktime_get_ns)] + INS_BPF_EXIT_BLOCK
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions,
b"Apache 2.0")
# No exceptions? Good.
##############################################################################
#
# Test for presence of kernel patch:
#
# BACKPORT: bpf: add bpf_ktime_get_boot_ns()
#
# 4.14: https://android-review.googlesource.com/c/kernel/common/+/1585587
# commit 34073d7a8ee47ca908b56e9a1d14ca0615fdfc09
#
# 4.19: https://android-review.googlesource.com/c/kernel/common/+/1585606
# commit 4812ec50935dfe59ba9f48a572e278dd0b02af68
#
# 5.4: https://android-review.googlesource.com/c/kernel/common/+/1585252
# commit 57b3f4830fb66a6038c4c1c66ca2e138fe8be231
#
@unittest.skipUnless(HAVE_EBPF_KTIME_GET_BOOT_NS,
"no bpf_ktime_get_boot_ns() support")
def testKtimeGetBootNs(self):
instructions = [
BpfFuncCall(BPF_FUNC_ktime_get_boot_ns),
] + INS_BPF_EXIT_BLOCK
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SCHED_CLS, instructions,
b"Apache 2.0")
# No exceptions? Good.
def testGetSocketCookie(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
# Move skb to REG6 for further usage, call helper function to get socket
# cookie of current skb and return the cookie at REG0 for next code block
instructions = [
BpfMov64Reg(BPF_REG_6, BPF_REG_1),
BpfFuncCall(BPF_FUNC_get_socket_cookie)
]
instructions += (INS_BPF_PARAM_STORE + BpfFuncCountPacketInit(self.map_fd)
+ INS_SK_FILTER_ACCEPT + INS_PACK_COUNT_UPDATE
+ INS_SK_FILTER_ACCEPT)
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, instructions)
packet_count = 10
def PacketCountByCookie(version):
self.sock = SocketUDPLoopBack(packet_count, version, self.prog_fd)
cookie = sock_diag.SockDiag.GetSocketCookie(self.sock)
self.assertEqual(packet_count, LookupMap(self.map_fd, cookie).value)
self.sock.close()
PacketCountByCookie(4)
PacketCountByCookie(6)
def testGetSocketUid(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
# Set up the instruction with uid at BPF_REG_0.
instructions = [
BpfMov64Reg(BPF_REG_6, BPF_REG_1),
BpfFuncCall(BPF_FUNC_get_socket_uid)
]
# Concatenate the generic packet count bpf program to it.
instructions += (INS_BPF_PARAM_STORE + BpfFuncCountPacketInit(self.map_fd)
+ INS_SK_FILTER_ACCEPT + INS_PACK_COUNT_UPDATE
+ INS_SK_FILTER_ACCEPT)
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_SOCKET_FILTER, instructions)
packet_count = 10
uid = TEST_UID
with net_test.RunAsUid(uid):
self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, uid)
SocketUDPLoopBack(packet_count, 4, self.prog_fd)
self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value)
DeleteMap(self.map_fd, uid)
SocketUDPLoopBack(packet_count, 6, self.prog_fd)
self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value)
@unittest.skipUnless(HAVE_EBPF_ACCOUNTING,
"Cgroup BPF is not fully supported")
class BpfCgroupTest(net_test.NetworkTest):
@classmethod
def setUpClass(cls):
super(BpfCgroupTest, cls).setUpClass()
cls._cg_dir = tempfile.mkdtemp(prefix="cg_bpf-")
cmd = "mount -t cgroup2 cg_bpf %s" % cls._cg_dir
try:
subprocess.check_call(cmd.split())
except subprocess.CalledProcessError:
# If an exception is thrown in setUpClass, the test fails and
# tearDownClass is not called.
os.rmdir(cls._cg_dir)
raise
cls._cg_fd = os.open(cls._cg_dir, os.O_DIRECTORY | os.O_RDONLY)
@classmethod
def tearDownClass(cls):
os.close(cls._cg_fd)
subprocess.call(("umount %s" % cls._cg_dir).split())
os.rmdir(cls._cg_dir)
super(BpfCgroupTest, cls).tearDownClass()
def setUp(self):
super(BpfCgroupTest, self).setUp()
self.prog_fd = -1
self.map_fd = -1
def tearDown(self):
if self.prog_fd >= 0:
os.close(self.prog_fd)
if self.map_fd >= 0:
os.close(self.map_fd)
try:
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_EGRESS)
except socket.error:
pass
try:
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
except socket.error:
pass
try:
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
except socket.error:
pass
super(BpfCgroupTest, self).tearDown()
def testCgroupBpfAttach(self):
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, INS_BPF_EXIT_BLOCK)
BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_INGRESS)
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
def testCgroupIngress(self):
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, INS_BPF_EXIT_BLOCK)
BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_INGRESS)
self.assertRaisesErrno(errno.EAGAIN, SocketUDPLoopBack, 1, 4, None)
self.assertRaisesErrno(errno.EAGAIN, SocketUDPLoopBack, 1, 6, None)
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
SocketUDPLoopBack(1, 4, None)
SocketUDPLoopBack(1, 6, None)
def testCgroupEgress(self):
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, INS_BPF_EXIT_BLOCK)
BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_EGRESS)
self.assertRaisesErrno(errno.EPERM, SocketUDPLoopBack, 1, 4, None)
self.assertRaisesErrno(errno.EPERM, SocketUDPLoopBack, 1, 6, None)
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_EGRESS)
SocketUDPLoopBack(1, 4, None)
SocketUDPLoopBack(1, 6, None)
def testCgroupBpfUid(self):
self.map_fd = CreateMap(BPF_MAP_TYPE_HASH, KEY_SIZE, VALUE_SIZE,
TOTAL_ENTRIES)
# Similar to the program used in testGetSocketUid.
instructions = [
BpfMov64Reg(BPF_REG_6, BPF_REG_1),
BpfFuncCall(BPF_FUNC_get_socket_uid)
]
instructions += (INS_BPF_PARAM_STORE + BpfFuncCountPacketInit(self.map_fd)
+ INS_CGROUP_ACCEPT + INS_PACK_COUNT_UPDATE
+ INS_CGROUP_ACCEPT)
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SKB, instructions)
BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_INGRESS)
packet_count = 20
uid = TEST_UID
with net_test.RunAsUid(uid):
self.assertRaisesErrno(errno.ENOENT, LookupMap, self.map_fd, uid)
SocketUDPLoopBack(packet_count, 4, None)
self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value)
DeleteMap(self.map_fd, uid)
SocketUDPLoopBack(packet_count, 6, None)
self.assertEqual(packet_count, LookupMap(self.map_fd, uid).value)
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_INGRESS)
def checkSocketCreate(self, family, socktype, success):
try:
sock = socket.socket(family, socktype, 0)
sock.close()
except socket.error as e:
if success:
self.fail("Failed to create socket family=%d type=%d err=%s" %
(family, socktype, os.strerror(e.errno)))
return
if not success:
self.fail("unexpected socket family=%d type=%d created, should be blocked"
% (family, socktype))
def trySocketCreate(self, success):
for family in [socket.AF_INET, socket.AF_INET6]:
for socktype in [socket.SOCK_DGRAM, socket.SOCK_STREAM]:
self.checkSocketCreate(family, socktype, success)
@unittest.skipUnless(HAVE_EBPF_SOCKET,
"Cgroup BPF socket is not supported")
def testCgroupSocketCreateBlock(self):
instructions = [
BpfFuncCall(BPF_FUNC_get_current_uid_gid),
BpfAlu64Imm(BPF_AND, BPF_REG_0, 0xfffffff),
BpfJumpImm(BPF_JNE, BPF_REG_0, TEST_UID, 2),
]
instructions += INS_BPF_EXIT_BLOCK + INS_CGROUP_ACCEPT
self.prog_fd = BpfProgLoad(BPF_PROG_TYPE_CGROUP_SOCK, instructions)
BpfProgAttach(self.prog_fd, self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
with net_test.RunAsUid(TEST_UID):
# Socket creation with target uid should fail
self.trySocketCreate(False)
# Socket create with different uid should success
self.trySocketCreate(True)
BpfProgDetach(self._cg_fd, BPF_CGROUP_INET_SOCK_CREATE)
with net_test.RunAsUid(TEST_UID):
self.trySocketCreate(True)
if __name__ == "__main__":
unittest.main()