[NFCT.TETHER.15] Attach BPF program in the mainline module
Migrate Maze's BPF program attaching and detaching functions from
system/netd/server/OffloadUtils.{c, h} to tethering module.
Test: atest TetheringCoverageTests
Test case #1:
Enable WiFi hotspot and check tc filters are added or removed on both
wlan1 and rmnet_data#.
$ adb shell tc filter show dev wlan1 ingress
filter protocol ipv6 pref 1 bpf chain 0
filter protocol ipv6 pref 1 bpf chain 0 handle 0x1
prog_offload_schedcls_tether_upstream6_ether:[*fsobj] direct-action
not_in_hw id 2 tag 7cf020cc09a7c982
filter protocol ip pref 2 bpf chain 0
filter protocol ip pref 2 bpf chain 0 handle 0x1
prog_offload_schedcls_tether_upstream4_ether:[*fsobj] direct-action
not_in_hw id 7 tag 2f87d55b636c082c
$ adb shell tc filter show dev rmnet_data2 ingress;
filter protocol ipv6 pref 1 bpf chain 0
filter protocol ipv6 pref 1 bpf chain 0 handle 0x1
prog_offload_schedcls_tether_downstream6_rawip:[*fsobj] direct-action
not_in_hw id 3 tag 8b3885b75bd261de
filter protocol ip pref 2 bpf chain 0
filter protocol ip pref 2 bpf chain 0 handle 0x1
prog_offload_schedcls_tether_downstream4_rawip:[*fsobj] direct-action
not_in_hw id 6 tag b1c9478c91f8df9a
Test case #2:
Enable USB tethering and check tc filters are added or removed on both
rndis0 and rmnet_data#.
Test case #3:
Enable WiFi and USB tethering and check tc filter are added or removed
on rndis0, wlan1 and rmnet_data#.
Change-Id: I3f9a65043271bc8f5bf1b82ae505c471625ca9de
This commit is contained in:
@@ -158,6 +158,18 @@ public class BpfCoordinatorShimImpl
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean attachProgram(String iface, boolean downstream) {
|
||||||
|
/* no op */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean detachProgram(String iface) {
|
||||||
|
/* no op */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Netd used";
|
return "Netd used";
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
|
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
|
||||||
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
|
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
|
||||||
import com.android.networkstack.tethering.BpfMap;
|
import com.android.networkstack.tethering.BpfMap;
|
||||||
|
import com.android.networkstack.tethering.BpfUtils;
|
||||||
import com.android.networkstack.tethering.Tether4Key;
|
import com.android.networkstack.tethering.Tether4Key;
|
||||||
import com.android.networkstack.tethering.Tether4Value;
|
import com.android.networkstack.tethering.Tether4Value;
|
||||||
import com.android.networkstack.tethering.Tether6Value;
|
import com.android.networkstack.tethering.Tether6Value;
|
||||||
@@ -42,6 +43,7 @@ import com.android.networkstack.tethering.TetherStatsValue;
|
|||||||
import com.android.networkstack.tethering.TetherUpstream6Key;
|
import com.android.networkstack.tethering.TetherUpstream6Key;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bpf coordinator class for API shims.
|
* Bpf coordinator class for API shims.
|
||||||
@@ -358,6 +360,32 @@ public class BpfCoordinatorShimImpl
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean attachProgram(String iface, boolean downstream) {
|
||||||
|
if (!isInitialized()) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BpfUtils.attachProgram(iface, downstream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
mLog.e("Could not attach program: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean detachProgram(String iface) {
|
||||||
|
if (!isInitialized()) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BpfUtils.detachProgram(iface);
|
||||||
|
} catch (IOException e) {
|
||||||
|
mLog.e("Could not detach program: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private String mapStatus(BpfMap m, String name) {
|
private String mapStatus(BpfMap m, String name) {
|
||||||
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
|
return name + "{" + (m != null ? "OK" : "ERROR") + "}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,5 +143,19 @@ public abstract class BpfCoordinatorShim {
|
|||||||
* Deletes a tethering IPv4 offload rule from the appropriate BPF map.
|
* Deletes a tethering IPv4 offload rule from the appropriate BPF map.
|
||||||
*/
|
*/
|
||||||
public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key);
|
public abstract boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach BPF program.
|
||||||
|
*
|
||||||
|
* TODO: consider using InterfaceParams to replace interface name.
|
||||||
|
*/
|
||||||
|
public abstract boolean attachProgram(@NonNull String iface, boolean downstream);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach BPF program.
|
||||||
|
*
|
||||||
|
* TODO: consider using InterfaceParams to replace interface name.
|
||||||
|
*/
|
||||||
|
public abstract boolean detachProgram(@NonNull String iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
350
Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
Normal file
350
Tethering/jni/com_android_networkstack_tethering_BpfUtils.cpp
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <linux/if_arp.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/pkt_cls.h>
|
||||||
|
#include <linux/pkt_sched.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <nativehelper/JNIHelp.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
// TODO: use unique_fd.
|
||||||
|
#define BPF_FD_JUST_USE_INT
|
||||||
|
#include "BpfSyscallWrappers.h"
|
||||||
|
#include "bpf_tethering.h"
|
||||||
|
#include "nativehelper/scoped_utf_chars.h"
|
||||||
|
|
||||||
|
// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
|
||||||
|
#define CLS_BPF_NAME_LEN 256
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
// Sync from system/netd/server/NetlinkCommands.h
|
||||||
|
const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
|
||||||
|
const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
|
||||||
|
|
||||||
|
// TODO: move to frameworks/libs/net/common/native for sharing with
|
||||||
|
// system/netd/server/OffloadUtils.{c, h}.
|
||||||
|
static void sendAndProcessNetlinkResponse(JNIEnv* env, const void* req, int len) {
|
||||||
|
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); // TODO: use unique_fd
|
||||||
|
if (fd == -1) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %s",
|
||||||
|
strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr int on = 1;
|
||||||
|
if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, %d)", on);
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is needed to get valid strace netlink parsing, it allocates the pid
|
||||||
|
if (bind(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "bind(fd, {AF_NETLINK, 0, 0}): %s",
|
||||||
|
strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we do not want to receive messages from anyone besides the kernel
|
||||||
|
if (connect(fd, (const struct sockaddr*)&KERNEL_NLADDR, sizeof(KERNEL_NLADDR))) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "connect(fd, {AF_NETLINK, 0, 0}): %s",
|
||||||
|
strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rv = send(fd, req, len, 0);
|
||||||
|
|
||||||
|
if (rv == -1) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
|
||||||
|
strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv != len) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "send(fd, req, len, 0): %s",
|
||||||
|
strerror(EMSGSIZE));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
nlmsghdr h;
|
||||||
|
nlmsgerr e;
|
||||||
|
char buf[256];
|
||||||
|
} resp = {};
|
||||||
|
|
||||||
|
rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
|
||||||
|
|
||||||
|
if (rv == -1) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "recv() failed: %s", strerror(errno));
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "recv() returned short packet: %d", rv);
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.h.nlmsg_len != (unsigned)rv) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"recv() returned invalid header length: %d != %d", resp.h.nlmsg_len,
|
||||||
|
rv);
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.h.nlmsg_type != NLMSG_ERROR) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"recv() did not return NLMSG_ERROR message: %d", resp.h.nlmsg_type);
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.e.error) { // returns 0 on success
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "NLMSG_ERROR message return error: %s",
|
||||||
|
strerror(-resp.e.error));
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hardwareAddressType(const char* interface) {
|
||||||
|
int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||||
|
if (fd < 0) return -errno;
|
||||||
|
|
||||||
|
struct ifreq ifr = {};
|
||||||
|
// We use strncpy() instead of strlcpy() since kernel has to be able
|
||||||
|
// to handle non-zero terminated junk passed in by userspace anyway,
|
||||||
|
// and this way too long interface names (more than IFNAMSIZ-1 = 15
|
||||||
|
// characters plus terminating NULL) will not get truncated to 15
|
||||||
|
// characters and zero-terminated and thus potentially erroneously
|
||||||
|
// match a truncated interface if one were to exist.
|
||||||
|
strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
|
||||||
|
|
||||||
|
int rv;
|
||||||
|
if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
|
||||||
|
rv = -errno;
|
||||||
|
} else {
|
||||||
|
rv = ifr.ifr_hwaddr.sa_family;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jboolean com_android_networkstack_tethering_BpfUtils_isEthernet(JNIEnv* env, jobject clazz,
|
||||||
|
jstring iface) {
|
||||||
|
ScopedUtfChars interface(env, iface);
|
||||||
|
|
||||||
|
int rv = hardwareAddressType(interface.c_str());
|
||||||
|
if (rv < 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"Get hardware address type of interface %s failed: %s",
|
||||||
|
interface.c_str(), strerror(-rv));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (rv) {
|
||||||
|
case ARPHRD_ETHER:
|
||||||
|
return true;
|
||||||
|
case ARPHRD_NONE:
|
||||||
|
case ARPHRD_RAWIP: // in Linux 4.14+ rmnet support was upstreamed and this is 519
|
||||||
|
case 530: // this is ARPHRD_RAWIP on some Android 4.9 kernels with rmnet
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException",
|
||||||
|
"Unknown hardware address type %s on interface %s", rv,
|
||||||
|
interface.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned /sys/fs/bpf/...
|
||||||
|
// direct-action
|
||||||
|
static void com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf(
|
||||||
|
JNIEnv* env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio, jshort proto,
|
||||||
|
jstring bpfProgPath) {
|
||||||
|
ScopedUtfChars pathname(env, bpfProgPath);
|
||||||
|
|
||||||
|
const int bpfFd = bpf::retrieveProgram(pathname.c_str());
|
||||||
|
if (bpfFd == -1) {
|
||||||
|
jniThrowExceptionFmt(env, "java/io/IOException", "retrieveProgram failed %s",
|
||||||
|
strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
nlmsghdr n;
|
||||||
|
tcmsg t;
|
||||||
|
struct {
|
||||||
|
nlattr attr;
|
||||||
|
// The maximum classifier name length is defined as IFNAMSIZ.
|
||||||
|
// See tcf_proto_ops in include/net/sch_generic.h.
|
||||||
|
char str[NLMSG_ALIGN(IFNAMSIZ)];
|
||||||
|
} kind;
|
||||||
|
struct {
|
||||||
|
nlattr attr;
|
||||||
|
struct {
|
||||||
|
nlattr attr;
|
||||||
|
__u32 u32;
|
||||||
|
} fd;
|
||||||
|
struct {
|
||||||
|
nlattr attr;
|
||||||
|
char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
|
||||||
|
} name;
|
||||||
|
struct {
|
||||||
|
nlattr attr;
|
||||||
|
__u32 u32;
|
||||||
|
} flags;
|
||||||
|
} options;
|
||||||
|
} req = {
|
||||||
|
.n =
|
||||||
|
{
|
||||||
|
.nlmsg_len = sizeof(req),
|
||||||
|
.nlmsg_type = RTM_NEWTFILTER,
|
||||||
|
.nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
|
||||||
|
},
|
||||||
|
.t =
|
||||||
|
{
|
||||||
|
.tcm_family = AF_UNSPEC,
|
||||||
|
.tcm_ifindex = ifIndex,
|
||||||
|
.tcm_handle = TC_H_UNSPEC,
|
||||||
|
.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
|
||||||
|
ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
|
||||||
|
.tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
|
||||||
|
htons(static_cast<uint16_t>(proto))),
|
||||||
|
},
|
||||||
|
.kind =
|
||||||
|
{
|
||||||
|
.attr =
|
||||||
|
{
|
||||||
|
.nla_len = sizeof(req.kind),
|
||||||
|
.nla_type = TCA_KIND,
|
||||||
|
},
|
||||||
|
// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
|
||||||
|
.str = "bpf",
|
||||||
|
},
|
||||||
|
.options =
|
||||||
|
{
|
||||||
|
.attr =
|
||||||
|
{
|
||||||
|
.nla_len = sizeof(req.options),
|
||||||
|
.nla_type = NLA_F_NESTED | TCA_OPTIONS,
|
||||||
|
},
|
||||||
|
.fd =
|
||||||
|
{
|
||||||
|
.attr =
|
||||||
|
{
|
||||||
|
.nla_len = sizeof(req.options.fd),
|
||||||
|
.nla_type = TCA_BPF_FD,
|
||||||
|
},
|
||||||
|
.u32 = static_cast<__u32>(bpfFd),
|
||||||
|
},
|
||||||
|
.name =
|
||||||
|
{
|
||||||
|
.attr =
|
||||||
|
{
|
||||||
|
.nla_len = sizeof(req.options.name),
|
||||||
|
.nla_type = TCA_BPF_NAME,
|
||||||
|
},
|
||||||
|
// Visible via 'tc filter show', but
|
||||||
|
// is overwritten by strncpy below
|
||||||
|
.str = "placeholder",
|
||||||
|
},
|
||||||
|
.flags =
|
||||||
|
{
|
||||||
|
.attr =
|
||||||
|
{
|
||||||
|
.nla_len = sizeof(req.options.flags),
|
||||||
|
.nla_type = TCA_BPF_FLAGS,
|
||||||
|
},
|
||||||
|
.u32 = TCA_BPF_FLAG_ACT_DIRECT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
|
||||||
|
basename(pathname.c_str()));
|
||||||
|
|
||||||
|
// The exception may be thrown from sendAndProcessNetlinkResponse. Close the file descriptor of
|
||||||
|
// BPF program before returning the function in any case.
|
||||||
|
sendAndProcessNetlinkResponse(env, &req, sizeof(req));
|
||||||
|
close(bpfFd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tc filter del dev .. in/egress prio .. protocol ..
|
||||||
|
static void com_android_networkstack_tethering_BpfUtils_tcFilterDelDev(JNIEnv* env, jobject clazz,
|
||||||
|
jint ifIndex,
|
||||||
|
jboolean ingress,
|
||||||
|
jshort prio, jshort proto) {
|
||||||
|
const struct {
|
||||||
|
nlmsghdr n;
|
||||||
|
tcmsg t;
|
||||||
|
} req = {
|
||||||
|
.n =
|
||||||
|
{
|
||||||
|
.nlmsg_len = sizeof(req),
|
||||||
|
.nlmsg_type = RTM_DELTFILTER,
|
||||||
|
.nlmsg_flags = NETLINK_REQUEST_FLAGS,
|
||||||
|
},
|
||||||
|
.t =
|
||||||
|
{
|
||||||
|
.tcm_family = AF_UNSPEC,
|
||||||
|
.tcm_ifindex = ifIndex,
|
||||||
|
.tcm_handle = TC_H_UNSPEC,
|
||||||
|
.tcm_parent = TC_H_MAKE(TC_H_CLSACT,
|
||||||
|
ingress ? TC_H_MIN_INGRESS : TC_H_MIN_EGRESS),
|
||||||
|
.tcm_info = static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
|
||||||
|
htons(static_cast<uint16_t>(proto))),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
sendAndProcessNetlinkResponse(env, &req, sizeof(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JNI registration.
|
||||||
|
*/
|
||||||
|
static const JNINativeMethod gMethods[] = {
|
||||||
|
/* name, signature, funcPtr */
|
||||||
|
{"isEthernet", "(Ljava/lang/String;)Z",
|
||||||
|
(void*)com_android_networkstack_tethering_BpfUtils_isEthernet},
|
||||||
|
{"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
|
||||||
|
(void*)com_android_networkstack_tethering_BpfUtils_tcFilterAddDevBpf},
|
||||||
|
{"tcFilterDelDev", "(IZSS)V",
|
||||||
|
(void*)com_android_networkstack_tethering_BpfUtils_tcFilterDelDev},
|
||||||
|
};
|
||||||
|
|
||||||
|
int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env) {
|
||||||
|
return jniRegisterNativeMethods(env, "com/android/networkstack/tethering/BpfUtils", gMethods,
|
||||||
|
NELEM(gMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace android
|
||||||
@@ -25,6 +25,7 @@ namespace android {
|
|||||||
int register_android_net_util_TetheringUtils(JNIEnv* env);
|
int register_android_net_util_TetheringUtils(JNIEnv* env);
|
||||||
int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env);
|
int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env);
|
||||||
int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env);
|
int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env);
|
||||||
|
int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env);
|
||||||
|
|
||||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
@@ -39,6 +40,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
|||||||
|
|
||||||
if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
|
if (register_com_android_networkstack_tethering_BpfCoordinator(env) < 0) return JNI_ERR;
|
||||||
|
|
||||||
|
if (register_com_android_networkstack_tethering_BpfUtils(env) < 0) return JNI_ERR;
|
||||||
|
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1291,6 +1291,7 @@ public class IpServer extends StateMachine {
|
|||||||
// Sometimes interfaces are gone before we get
|
// Sometimes interfaces are gone before we get
|
||||||
// to remove their rules, which generates errors.
|
// to remove their rules, which generates errors.
|
||||||
// Just do the best we can.
|
// Just do the best we can.
|
||||||
|
mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface);
|
||||||
try {
|
try {
|
||||||
mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
|
mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
|
||||||
} catch (RemoteException | ServiceSpecificException e) {
|
} catch (RemoteException | ServiceSpecificException e) {
|
||||||
@@ -1334,6 +1335,7 @@ public class IpServer extends StateMachine {
|
|||||||
mUpstreamIfaceSet = newUpstreamIfaceSet;
|
mUpstreamIfaceSet = newUpstreamIfaceSet;
|
||||||
|
|
||||||
for (String ifname : added) {
|
for (String ifname : added) {
|
||||||
|
mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname);
|
||||||
try {
|
try {
|
||||||
mNetd.tetherAddForward(mIfaceName, ifname);
|
mNetd.tetherAddForward(mIfaceName, ifname);
|
||||||
mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
|
mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED
|
|||||||
import static android.system.OsConstants.ETH_P_IP;
|
import static android.system.OsConstants.ETH_P_IP;
|
||||||
import static android.system.OsConstants.ETH_P_IPV6;
|
import static android.system.OsConstants.ETH_P_IPV6;
|
||||||
|
|
||||||
|
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
|
||||||
|
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
|
||||||
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
|
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
|
||||||
|
|
||||||
import android.app.usage.NetworkStatsManager;
|
import android.app.usage.NetworkStatsManager;
|
||||||
@@ -92,9 +94,6 @@ public class BpfCoordinator {
|
|||||||
System.loadLibrary("tetherutilsjni");
|
System.loadLibrary("tetherutilsjni");
|
||||||
}
|
}
|
||||||
|
|
||||||
static final boolean DOWNSTREAM = true;
|
|
||||||
static final boolean UPSTREAM = false;
|
|
||||||
|
|
||||||
private static final String TAG = BpfCoordinator.class.getSimpleName();
|
private static final String TAG = BpfCoordinator.class.getSimpleName();
|
||||||
private static final int DUMP_TIMEOUT_MS = 10_000;
|
private static final int DUMP_TIMEOUT_MS = 10_000;
|
||||||
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
|
private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
|
||||||
@@ -118,7 +117,6 @@ public class BpfCoordinator {
|
|||||||
return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
|
return makeMapPath((downstream ? "downstream" : "upstream") + ipVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
enum StatsType {
|
enum StatsType {
|
||||||
STATS_PER_IFACE,
|
STATS_PER_IFACE,
|
||||||
@@ -218,6 +216,9 @@ public class BpfCoordinator {
|
|||||||
// is okay for now because there have only one upstream generally.
|
// is okay for now because there have only one upstream generally.
|
||||||
private final HashMap<Inet4Address, Integer> mIpv4UpstreamIndices = new HashMap<>();
|
private final HashMap<Inet4Address, Integer> mIpv4UpstreamIndices = new HashMap<>();
|
||||||
|
|
||||||
|
// Map for upstream and downstream pair.
|
||||||
|
private final HashMap<String, HashSet<String>> mForwardingPairs = new HashMap<>();
|
||||||
|
|
||||||
// Runnable that used by scheduling next polling of stats.
|
// Runnable that used by scheduling next polling of stats.
|
||||||
private final Runnable mScheduledPollingTask = () -> {
|
private final Runnable mScheduledPollingTask = () -> {
|
||||||
updateForwardedStats();
|
updateForwardedStats();
|
||||||
@@ -691,6 +692,37 @@ public class BpfCoordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach BPF program
|
||||||
|
*
|
||||||
|
* TODO: consider error handling if the attach program failed.
|
||||||
|
*/
|
||||||
|
public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) {
|
||||||
|
if (forwardingPairExists(intIface, extIface)) return;
|
||||||
|
|
||||||
|
boolean firstDownstreamForThisUpstream = !isAnyForwardingPairOnUpstream(extIface);
|
||||||
|
forwardingPairAdd(intIface, extIface);
|
||||||
|
|
||||||
|
mBpfCoordinatorShim.attachProgram(intIface, UPSTREAM);
|
||||||
|
// Attach if the upstream is the first time to be used in a forwarding pair.
|
||||||
|
if (firstDownstreamForThisUpstream) {
|
||||||
|
mBpfCoordinatorShim.attachProgram(extIface, DOWNSTREAM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach BPF program
|
||||||
|
*/
|
||||||
|
public void maybeDetachProgram(@NonNull String intIface, @NonNull String extIface) {
|
||||||
|
forwardingPairRemove(intIface, extIface);
|
||||||
|
|
||||||
|
// Detaching program may fail because the interface has been removed already.
|
||||||
|
mBpfCoordinatorShim.detachProgram(intIface);
|
||||||
|
// Detach if no more forwarding pair is using the upstream.
|
||||||
|
if (!isAnyForwardingPairOnUpstream(extIface)) {
|
||||||
|
mBpfCoordinatorShim.detachProgram(extIface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: make mInterfaceNames accessible to the shim and move this code to there.
|
// TODO: make mInterfaceNames accessible to the shim and move this code to there.
|
||||||
private String getIfName(long ifindex) {
|
private String getIfName(long ifindex) {
|
||||||
@@ -1227,6 +1259,33 @@ public class BpfCoordinator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void forwardingPairAdd(@NonNull String intIface, @NonNull String extIface) {
|
||||||
|
if (!mForwardingPairs.containsKey(extIface)) {
|
||||||
|
mForwardingPairs.put(extIface, new HashSet<String>());
|
||||||
|
}
|
||||||
|
mForwardingPairs.get(extIface).add(intIface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forwardingPairRemove(@NonNull String intIface, @NonNull String extIface) {
|
||||||
|
HashSet<String> downstreams = mForwardingPairs.get(extIface);
|
||||||
|
if (downstreams == null) return;
|
||||||
|
if (!downstreams.remove(intIface)) return;
|
||||||
|
|
||||||
|
if (downstreams.isEmpty()) {
|
||||||
|
mForwardingPairs.remove(extIface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean forwardingPairExists(@NonNull String intIface, @NonNull String extIface) {
|
||||||
|
if (!mForwardingPairs.containsKey(extIface)) return false;
|
||||||
|
|
||||||
|
return mForwardingPairs.get(extIface).contains(intIface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAnyForwardingPairOnUpstream(@NonNull String extIface) {
|
||||||
|
return mForwardingPairs.containsKey(extIface);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
|
private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
|
||||||
@NonNull final ForwardedStats diff) {
|
@NonNull final ForwardedStats diff) {
|
||||||
|
|||||||
144
Tethering/src/com/android/networkstack/tethering/BpfUtils.java
Normal file
144
Tethering/src/com/android/networkstack/tethering/BpfUtils.java
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.
|
||||||
|
*/
|
||||||
|
package com.android.networkstack.tethering;
|
||||||
|
|
||||||
|
import static android.system.OsConstants.ETH_P_IP;
|
||||||
|
import static android.system.OsConstants.ETH_P_IPV6;
|
||||||
|
|
||||||
|
import android.net.util.InterfaceParams;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The classes and the methods for BPF utilization.
|
||||||
|
*
|
||||||
|
* {@hide}
|
||||||
|
*/
|
||||||
|
public class BpfUtils {
|
||||||
|
static {
|
||||||
|
System.loadLibrary("tetherutilsjni");
|
||||||
|
}
|
||||||
|
|
||||||
|
// For better code clarity when used for 'bool ingress' parameter.
|
||||||
|
static final boolean EGRESS = false;
|
||||||
|
static final boolean INGRESS = true;
|
||||||
|
|
||||||
|
// For better code clarify when used for 'bool downstream' parameter.
|
||||||
|
//
|
||||||
|
// This is talking about the direction of travel of the offloaded packets.
|
||||||
|
//
|
||||||
|
// Upstream means packets heading towards the internet/uplink (upload),
|
||||||
|
// thus for tethering this is attached to ingress on the downstream interface,
|
||||||
|
// while for clat this is attached to egress on the v4-* clat interface.
|
||||||
|
//
|
||||||
|
// Downstream means packets coming from the internet/uplink (download), thus
|
||||||
|
// for both clat and tethering this is attached to ingress on the upstream interface.
|
||||||
|
static final boolean DOWNSTREAM = true;
|
||||||
|
static final boolean UPSTREAM = false;
|
||||||
|
|
||||||
|
// The priority of clat/tether hooks - smaller is higher priority.
|
||||||
|
// TC tether is higher priority then TC clat to match XDP winning over TC.
|
||||||
|
// Sync from system/netd/server/OffloadUtils.h.
|
||||||
|
static final short PRIO_TETHER6 = 1;
|
||||||
|
static final short PRIO_TETHER4 = 2;
|
||||||
|
static final short PRIO_CLAT = 3;
|
||||||
|
|
||||||
|
private static String makeProgPath(boolean downstream, int ipVersion, boolean ether) {
|
||||||
|
String path = "/sys/fs/bpf/tethering/prog_offload_schedcls_tether_"
|
||||||
|
+ (downstream ? "downstream" : "upstream")
|
||||||
|
+ ipVersion + "_"
|
||||||
|
+ (ether ? "ether" : "rawip");
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach BPF program
|
||||||
|
*
|
||||||
|
* TODO: use interface index to replace interface name.
|
||||||
|
*/
|
||||||
|
public static void attachProgram(@NonNull String iface, boolean downstream)
|
||||||
|
throws IOException {
|
||||||
|
final InterfaceParams params = InterfaceParams.getByName(iface);
|
||||||
|
if (params == null) {
|
||||||
|
throw new IOException("Fail to get interface params for interface " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ether;
|
||||||
|
try {
|
||||||
|
ether = isEthernet(iface);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("isEthernet(" + params.index + "[" + iface + "]) failure: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// tc filter add dev .. ingress prio 1 protocol ipv6 bpf object-pinned /sys/fs/bpf/...
|
||||||
|
// direct-action
|
||||||
|
tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6,
|
||||||
|
makeProgPath(downstream, 6, ether));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("tc filter add dev (" + params.index + "[" + iface
|
||||||
|
+ "]) ingress prio PRIO_TETHER6 protocol ipv6 failure: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// tc filter add dev .. ingress prio 2 protocol ip bpf object-pinned /sys/fs/bpf/...
|
||||||
|
// direct-action
|
||||||
|
tcFilterAddDevBpf(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP,
|
||||||
|
makeProgPath(downstream, 4, ether));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("tc filter add dev (" + params.index + "[" + iface
|
||||||
|
+ "]) ingress prio PRIO_TETHER4 protocol ip failure: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach BPF program
|
||||||
|
*
|
||||||
|
* TODO: use interface index to replace interface name.
|
||||||
|
*/
|
||||||
|
public static void detachProgram(@NonNull String iface) throws IOException {
|
||||||
|
final InterfaceParams params = InterfaceParams.getByName(iface);
|
||||||
|
if (params == null) {
|
||||||
|
throw new IOException("Fail to get interface params for interface " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// tc filter del dev .. ingress prio 1 protocol ipv6
|
||||||
|
tcFilterDelDev(params.index, INGRESS, PRIO_TETHER6, (short) ETH_P_IPV6);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("tc filter del dev (" + params.index + "[" + iface
|
||||||
|
+ "]) ingress prio PRIO_TETHER6 protocol ipv6 failure: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// tc filter del dev .. ingress prio 2 protocol ip
|
||||||
|
tcFilterDelDev(params.index, INGRESS, PRIO_TETHER4, (short) ETH_P_IP);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("tc filter del dev (" + params.index + "[" + iface
|
||||||
|
+ "]) ingress prio PRIO_TETHER4 protocol ip failure: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native boolean isEthernet(String iface) throws IOException;
|
||||||
|
|
||||||
|
private static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio,
|
||||||
|
short proto, String bpfProgPath) throws IOException;
|
||||||
|
|
||||||
|
private static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio,
|
||||||
|
short proto) throws IOException;
|
||||||
|
}
|
||||||
@@ -247,7 +247,7 @@ public class IpServerTest {
|
|||||||
lp.setInterfaceName(upstreamIface);
|
lp.setInterfaceName(upstreamIface);
|
||||||
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
|
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
|
||||||
}
|
}
|
||||||
reset(mNetd, mCallback, mAddressCoordinator);
|
reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
|
||||||
when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
|
when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
|
||||||
mTestAddress);
|
mTestAddress);
|
||||||
}
|
}
|
||||||
@@ -471,10 +471,14 @@ public class IpServerTest {
|
|||||||
// Telling the state machine about its upstream interface triggers
|
// Telling the state machine about its upstream interface triggers
|
||||||
// a little more configuration.
|
// a little more configuration.
|
||||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
|
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
|
||||||
InOrder inOrder = inOrder(mNetd);
|
InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
|
||||||
|
|
||||||
|
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
verifyNoMoreInteractions(mNetd, mCallback);
|
|
||||||
|
verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -482,12 +486,19 @@ public class IpServerTest {
|
|||||||
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
||||||
|
|
||||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
||||||
InOrder inOrder = inOrder(mNetd);
|
InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
|
||||||
|
|
||||||
|
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
|
|
||||||
|
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2>.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
verifyNoMoreInteractions(mNetd, mCallback);
|
|
||||||
|
verifyNoMoreInteractions(mNetd, mCallback, mBpfCoordinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -497,10 +508,20 @@ public class IpServerTest {
|
|||||||
doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
|
|
||||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
||||||
InOrder inOrder = inOrder(mNetd);
|
InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
|
||||||
|
|
||||||
|
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
|
|
||||||
|
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
|
||||||
|
// tetherAddForward.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
|
|
||||||
|
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
}
|
}
|
||||||
@@ -513,11 +534,21 @@ public class IpServerTest {
|
|||||||
IFACE_NAME, UPSTREAM_IFACE2);
|
IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
|
|
||||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
||||||
InOrder inOrder = inOrder(mNetd);
|
InOrder inOrder = inOrder(mNetd, mBpfCoordinator);
|
||||||
|
|
||||||
|
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE>.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
|
|
||||||
|
// Add the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> and expect that failed on
|
||||||
|
// ipfwdAddInterfaceForward.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
|
|
||||||
|
// Remove the forwarding pair <IFACE_NAME, UPSTREAM_IFACE2> to fallback.
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
|
||||||
}
|
}
|
||||||
@@ -527,19 +558,22 @@ public class IpServerTest {
|
|||||||
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
||||||
|
|
||||||
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
|
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
|
||||||
InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
|
InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
|
||||||
|
inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
|
||||||
|
inOrder.verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
|
||||||
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
|
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
|
||||||
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
|
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
|
||||||
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
|
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
|
||||||
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
|
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
|
||||||
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
|
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
|
||||||
|
inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
|
||||||
inOrder.verify(mCallback).updateInterfaceState(
|
inOrder.verify(mCallback).updateInterfaceState(
|
||||||
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
|
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
|
||||||
inOrder.verify(mCallback).updateLinkProperties(
|
inOrder.verify(mCallback).updateLinkProperties(
|
||||||
eq(mIpServer), any(LinkProperties.class));
|
eq(mIpServer), any(LinkProperties.class));
|
||||||
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
|
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ import static android.net.NetworkStats.UID_TETHERING;
|
|||||||
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
|
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
|
||||||
import static android.system.OsConstants.ETH_P_IPV6;
|
import static android.system.OsConstants.ETH_P_IPV6;
|
||||||
|
|
||||||
|
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
|
||||||
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
|
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
|
||||||
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
|
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
|
||||||
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
|
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
|
||||||
|
import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
|
||||||
|
import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
|
||||||
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
|
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -70,6 +73,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.dx.mockito.inline.extended.ExtendedMockito;
|
||||||
import com.android.net.module.util.NetworkStackConstants;
|
import com.android.net.module.util.NetworkStackConstants;
|
||||||
import com.android.net.module.util.Struct;
|
import com.android.net.module.util.Struct;
|
||||||
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
|
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
|
||||||
@@ -84,6 +88,7 @@ import org.mockito.ArgumentMatcher;
|
|||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.mockito.MockitoSession;
|
||||||
|
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -1042,6 +1047,59 @@ public class BpfCoordinatorTest {
|
|||||||
verify(mBpfLimitMap).clear();
|
verify(mBpfLimitMap).clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IgnoreUpTo(Build.VERSION_CODES.R)
|
||||||
|
public void testAttachDetachBpfProgram() throws Exception {
|
||||||
|
setupFunctioningNetdInterface();
|
||||||
|
|
||||||
|
// Static mocking for BpfUtils.
|
||||||
|
MockitoSession mockSession = ExtendedMockito.mockitoSession()
|
||||||
|
.mockStatic(BpfUtils.class)
|
||||||
|
.startMocking();
|
||||||
|
try {
|
||||||
|
final String intIface1 = "wlan1";
|
||||||
|
final String intIface2 = "rndis0";
|
||||||
|
final String extIface = "rmnet_data0";
|
||||||
|
final BpfUtils mockMarkerBpfUtils = staticMockMarker(BpfUtils.class);
|
||||||
|
final BpfCoordinator coordinator = makeBpfCoordinator();
|
||||||
|
|
||||||
|
// [1] Add the forwarding pair <wlan1, rmnet_data0>. Expect that attach both wlan1 and
|
||||||
|
// rmnet_data0.
|
||||||
|
coordinator.maybeAttachProgram(intIface1, extIface);
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.attachProgram(extIface, DOWNSTREAM));
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.attachProgram(intIface1, UPSTREAM));
|
||||||
|
ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
|
||||||
|
ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
|
||||||
|
|
||||||
|
// [2] Add the forwarding pair <wlan1, rmnet_data0> again. Expect no more action.
|
||||||
|
coordinator.maybeAttachProgram(intIface1, extIface);
|
||||||
|
ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
|
||||||
|
ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
|
||||||
|
|
||||||
|
// [3] Add the forwarding pair <rndis0, rmnet_data0>. Expect that attach rndis0 only.
|
||||||
|
coordinator.maybeAttachProgram(intIface2, extIface);
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.attachProgram(intIface2, UPSTREAM));
|
||||||
|
ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
|
||||||
|
ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
|
||||||
|
|
||||||
|
// [4] Remove the forwarding pair <rndis0, rmnet_data0>. Expect detach rndis0 only.
|
||||||
|
coordinator.maybeDetachProgram(intIface2, extIface);
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.detachProgram(intIface2));
|
||||||
|
ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
|
||||||
|
ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
|
||||||
|
|
||||||
|
// [5] Remove the forwarding pair <wlan1, rmnet_data0>. Expect that detach both wlan1
|
||||||
|
// and rmnet_data0.
|
||||||
|
coordinator.maybeDetachProgram(intIface1, extIface);
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.detachProgram(extIface));
|
||||||
|
ExtendedMockito.verify(() -> BpfUtils.detachProgram(intIface1));
|
||||||
|
ExtendedMockito.verifyNoMoreInteractions(mockMarkerBpfUtils);
|
||||||
|
ExtendedMockito.clearInvocations(mockMarkerBpfUtils);
|
||||||
|
} finally {
|
||||||
|
mockSession.finishMocking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTetheringConfigSetPollingInterval() throws Exception {
|
public void testTetheringConfigSetPollingInterval() throws Exception {
|
||||||
setupFunctioningNetdInterface();
|
setupFunctioningNetdInterface();
|
||||||
|
|||||||
Reference in New Issue
Block a user