1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.apf; 18 19 import android.util.ArrayMap; 20 import android.util.Log; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.util.Arrays; 25 import java.util.List; 26 import java.util.Map; 27 28 /** 29 * Common counter class for {@code ApfFilter} and {@code LegacyApfFilter}. 30 * 31 * @hide 32 */ 33 public class ApfCounterTracker { 34 /** 35 * APF packet counters. 36 * 37 * Packet counters are 32bit big-endian values, and allocated near the end of the APF data 38 * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4, 39 * the last writable 32bit word. 40 */ 41 public enum Counter { 42 RESERVED_OOB, // Points to offset 0 from the end of the buffer (out-of-bounds) 43 ENDIANNESS, // APFv6 interpreter stores 0x12345678 here 44 TOTAL_PACKETS, // hardcoded in APFv6 interpreter 45 PASSED_ALLOCATE_FAILURE, // hardcoded in APFv6 interpreter 46 PASSED_TRANSMIT_FAILURE, // hardcoded in APFv6 interpreter 47 CORRUPT_DNS_PACKET, // hardcoded in APFv6 interpreter 48 FILTER_AGE_SECONDS, 49 FILTER_AGE_16384THS, 50 APF_VERSION, 51 APF_PROGRAM_ID, 52 // TODO: removing PASSED_ARP after remove LegacyApfFilter.java 53 PASSED_ARP, // see also MIN_PASS_COUNTER below. 54 PASSED_ARP_BROADCAST_REPLY, 55 // TODO: removing PASSED_ARP_NON_IPV4 after remove LegacyApfFilter.java 56 PASSED_ARP_NON_IPV4, 57 PASSED_ARP_REQUEST, 58 PASSED_ARP_UNICAST_REPLY, 59 PASSED_ARP_UNKNOWN, 60 PASSED_DHCP, 61 PASSED_IPV4, 62 PASSED_IPV4_FROM_DHCPV4_SERVER, 63 PASSED_IPV4_UNICAST, 64 PASSED_IPV6_ICMP, 65 PASSED_IPV6_NON_ICMP, 66 PASSED_IPV6_NS_MULTIPLE_OPTIONS, 67 PASSED_IPV6_NS_NO_ADDRESS, 68 PASSED_IPV6_UNICAST_NON_ICMP, 69 PASSED_NON_IP_UNICAST, 70 PASSED_MDNS, 71 PASSED_MLD, // see also MAX_PASS_COUNTER below 72 DROPPED_ETH_BROADCAST, // see also MIN_DROP_COUNTER below 73 DROPPED_RA, 74 DROPPED_IPV4_L2_BROADCAST, 75 DROPPED_IPV4_BROADCAST_ADDR, 76 DROPPED_IPV4_BROADCAST_NET, 77 DROPPED_IPV4_MULTICAST, 78 DROPPED_IPV4_NON_DHCP4, 79 DROPPED_IPV6_ROUTER_SOLICITATION, 80 DROPPED_IPV6_MULTICAST_NA, 81 DROPPED_IPV6_MULTICAST, 82 DROPPED_IPV6_MULTICAST_PING, 83 DROPPED_IPV6_NON_ICMP_MULTICAST, 84 DROPPED_IPV6_NS_INVALID, 85 DROPPED_IPV6_NS_OTHER_HOST, 86 DROPPED_802_3_FRAME, 87 DROPPED_ETHERTYPE_NOT_ALLOWED, 88 DROPPED_IPV4_KEEPALIVE_ACK, 89 DROPPED_IPV6_KEEPALIVE_ACK, 90 DROPPED_IPV4_NATT_KEEPALIVE, 91 DROPPED_MDNS, 92 DROPPED_IPV4_TCP_PORT7_UNICAST, 93 DROPPED_ARP_NON_IPV4, 94 DROPPED_ARP_OTHER_HOST, 95 DROPPED_ARP_REPLY_SPA_NO_HOST, 96 DROPPED_ARP_REQUEST_ANYHOST, 97 DROPPED_ARP_REQUEST_REPLIED, 98 DROPPED_ARP_UNKNOWN, 99 DROPPED_ARP_V6_ONLY, 100 DROPPED_GARP_REPLY; // see also MAX_DROP_COUNTER below 101 102 /** 103 * Returns the negative byte offset from the end of the APF data segment for 104 * a given counter. 105 */ offset()106 public int offset() { 107 return -this.ordinal() * 4; // Currently, all counters are 32bit long. 108 } 109 110 /** 111 * Returns the counter sequence number from the end of the APF data segment for 112 * a given counter. 113 */ value()114 public int value() { 115 return this.ordinal(); 116 } 117 118 /** 119 * Returns the total size of the data segment in bytes. 120 */ totalSize()121 public static int totalSize() { 122 return (Counter.class.getEnumConstants().length - 1) * 4; 123 } 124 125 /** 126 * Returns the counter enum based on the offset. 127 */ 128 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getCounterEnumFromOffset(int offset)129 public static Counter getCounterEnumFromOffset(int offset) { 130 for (Counter cnt : Counter.class.getEnumConstants()) { 131 if (cnt.offset() == offset) { 132 return cnt; 133 } 134 } 135 return RESERVED_OOB; 136 } 137 } 138 139 public static final Counter MIN_DROP_COUNTER = Counter.DROPPED_ETH_BROADCAST; 140 public static final Counter MAX_DROP_COUNTER = Counter.DROPPED_GARP_REPLY; 141 public static final Counter MIN_PASS_COUNTER = Counter.PASSED_ARP; 142 public static final Counter MAX_PASS_COUNTER = Counter.PASSED_MLD; 143 144 private static final String TAG = ApfCounterTracker.class.getSimpleName(); 145 146 private final List<Counter> mCounterList; 147 // Store the counters' value 148 private final Map<Counter, Long> mCounters = new ArrayMap<>(); 149 ApfCounterTracker()150 public ApfCounterTracker() { 151 Counter[] counters = Counter.class.getEnumConstants(); 152 mCounterList = Arrays.asList(counters).subList(1, counters.length); 153 } 154 155 /** 156 * Get the value of a counter from APF data. 157 */ getCounterValue(byte[] data, Counter counter)158 public static long getCounterValue(byte[] data, Counter counter) 159 throws ArrayIndexOutOfBoundsException { 160 int offset = data.length + Counter.ENDIANNESS.offset(); 161 int endianness = 0; 162 for (int i = 0; i < 4; i++) { 163 endianness = endianness << 8 | (data[offset + i] & 0xff); 164 } 165 // Follow the same wrap-around addressing scheme of the interpreter. 166 offset = data.length + counter.offset(); 167 168 boolean isBe = true; 169 switch (endianness) { 170 case 0: 171 case 0x12345678: 172 isBe = true; 173 break; 174 case 0x78563412: 175 isBe = false; 176 break; 177 default: 178 Log.wtf(TAG, "Unknown endianness: 0x" + Integer.toHexString(endianness)); 179 } 180 181 // Decode 32bit big-endian integer into a long so we can count up beyond 2^31. 182 long value = 0; 183 for (int i = 0; i < 4; i++) { 184 value = value << 8 | (data[offset + (isBe ? i : 3 - i)] & 0xff); 185 } 186 return value; 187 } 188 189 /** 190 * Update counters from APF data. 191 */ updateCountersFromData(byte[] data)192 public void updateCountersFromData(byte[] data) { 193 if (data == null) return; 194 for (Counter counter : mCounterList) { 195 long value; 196 try { 197 value = getCounterValue(data, counter); 198 } catch (ArrayIndexOutOfBoundsException e) { 199 value = 0; 200 } 201 long oldValue = mCounters.getOrDefault(counter, 0L); 202 // All counters are increamental 203 if (value > oldValue) { 204 mCounters.put(counter, value); 205 } 206 } 207 } 208 209 /** 210 * Get counters map. 211 */ getCounters()212 public Map<Counter, Long> getCounters() { 213 return mCounters; 214 } 215 } 216