From 347dd9062064345e75fb16a69d9faa76ac9703dc Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Wed, 23 Dec 2020 21:08:32 +0800 Subject: [PATCH 1/2] [NFCT.TETHER.9] Build IPv4 offload BPF rules for raw ip Build the upstream and upstream rules when the conntrack event is received. Test: atest TetheringCoverageTests Change-Id: Ibb52c7b75812bd586091d809e260bc9206c06262 --- .../tethering/BpfCoordinator.java | 86 +++++++++++++++- .../tethering/TetherDownstream4Key.java | 79 +++++++++++++++ .../tethering/TetherDownstream4Value.java | 97 +++++++++++++++++++ .../tethering/TetherUpstream4Key.java | 81 ++++++++++++++++ .../tethering/TetherUpstream4Value.java | 97 +++++++++++++++++++ 5 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 Tethering/src/com/android/networkstack/tethering/TetherDownstream4Key.java create mode 100644 Tethering/src/com/android/networkstack/tethering/TetherDownstream4Value.java create mode 100644 Tethering/src/com/android/networkstack/tethering/TetherUpstream4Key.java create mode 100644 Tethering/src/com/android/networkstack/tethering/TetherUpstream4Value.java diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 3268e94993..717bf6189b 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -23,7 +23,9 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; +import static android.net.ip.ConntrackMonitor.ConntrackEvent; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; +import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; @@ -38,6 +40,7 @@ import android.net.TetherOffloadRuleParcel; import android.net.ip.ConntrackMonitor; import android.net.ip.ConntrackMonitor.ConntrackEventConsumer; import android.net.ip.IpServer; +import android.net.netlink.NetlinkConstants; import android.net.netstats.provider.NetworkStatsProvider; import android.net.util.InterfaceParams; import android.net.util.SharedLog; @@ -82,6 +85,8 @@ import java.util.Set; public class BpfCoordinator { private static final String TAG = BpfCoordinator.class.getSimpleName(); private static final int DUMP_TIMEOUT_MS = 10_000; + private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString( + "00:00:00:00:00:00"); private static final String TETHER_DOWNSTREAM6_FS_PATH = "/sys/fs/bpf/tethering/map_offload_tether_downstream6_map"; private static final String TETHER_STATS_MAP_PATH = @@ -174,6 +179,9 @@ public class BpfCoordinator { // - Must only be modified by that IpServer. // - Is created when the IpServer adds its first client, and deleted when the IpServer deletes // its last client. + // Note that relying on the client address for finding downstream is okay for now because the + // client address is unique. See PrivateAddressCoordinator#requestDownstreamAddress. + // TODO: Refactor if any possible that the client address is not unique. private final HashMap> mTetherClients = new HashMap<>(); @@ -854,8 +862,84 @@ public class BpfCoordinator { } } + @Nullable + private ClientInfo getClientInfo(@NonNull Inet4Address clientAddress) { + for (HashMap clients : mTetherClients.values()) { + for (ClientInfo client : clients.values()) { + if (clientAddress.equals(client.clientAddress)) { + return client; + } + } + } + return null; + } + + // Support raw ip only. + // TODO: add ether ip support. private class BpfConntrackEventConsumer implements ConntrackEventConsumer { - public void accept(ConntrackMonitor.ConntrackEvent e) { /* TODO */ } + @NonNull + private TetherUpstream4Key makeTetherUpstream4Key( + @NonNull ConntrackEvent e, @NonNull ClientInfo c) { + return new TetherUpstream4Key(c.downstreamIfindex, c.downstreamMac, + e.tupleOrig.protoNum, e.tupleOrig.srcIp.getAddress(), + e.tupleOrig.dstIp.getAddress(), e.tupleOrig.srcPort, e.tupleOrig.dstPort); + } + + @NonNull + private TetherDownstream4Key makeTetherDownstream4Key( + @NonNull ConntrackEvent e, @NonNull ClientInfo c, int upstreamIndex) { + return new TetherDownstream4Key(upstreamIndex, NULL_MAC_ADDRESS /* dstMac (rawip) */, + e.tupleReply.protoNum, e.tupleReply.srcIp.getAddress(), + e.tupleReply.dstIp.getAddress(), e.tupleReply.srcPort, e.tupleReply.dstPort); + } + + @NonNull + private TetherUpstream4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e, + int upstreamIndex) { + // TODO: convert {src46, dst46} from ipv4 address (a.b.c.d) to ipv4-mapped address + // (::ffff:a.b.d.d). + return new TetherUpstream4Value(upstreamIndex, + NULL_MAC_ADDRESS /* ethDstMac (rawip) */, + NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP, + NetworkStackConstants.ETHER_MTU, e.tupleReply.dstIp.getAddress(), + e.tupleReply.srcIp.getAddress(), e.tupleReply.dstPort, e.tupleReply.srcPort, + 0 /* lastUsed, filled by bpf prog only */); + } + + @NonNull + private TetherDownstream4Value makeTetherDownstream4Value(@NonNull ConntrackEvent e, + @NonNull ClientInfo c, int upstreamIndex) { + return new TetherDownstream4Value(c.downstreamIfindex, + c.clientMac, c.downstreamMac, ETH_P_IP, NetworkStackConstants.ETHER_MTU, + e.tupleOrig.dstIp.getAddress(), e.tupleOrig.srcIp.getAddress(), + e.tupleOrig.dstPort, e.tupleOrig.srcPort, + 0 /* lastUsed, filled by bpf prog only */); + } + + public void accept(ConntrackEvent e) { + final ClientInfo tetherClient = getClientInfo(e.tupleOrig.srcIp); + if (tetherClient == null) return; + + final Integer upstreamIndex = mIpv4UpstreamIndices.get(e.tupleReply.dstIp); + if (upstreamIndex == null) return; + + final TetherUpstream4Key upstream4Key = makeTetherUpstream4Key(e, tetherClient); + final TetherDownstream4Key downstream4Key = makeTetherDownstream4Key(e, + tetherClient, upstreamIndex); + + if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 + | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) { + // TODO: remove ingress and egress rules from BPF maps. + return; + } + + final TetherUpstream4Value upstream4Value = makeTetherUpstream4Value(e, + upstreamIndex); + final TetherDownstream4Value downstream4Value = makeTetherDownstream4Value(e, + tetherClient, upstreamIndex); + + // TODO: insert ingress and egress rules to BPF maps. + } } private boolean isBpfEnabled() { diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Key.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Key.java new file mode 100644 index 0000000000..51b1f76f35 --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Key.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 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 android.net.MacAddress; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Objects; + +/** The key of BpfMap which is used for IPv4 bpf offload. */ +public class TetherDownstream4Key extends Struct { + @Field(order = 0, type = Type.U32) + public final long iif; + + @Field(order = 1, type = Type.EUI48) + public final MacAddress dstMac; + + @Field(order = 2, type = Type.U8, padding = 1) + public final short l4proto; + + @Field(order = 3, type = Type.ByteArray, arraysize = 4) + public final byte[] src4; + + @Field(order = 4, type = Type.ByteArray, arraysize = 4) + public final byte[] dst4; + + @Field(order = 5, type = Type.UBE16) + public final int srcPort; + + @Field(order = 6, type = Type.UBE16) + public final int dstPort; + + public TetherDownstream4Key(final long iif, final MacAddress dstMac, final short l4proto, + final byte[] src4, final byte[] dst4, final int srcPort, + final int dstPort) { + Objects.requireNonNull(dstMac); + + this.iif = iif; + this.dstMac = dstMac; + this.l4proto = l4proto; + this.src4 = src4; + this.dst4 = dst4; + this.srcPort = srcPort; + this.dstPort = dstPort; + } + + @Override + public String toString() { + try { + return String.format( + "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, " + + "srcPort: %d, dstPort: %d", + iif, dstMac, l4proto, + Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4), + Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort)); + } catch (UnknownHostException | IllegalArgumentException e) { + return String.format("Invalid IP address", e); + } + } +} diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Value.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Value.java new file mode 100644 index 0000000000..56ea56675b --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream4Value.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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 android.net.MacAddress; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** The value of BpfMap which is used for IPv4 bpf offload. */ +public class TetherDownstream4Value extends Struct { + @Field(order = 0, type = Type.U32) + public final long oif; + + // The ethhdr struct which is defined in uapi/linux/if_ether.h + @Field(order = 1, type = Type.EUI48) + public final MacAddress ethDstMac; + @Field(order = 2, type = Type.EUI48) + public final MacAddress ethSrcMac; + @Field(order = 3, type = Type.UBE16) + public final int ethProto; // Packet type ID field. + + @Field(order = 4, type = Type.U16) + public final int pmtu; + + @Field(order = 5, type = Type.ByteArray, arraysize = 4) + public final byte[] src4; + + @Field(order = 6, type = Type.ByteArray, arraysize = 4) + public final byte[] dst4; + + @Field(order = 7, type = Type.UBE16) + public final int srcPort; + + @Field(order = 8, type = Type.UBE16) + public final int dstPort; + + // TODO: consider using U64. + @Field(order = 9, type = Type.U63) + public final long lastUsed; + + public TetherDownstream4Value(final long oif, @NonNull final MacAddress ethDstMac, + @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu, + final byte[] src4, final byte[] dst4, final int srcPort, + final int dstPort, final long lastUsed) { + Objects.requireNonNull(ethDstMac); + Objects.requireNonNull(ethSrcMac); + + this.oif = oif; + this.ethDstMac = ethDstMac; + this.ethSrcMac = ethSrcMac; + this.ethProto = ethProto; + this.pmtu = pmtu; + this.src4 = src4; + this.dst4 = dst4; + this.srcPort = srcPort; + this.dstPort = dstPort; + this.lastUsed = lastUsed; + } + + @Override + public String toString() { + try { + return String.format( + "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, " + + "src4: %s, dst4: %s, srcPort: %d, dstPort: %d, " + + "lastUsed: %d", + oif, ethDstMac, ethSrcMac, ethProto, pmtu, + InetAddress.getByAddress(src4), InetAddress.getByAddress(dst4), + Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort), + lastUsed); + } catch (UnknownHostException | IllegalArgumentException e) { + return String.format("Invalid IP address", e); + } + } +} diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Key.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Key.java new file mode 100644 index 0000000000..77cfa996fe --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Key.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 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 android.net.MacAddress; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.Objects; + +/** The key of BpfMap which is used for IPv4 bpf offload. */ +public class TetherUpstream4Key extends Struct { + @Field(order = 0, type = Type.U32) + public final long iif; + + @Field(order = 1, type = Type.EUI48) + public final MacAddress dstMac; + + @Field(order = 2, type = Type.U8, padding = 1) + public final short l4proto; + + @Field(order = 3, type = Type.ByteArray, arraysize = 4) + public final byte[] src4; + + @Field(order = 4, type = Type.ByteArray, arraysize = 4) + public final byte[] dst4; + + @Field(order = 5, type = Type.UBE16) + public final int srcPort; + + @Field(order = 6, type = Type.UBE16) + public final int dstPort; + + public TetherUpstream4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto, + final byte[] src4, final byte[] dst4, final int srcPort, + final int dstPort) { + Objects.requireNonNull(dstMac); + + this.iif = iif; + this.dstMac = dstMac; + this.l4proto = l4proto; + this.src4 = src4; + this.dst4 = dst4; + this.srcPort = srcPort; + this.dstPort = dstPort; + } + + @Override + public String toString() { + try { + return String.format( + "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, " + + "srcPort: %d, dstPort: %d", + iif, dstMac, l4proto, + Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4), + Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort)); + } catch (UnknownHostException | IllegalArgumentException e) { + return String.format("Invalid IP address", e); + } + } +} diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Value.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Value.java new file mode 100644 index 0000000000..e1ff688dd5 --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream4Value.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 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 android.net.MacAddress; + +import androidx.annotation.NonNull; + +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** The value of BpfMap which is used for IPv4 bpf offload. */ +public class TetherUpstream4Value extends Struct { + @Field(order = 0, type = Type.U32) + public final long oif; + + // The ethhdr struct which is defined in uapi/linux/if_ether.h + @Field(order = 1, type = Type.EUI48) + public final MacAddress ethDstMac; + @Field(order = 2, type = Type.EUI48) + public final MacAddress ethSrcMac; + @Field(order = 3, type = Type.UBE16) + public final int ethProto; // Packet type ID field. + + @Field(order = 4, type = Type.U16) + public final int pmtu; + + @Field(order = 5, type = Type.ByteArray, arraysize = 16) + public final byte[] src46; + + @Field(order = 6, type = Type.ByteArray, arraysize = 16) + public final byte[] dst46; + + @Field(order = 7, type = Type.UBE16) + public final int srcPort; + + @Field(order = 8, type = Type.UBE16) + public final int dstPort; + + // TODO: consider using U64. + @Field(order = 9, type = Type.U63) + public final long lastUsed; + + public TetherUpstream4Value(final long oif, @NonNull final MacAddress ethDstMac, + @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu, + final byte[] src46, final byte[] dst46, final int srcPort, + final int dstPort, final long lastUsed) { + Objects.requireNonNull(ethDstMac); + Objects.requireNonNull(ethSrcMac); + + this.oif = oif; + this.ethDstMac = ethDstMac; + this.ethSrcMac = ethSrcMac; + this.ethProto = ethProto; + this.pmtu = pmtu; + this.src46 = src46; + this.dst46 = dst46; + this.srcPort = srcPort; + this.dstPort = dstPort; + this.lastUsed = lastUsed; + } + + @Override + public String toString() { + try { + return String.format( + "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, " + + "src46: %s, dst46: %s, srcPort: %d, dstPort: %d, " + + "lastUsed: %d", + oif, ethDstMac, ethSrcMac, ethProto, pmtu, + InetAddress.getByAddress(src46), InetAddress.getByAddress(dst46), + Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort), + lastUsed); + } catch (UnknownHostException | IllegalArgumentException e) { + return String.format("Invalid IP address", e); + } + } +} From dd833dee3102ace586f49c96ea7e8fa7249082f2 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 22 Jan 2021 21:19:43 +0800 Subject: [PATCH 2/2] [NFCT.TETHER.10] Add/delete IPv4 offload BPF rules to/from BPF map Access the IPv4 downstream and upstream BPF map with the built rules. Test: atest TetheringCoverageTests Change-Id: I8cd6e49b377c72250988019eea57f93cccd78309 --- .../apishim/api30/BpfCoordinatorShimImpl.java | 30 ++++++ .../apishim/api31/BpfCoordinatorShimImpl.java | 96 ++++++++++++++++++- .../apishim/common/BpfCoordinatorShim.java | 26 +++++ .../tethering/BpfCoordinator.java | 55 +++++++++-- .../unit/src/android/net/ip/IpServerTest.java | 18 ++++ .../tethering/BpfCoordinatorTest.java | 14 +++ 6 files changed, 228 insertions(+), 11 deletions(-) diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java index eafa3eae4c..90b9b3f7b2 100644 --- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java @@ -28,7 +28,11 @@ import androidx.annotation.Nullable; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.TetherDownstream4Key; +import com.android.networkstack.tethering.TetherDownstream4Value; import com.android.networkstack.tethering.TetherStatsValue; +import com.android.networkstack.tethering.TetherUpstream4Key; +import com.android.networkstack.tethering.TetherUpstream4Value; /** * Bpf coordinator class for API shims. @@ -131,6 +135,32 @@ public class BpfCoordinatorShimImpl } } + @Override + public boolean tetherOffloadRuleAdd(@NonNull TetherDownstream4Key key, + @NonNull TetherDownstream4Value value) { + /* no op */ + return true; + } + + @Override + public boolean tetherOffloadRuleRemove(@NonNull TetherDownstream4Key key) { + /* no op */ + return true; + } + + @Override + public boolean tetherOffloadRuleAdd(@NonNull TetherUpstream4Key key, + @NonNull TetherUpstream4Value value) { + /* no op */ + return true; + } + + @Override + public boolean tetherOffloadRuleRemove(@NonNull TetherUpstream4Key key) { + /* no op */ + return true; + } + @Override public String toString() { return "Netd used"; diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java index c0d85aeb25..b9ce769f38 100644 --- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java @@ -30,12 +30,16 @@ import androidx.annotation.Nullable; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.BpfMap; +import com.android.networkstack.tethering.TetherDownstream4Key; +import com.android.networkstack.tethering.TetherDownstream4Value; import com.android.networkstack.tethering.TetherDownstream6Key; import com.android.networkstack.tethering.TetherDownstream6Value; import com.android.networkstack.tethering.TetherLimitKey; import com.android.networkstack.tethering.TetherLimitValue; import com.android.networkstack.tethering.TetherStatsKey; import com.android.networkstack.tethering.TetherStatsValue; +import com.android.networkstack.tethering.TetherUpstream4Key; +import com.android.networkstack.tethering.TetherUpstream4Value; import java.io.FileDescriptor; @@ -54,6 +58,16 @@ public class BpfCoordinatorShimImpl @NonNull private final SharedLog mLog; + // BPF map of ingress queueing discipline which pre-processes the packets by the IPv4 + // downstream rules. + @Nullable + private final BpfMap mBpfDownstream4Map; + + // BPF map of ingress queueing discipline which pre-processes the packets by the IPv4 + // upstream rules. + @Nullable + private final BpfMap mBpfUpstream4Map; + // BPF map of ingress queueing discipline which pre-processes the packets by the IPv6 // forwarding rules. @Nullable @@ -69,6 +83,8 @@ public class BpfCoordinatorShimImpl public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) { mLog = deps.getSharedLog().forSubComponent(TAG); + mBpfDownstream4Map = deps.getBpfDownstream4Map(); + mBpfUpstream4Map = deps.getBpfUpstream4Map(); mBpfDownstream6Map = deps.getBpfDownstream6Map(); mBpfStatsMap = deps.getBpfStatsMap(); mBpfLimitMap = deps.getBpfLimitMap(); @@ -76,7 +92,8 @@ public class BpfCoordinatorShimImpl @Override public boolean isInitialized() { - return mBpfDownstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null; + return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null + && mBpfStatsMap != null && mBpfLimitMap != null; } @Override @@ -232,15 +249,86 @@ public class BpfCoordinatorShimImpl return statsValue; } + @Override + public boolean tetherOffloadRuleAdd(@NonNull TetherDownstream4Key key, + @NonNull TetherDownstream4Value value) { + if (!isInitialized()) return false; + + try { + // The last used time field of the value is updated by the bpf program. Adding the same + // map pair twice causes the unexpected refresh. Must be fixed before starting the + // conntrack timeout extension implementation. + // TODO: consider using insertEntry. + mBpfDownstream4Map.updateEntry(key, value); + } catch (ErrnoException e) { + mLog.e("Could not update entry: ", e); + return false; + } + return true; + } + + @Override + public boolean tetherOffloadRuleRemove(@NonNull TetherDownstream4Key key) { + if (!isInitialized()) return false; + + try { + mBpfDownstream4Map.deleteEntry(key); + } catch (ErrnoException e) { + // Silent if the rule did not exist. + if (e.errno != OsConstants.ENOENT) { + mLog.e("Could not delete entry: ", e); + return false; + } + } + return true; + } + + @Override + public boolean tetherOffloadRuleAdd(@NonNull TetherUpstream4Key key, + @NonNull TetherUpstream4Value value) { + if (!isInitialized()) return false; + + try { + // The last used time field of the value is updated by the bpf program. Adding the same + // map pair twice causes the unexpected refresh. Must be fixed before starting the + // conntrack timeout extension implementation. + // TODO: consider using insertEntry. + mBpfUpstream4Map.updateEntry(key, value); + } catch (ErrnoException e) { + mLog.e("Could not update entry: ", e); + return false; + } + return true; + } + + @Override + public boolean tetherOffloadRuleRemove(@NonNull TetherUpstream4Key key) { + if (!isInitialized()) return false; + + try { + mBpfUpstream4Map.deleteEntry(key); + } catch (ErrnoException e) { + // Silent if the rule did not exist. + if (e.errno != OsConstants.ENOENT) { + mLog.e("Could not delete entry: ", e); + return false; + } + } + return true; + } + @Override public String toString() { - return "mBpfDownstream6Map{" + return "mBpfDownstream4Map{" + + (mBpfDownstream4Map != null ? "initialized" : "not initialized") + "}, " + + "mBpfUpstream4Map{" + + (mBpfUpstream4Map != null ? "initialized" : "not initialized") + "}, " + + "mBpfDownstream6Map{" + (mBpfDownstream6Map != null ? "initialized" : "not initialized") + "}, " + "mBpfStatsMap{" + (mBpfStatsMap != null ? "initialized" : "not initialized") + "}, " + "mBpfLimitMap{" - + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} " - + "}"; + + (mBpfLimitMap != null ? "initialized" : "not initialized") + "} "; } /** diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java index 61abfa3aaa..36d2de1a5c 100644 --- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java +++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java @@ -23,7 +23,11 @@ import androidx.annotation.Nullable; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.TetherDownstream4Key; +import com.android.networkstack.tethering.TetherDownstream4Value; import com.android.networkstack.tethering.TetherStatsValue; +import com.android.networkstack.tethering.TetherUpstream4Key; +import com.android.networkstack.tethering.TetherUpstream4Value; /** * Bpf coordinator class for API shims. @@ -108,5 +112,27 @@ public abstract class BpfCoordinatorShim { */ @Nullable public abstract TetherStatsValue tetherOffloadGetAndClearStats(int ifIndex); + + /** + * Adds a tethering IPv4 downstream offload rule to BPF map. + */ + public abstract boolean tetherOffloadRuleAdd(@NonNull TetherDownstream4Key key, + @NonNull TetherDownstream4Value value); + + /** + * Deletes a tethering IPv4 downstream offload rule from the BPF map. + */ + public abstract boolean tetherOffloadRuleRemove(@NonNull TetherDownstream4Key key); + + /** + * Adds a tethering IPv4 upstream offload rule to BPF map. + */ + public abstract boolean tetherOffloadRuleAdd(@NonNull TetherUpstream4Key key, + @NonNull TetherUpstream4Value value); + + /** + * Deletes a tethering IPv4 upstream offload rule from the BPF map. + */ + public abstract boolean tetherOffloadRuleRemove(@NonNull TetherUpstream4Key key); } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 717bf6189b..e4216d85b8 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -87,6 +87,10 @@ public class BpfCoordinator { private static final int DUMP_TIMEOUT_MS = 10_000; private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString( "00:00:00:00:00:00"); + private static final String TETHER_DOWNSTREAM4_MAP_PATH = + "/sys/fs/bpf/tethering/map_offload_tether_downstream4_map"; + private static final String TETHER_UPSTREAM4_MAP_PATH = + "/sys/fs/bpf/tethering/map_offload_tether_upstream4_map"; private static final String TETHER_DOWNSTREAM6_FS_PATH = "/sys/fs/bpf/tethering/map_offload_tether_downstream6_map"; private static final String TETHER_STATS_MAP_PATH = @@ -232,6 +236,30 @@ public class BpfCoordinator { return SdkLevel.isAtLeastS(); } + /** Get downstream4 BPF map. */ + @Nullable public BpfMap + getBpfDownstream4Map() { + try { + return new BpfMap<>(TETHER_DOWNSTREAM4_MAP_PATH, + BpfMap.BPF_F_RDWR, TetherDownstream4Key.class, TetherDownstream4Value.class); + } catch (ErrnoException e) { + Log.e(TAG, "Cannot create downstream4 map: " + e); + return null; + } + } + + /** Get upstream4 BPF map. */ + @Nullable public BpfMap + getBpfUpstream4Map() { + try { + return new BpfMap<>(TETHER_UPSTREAM4_MAP_PATH, + BpfMap.BPF_F_RDWR, TetherUpstream4Key.class, TetherUpstream4Value.class); + } catch (ErrnoException e) { + Log.e(TAG, "Cannot create upstream4 map: " + e); + return null; + } + } + /** Get downstream6 BPF map. */ @Nullable public BpfMap getBpfDownstream6Map() { @@ -896,14 +924,12 @@ public class BpfCoordinator { @NonNull private TetherUpstream4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e, int upstreamIndex) { - // TODO: convert {src46, dst46} from ipv4 address (a.b.c.d) to ipv4-mapped address - // (::ffff:a.b.d.d). return new TetherUpstream4Value(upstreamIndex, NULL_MAC_ADDRESS /* ethDstMac (rawip) */, NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP, - NetworkStackConstants.ETHER_MTU, e.tupleReply.dstIp.getAddress(), - e.tupleReply.srcIp.getAddress(), e.tupleReply.dstPort, e.tupleReply.srcPort, - 0 /* lastUsed, filled by bpf prog only */); + NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(e.tupleReply.dstIp), + toIpv4MappedAddressBytes(e.tupleReply.srcIp), e.tupleReply.dstPort, + e.tupleReply.srcPort, 0 /* lastUsed, filled by bpf prog only */); } @NonNull @@ -916,6 +942,19 @@ public class BpfCoordinator { 0 /* lastUsed, filled by bpf prog only */); } + @NonNull + private byte[] toIpv4MappedAddressBytes(Inet4Address ia4) { + final byte[] addr4 = ia4.getAddress(); + final byte[] addr6 = new byte[16]; + addr6[10] = (byte) 0xff; + addr6[11] = (byte) 0xff; + addr6[12] = addr4[0]; + addr6[13] = addr4[1]; + addr6[14] = addr4[2]; + addr6[15] = addr4[3]; + return addr6; + } + public void accept(ConntrackEvent e) { final ClientInfo tetherClient = getClientInfo(e.tupleOrig.srcIp); if (tetherClient == null) return; @@ -929,7 +968,8 @@ public class BpfCoordinator { if (e.msgType == (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | NetlinkConstants.IPCTNL_MSG_CT_DELETE)) { - // TODO: remove ingress and egress rules from BPF maps. + mBpfCoordinatorShim.tetherOffloadRuleRemove(upstream4Key); + mBpfCoordinatorShim.tetherOffloadRuleRemove(downstream4Key); return; } @@ -938,7 +978,8 @@ public class BpfCoordinator { final TetherDownstream4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient, upstreamIndex); - // TODO: insert ingress and egress rules to BPF maps. + mBpfCoordinatorShim.tetherOffloadRuleAdd(upstream4Key, upstream4Value); + mBpfCoordinatorShim.tetherOffloadRuleAdd(downstream4Key, downstream4Value); } } diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index e20e011598..9b42c73c79 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -104,12 +104,16 @@ import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.BpfMap; import com.android.networkstack.tethering.PrivateAddressCoordinator; +import com.android.networkstack.tethering.TetherDownstream4Key; +import com.android.networkstack.tethering.TetherDownstream4Value; import com.android.networkstack.tethering.TetherDownstream6Key; import com.android.networkstack.tethering.TetherDownstream6Value; import com.android.networkstack.tethering.TetherLimitKey; import com.android.networkstack.tethering.TetherLimitValue; import com.android.networkstack.tethering.TetherStatsKey; import com.android.networkstack.tethering.TetherStatsValue; +import com.android.networkstack.tethering.TetherUpstream4Key; +import com.android.networkstack.tethering.TetherUpstream4Value; import com.android.networkstack.tethering.TetheringConfiguration; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; @@ -173,6 +177,8 @@ public class IpServerTest { @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; + @Mock private BpfMap mBpfDownstream4Map; + @Mock private BpfMap mBpfUpstream4Map; @Mock private BpfMap mBpfDownstream6Map; @Mock private BpfMap mBpfStatsMap; @Mock private BpfMap mBpfLimitMap; @@ -302,6 +308,18 @@ public class IpServerTest { return mConntrackMonitor; } + @Nullable + public BpfMap + getBpfDownstream4Map() { + return mBpfDownstream4Map; + } + + @Nullable + public BpfMap + getBpfUpstream4Map() { + return mBpfUpstream4Map; + } + @Nullable public BpfMap getBpfDownstream6Map() { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index 764e6516d1..30b4bf4eeb 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -158,6 +158,8 @@ public class BpfCoordinatorTest { @Mock private IpServer mIpServer2; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; + @Mock private BpfMap mBpfDownstream4Map; + @Mock private BpfMap mBpfUpstream4Map; @Mock private BpfMap mBpfDownstream6Map; // Late init since methods must be called by the thread that created this object. @@ -202,6 +204,18 @@ public class BpfCoordinatorTest { return mConntrackMonitor; } + @Nullable + public BpfMap + getBpfDownstream4Map() { + return mBpfDownstream4Map; + } + + @Nullable + public BpfMap + getBpfUpstream4Map() { + return mBpfUpstream4Map; + } + @Nullable public BpfMap getBpfDownstream6Map() {