blob: 5ceeb6712783ce90c7a1e69e000deea6837471e9 [file] [log] [blame]
/*
* 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 android.net;
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_STREAM;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Objects;
/**
* Filters a {@link QosSession} according to the binding on the provided {@link Socket}.
*
* @hide
*/
public class QosSocketFilter extends QosFilter {
private static final String TAG = QosSocketFilter.class.getSimpleName();
@NonNull
private final QosSocketInfo mQosSocketInfo;
/**
* Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}.
*
* @param qosSocketInfo the information required to filter and validate
*/
public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) {
Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null");
mQosSocketInfo = qosSocketInfo;
}
/**
* Gets the parcelable qos socket info that was used to create the filter.
*/
@NonNull
public QosSocketInfo getQosSocketInfo() {
return mQosSocketInfo;
}
/**
* Performs two validations:
* 1. If the socket is not bound, then return
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected
* by checking the local address on the filter which becomes null when the socket is no
* longer bound.
* 2. In the scenario that the socket is now bound to a different local address, which can
* happen in the case of UDP, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
* 3. In the scenario that the UDP socket changed remote address, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
*
* @return validation error code
*/
@Override
public int validate() {
final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
}
if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
}
if (mQosSocketInfo.getRemoteSocketAddress() != null) {
final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
if (da == null) {
return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
}
if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
}
}
return EX_TYPE_FILTER_NONE;
}
/**
* The local address of the socket's binding.
*
* Note: If the socket is no longer bound, null is returned.
*
* @return the local address
*/
@Nullable
private InetSocketAddress getLocalAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
final SocketAddress address;
try {
address = Os.getsockname(fd);
} catch (ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
return null;
}
if (address instanceof InetSocketAddress) {
return (InetSocketAddress) address;
}
return null;
}
/**
* The remote address of the socket's connected.
*
* <p>Note: If the socket is no longer connected, null is returned.
*
* @return the remote address
*/
@Nullable
private InetSocketAddress getRemoteAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
final SocketAddress address;
try {
address = Os.getpeername(fd);
} catch (ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
return null;
}
if (address instanceof InetSocketAddress) {
return (InetSocketAddress) address;
}
return null;
}
/**
* The network used with this filter.
*
* @return the registered {@link Network}
*/
@NonNull
@Override
public Network getNetwork() {
return mQosSocketInfo.getNetwork();
}
/**
* @inheritDoc
*/
@Override
public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort,
final int endPort) {
if (mQosSocketInfo.getLocalSocketAddress() == null) {
return false;
}
return matchesAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort,
endPort);
}
/**
* @inheritDoc
*/
@Override
public boolean matchesRemoteAddress(@NonNull final InetAddress address, final int startPort,
final int endPort) {
if (mQosSocketInfo.getRemoteSocketAddress() == null) {
return false;
}
return matchesAddress(mQosSocketInfo.getRemoteSocketAddress(), address, startPort,
endPort);
}
/**
* @inheritDoc
*/
@Override
public boolean matchesProtocol(final int protocol) {
if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
|| (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
return true;
}
return false;
}
/**
* Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
* and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
* filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
* <p>
* This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked
* due to being final.
*
* @param filterSocketAddress the socket address of the filter
* @param address the address to compare the filterSocketAddressWith
* @param startPort the start of the port range to check
* @param endPort the end of the port range to check
*/
@VisibleForTesting
public static boolean matchesAddress(@NonNull final InetSocketAddress filterSocketAddress,
@NonNull final InetAddress address,
final int startPort, final int endPort) {
return startPort <= filterSocketAddress.getPort()
&& endPort >= filterSocketAddress.getPort()
&& (address.isAnyLocalAddress()
|| filterSocketAddress.getAddress().equals(address));
}
}