1 /*
2  * Copyright (C) 2020 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;
18 
19 import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
20 import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
21 import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
22 import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
23 import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
24 import static android.system.OsConstants.IPPROTO_TCP;
25 import static android.system.OsConstants.IPPROTO_UDP;
26 import static android.system.OsConstants.SOCK_DGRAM;
27 import static android.system.OsConstants.SOCK_STREAM;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.os.ParcelFileDescriptor;
32 import android.system.ErrnoException;
33 import android.system.Os;
34 import android.util.Log;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.io.FileDescriptor;
39 import java.net.InetAddress;
40 import java.net.InetSocketAddress;
41 import java.net.Socket;
42 import java.net.SocketAddress;
43 import java.util.Objects;
44 
45 /**
46  * Filters a {@link QosSession} according to the binding on the provided {@link Socket}.
47  *
48  * @hide
49  */
50 public class QosSocketFilter extends QosFilter {
51 
52     private static final String TAG = QosSocketFilter.class.getSimpleName();
53 
54     @NonNull
55     private final QosSocketInfo mQosSocketInfo;
56 
57     /**
58      * Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}.
59      *
60      * @param qosSocketInfo the information required to filter and validate
61      */
QosSocketFilter(@onNull final QosSocketInfo qosSocketInfo)62     public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) {
63         Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null");
64         mQosSocketInfo = qosSocketInfo;
65     }
66 
67     /**
68      * Gets the parcelable qos socket info that was used to create the filter.
69      */
70     @NonNull
getQosSocketInfo()71     public QosSocketInfo getQosSocketInfo() {
72         return mQosSocketInfo;
73     }
74 
75     /**
76      * Performs two validations:
77      * 1. If the socket is not bound, then return
78      *    {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected
79      *    by checking the local address on the filter which becomes null when the socket is no
80      *    longer bound.
81      * 2. In the scenario that the socket is now bound to a different local address, which can
82      *    happen in the case of UDP, then
83      *    {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
84      * 3. In the scenario that the UDP socket changed remote address, then
85      *    {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
86      *
87      * @return validation error code
88      */
89     @Override
validate()90     public int validate() {
91         final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
92 
93         if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
94             return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
95         }
96 
97         if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
98             return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
99         }
100 
101         if (mQosSocketInfo.getRemoteSocketAddress() != null) {
102             final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
103             if (da == null) {
104                 return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
105             }
106 
107             if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
108                 return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
109             }
110         }
111 
112         return EX_TYPE_FILTER_NONE;
113     }
114 
115     /**
116      * The local address of the socket's binding.
117      *
118      * Note: If the socket is no longer bound, null is returned.
119      *
120      * @return the local address
121      */
122     @Nullable
getLocalAddressFromFileDescriptor()123     private InetSocketAddress getLocalAddressFromFileDescriptor() {
124         final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
125         final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
126 
127         final SocketAddress address;
128         try {
129             address = Os.getsockname(fd);
130         } catch (ErrnoException e) {
131             Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
132             return null;
133         }
134         if (address instanceof InetSocketAddress) {
135             return (InetSocketAddress) address;
136         }
137         return null;
138     }
139 
140     /**
141      * The remote address of the socket's connected.
142      *
143      * <p>Note: If the socket is no longer connected, null is returned.
144      *
145      * @return the remote address
146      */
147     @Nullable
getRemoteAddressFromFileDescriptor()148     private InetSocketAddress getRemoteAddressFromFileDescriptor() {
149         final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
150         final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
151 
152         final SocketAddress address;
153         try {
154             address = Os.getpeername(fd);
155         } catch (ErrnoException e) {
156             Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
157             return null;
158         }
159         if (address instanceof InetSocketAddress) {
160             return (InetSocketAddress) address;
161         }
162         return null;
163     }
164 
165     /**
166      * The network used with this filter.
167      *
168      * @return the registered {@link Network}
169      */
170     @NonNull
171     @Override
getNetwork()172     public Network getNetwork() {
173         return mQosSocketInfo.getNetwork();
174     }
175 
176     /**
177      * @inheritDoc
178      */
179     @Override
matchesLocalAddress(@onNull final InetAddress address, final int startPort, final int endPort)180     public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort,
181             final int endPort) {
182         if (mQosSocketInfo.getLocalSocketAddress() == null) {
183             return false;
184         }
185         return matchesAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort,
186                 endPort);
187     }
188 
189     /**
190      * @inheritDoc
191      */
192     @Override
matchesRemoteAddress(@onNull final InetAddress address, final int startPort, final int endPort)193     public boolean matchesRemoteAddress(@NonNull final InetAddress address, final int startPort,
194             final int endPort) {
195         if (mQosSocketInfo.getRemoteSocketAddress() == null) {
196             return false;
197         }
198         return matchesAddress(mQosSocketInfo.getRemoteSocketAddress(), address, startPort,
199                 endPort);
200     }
201 
202     /**
203      * @inheritDoc
204      */
205     @Override
matchesProtocol(final int protocol)206     public boolean matchesProtocol(final int protocol) {
207         if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
208                 || (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
209             return true;
210         }
211         return false;
212     }
213 
214     /**
215      * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
216      * and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
217      * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
218      * <p>
219      * This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked
220      * due to being final.
221      *
222      * @param filterSocketAddress the socket address of the filter
223      * @param address the address to compare the filterSocketAddressWith
224      * @param startPort the start of the port range to check
225      * @param endPort the end of the port range to check
226      */
227     @VisibleForTesting
matchesAddress(@onNull final InetSocketAddress filterSocketAddress, @NonNull final InetAddress address, final int startPort, final int endPort)228     public static boolean matchesAddress(@NonNull final InetSocketAddress filterSocketAddress,
229             @NonNull final InetAddress address,
230             final int startPort, final int endPort) {
231         return startPort <= filterSocketAddress.getPort()
232                 && endPort >= filterSocketAddress.getPort()
233                 && (address.isAnyLocalAddress()
234                         || filterSocketAddress.getAddress().equals(address));
235     }
236 }
237