• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * Counter class for {@code ApfFilter}.
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         EXCEPTIONS,              // hardcoded in APFv6.1 interpreter
49         FILTER_AGE_SECONDS,
50         FILTER_AGE_16384THS,
51         APF_VERSION,
52         APF_PROGRAM_ID,
53         // The counter sequence should keep the same as ApfSessionInfoMetrics.java
54         PASSED_ARP_BROADCAST_REPLY,  // see also MIN_PASS_COUNTER below.
55         PASSED_ARP_REQUEST,
56         PASSED_ARP_UNICAST_REPLY,
57         PASSED_DHCP,
58         PASSED_ETHER_OUR_SRC_MAC,
59         PASSED_IPV4,
60         PASSED_IPV4_FROM_DHCPV4_SERVER,
61         PASSED_IPV4_UNICAST,
62         PASSED_IPV6_HOPOPTS,
63         PASSED_IPV6_ICMP,
64         PASSED_IPV6_NON_ICMP,
65         PASSED_IPV6_UNICAST_NON_ICMP,
66         PASSED_NON_IP_UNICAST,
67         PASSED_MDNS, // see also MAX_PASS_COUNTER below
68         DROPPED_ETH_BROADCAST,  // see also MIN_DROP_COUNTER below
69         DROPPED_ETHER_OUR_SRC_MAC,
70         DROPPED_RA,
71         DROPPED_IPV4_L2_BROADCAST,
72         DROPPED_IPV4_BROADCAST_ADDR,
73         DROPPED_IPV4_BROADCAST_NET,
74         DROPPED_IPV4_ICMP_INVALID,
75         DROPPED_IPV4_MULTICAST,
76         DROPPED_IPV4_NON_DHCP4,
77         DROPPED_IPV4_PING_REQUEST_REPLIED,
78         DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID,
79         DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED,
80         DROPPED_IPV6_ROUTER_SOLICITATION,
81         DROPPED_IPV6_MLD_INVALID,
82         DROPPED_IPV6_MLD_REPORT,
83         DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED,
84         DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED,
85         DROPPED_IPV6_MULTICAST_NA,
86         DROPPED_IPV6_NON_ICMP_MULTICAST,
87         DROPPED_IPV6_NS_INVALID,
88         DROPPED_IPV6_NS_OTHER_HOST,
89         DROPPED_IPV6_NS_REPLIED_NON_DAD,
90         DROPPED_802_3_FRAME,
91         DROPPED_ETHERTYPE_NOT_ALLOWED,
92         DROPPED_IPV4_KEEPALIVE_ACK,
93         DROPPED_IPV4_NATT_KEEPALIVE,
94         DROPPED_MDNS,
95         DROPPED_MDNS_REPLIED,
96         DROPPED_NON_UNICAST_TDLS,
97         DROPPED_IPV4_TCP_PORT7_UNICAST,
98         DROPPED_ARP_NON_IPV4,
99         DROPPED_ARP_OTHER_HOST,
100         DROPPED_ARP_REPLY_SPA_NO_HOST,
101         DROPPED_ARP_REQUEST_REPLIED,
102         DROPPED_ARP_UNKNOWN,
103         DROPPED_ARP_V6_ONLY,
104         DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED,
105         DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED,
106         DROPPED_IGMP_INVALID,
107         DROPPED_IGMP_REPORT,
108         DROPPED_GARP_REPLY;  // see also MAX_DROP_COUNTER below
109 
110         /**
111          * Returns the negative byte offset from the end of the APF data segment for
112          * a given counter.
113          */
offset()114         public int offset() {
115             return -this.value() * 4;  // Currently, all counters are 32bit long.
116         }
117 
118         /**
119          * Returns the counter sequence number from the end of the APF data segment for
120          * a given counter.
121          */
value()122         public int value() {
123             return this.ordinal();
124         }
125 
126         /**
127          * Returns the total size of the data segment in bytes.
128          */
totalSize()129         public static int totalSize() {
130             return (Counter.class.getEnumConstants().length - 1) * 4;
131         }
132 
133         /**
134          * Returns the counter enum based on the offset.
135          */
136         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getCounterEnumFromOffset(int offset)137         public static Counter getCounterEnumFromOffset(int offset) {
138             for (Counter cnt : Counter.class.getEnumConstants()) {
139                 if (cnt.offset() == offset) {
140                     return cnt;
141                 }
142             }
143             return RESERVED_OOB;
144         }
145 
checkCounterRange(Counter lowerBound, Counter upperBound)146         private void checkCounterRange(Counter lowerBound, Counter upperBound) {
147             if (value() < lowerBound.value() || value() > upperBound.value()) {
148                 throw new IllegalArgumentException(
149                         String.format("Counter %s, is not in range [%s, %s]", this,
150                                 lowerBound, upperBound));
151             }
152         }
153 
154         /**
155          * Return the label such that if we jump to it, the counter will be increased by 1 and
156          * the packet will be passed.
157          */
getJumpPassLabel()158         public short getJumpPassLabel() {
159             checkCounterRange(MIN_PASS_COUNTER, MAX_PASS_COUNTER);
160             return (short) (2 * this.value());
161         }
162 
163         /**
164          * Return the label such that if we jump to it, the counter will be increased by 1 and
165          * the packet will be dropped.
166          */
getJumpDropLabel()167         public short getJumpDropLabel() {
168             checkCounterRange(MIN_DROP_COUNTER, MAX_DROP_COUNTER);
169             return (short) (2 * this.value() + 1);
170         }
171     }
172 
173     public static final Counter MIN_DROP_COUNTER = Counter.DROPPED_ETH_BROADCAST;
174     public static final Counter MAX_DROP_COUNTER = Counter.DROPPED_GARP_REPLY;
175     public static final Counter MIN_PASS_COUNTER = Counter.PASSED_ARP_BROADCAST_REPLY;
176     public static final Counter MAX_PASS_COUNTER = Counter.PASSED_MDNS;
177 
178     private static final String TAG = ApfCounterTracker.class.getSimpleName();
179 
180     private final List<Counter> mCounterList;
181     // Store the counters' value
182     private final Map<Counter, Long> mCounters = new ArrayMap<>();
183 
ApfCounterTracker()184     public ApfCounterTracker() {
185         Counter[] counters = Counter.class.getEnumConstants();
186         mCounterList = Arrays.asList(counters).subList(1, counters.length);
187     }
188 
189     /**
190      * Get the value of a counter from APF data.
191      */
getCounterValue(byte[] data, Counter counter)192     public static long getCounterValue(byte[] data, Counter counter)
193             throws ArrayIndexOutOfBoundsException {
194         int offset = data.length + Counter.ENDIANNESS.offset();
195         int endianness = 0;
196         for (int i = 0; i < 4; i++) {
197             endianness = endianness << 8 | (data[offset + i] & 0xff);
198         }
199         // Follow the same wrap-around addressing scheme of the interpreter.
200         offset = data.length + counter.offset();
201 
202         boolean isBe = true;
203         switch (endianness) {
204             case 0:
205             case 0x12345678:
206                 isBe = true;
207                 break;
208             case 0x78563412:
209                 isBe = false;
210                 break;
211             default:
212                 Log.wtf(TAG, "Unknown endianness: 0x" + Integer.toHexString(endianness));
213         }
214 
215         // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
216         long value = 0;
217         for (int i = 0; i < 4; i++) {
218             value = value << 8 | (data[offset + (isBe ? i : 3 - i)] & 0xff);
219         }
220         return value;
221     }
222 
223     /**
224      * Update counters from APF data.
225      */
updateCountersFromData(byte[] data)226     public void updateCountersFromData(byte[] data) {
227         if (data == null) return;
228         for (Counter counter : mCounterList) {
229             long value;
230             try {
231                 value = getCounterValue(data, counter);
232             } catch (ArrayIndexOutOfBoundsException e) {
233                 value = 0;
234             }
235             long oldValue = mCounters.getOrDefault(counter, 0L);
236             // All counters are incremental
237             if (value > oldValue) {
238                 mCounters.put(counter, value);
239             }
240         }
241     }
242 
243     /**
244      * Get counters map.
245      */
getCounters()246     public Map<Counter, Long> getCounters() {
247         return mCounters;
248     }
249 
250     /**
251      * Clear all counters.
252      */
clearCounters()253     public void clearCounters() {
254         mCounters.clear();
255     }
256 }
257