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