diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java index ed6eb15939..b80cff415e 100644 --- a/framework/src/android/net/QosCallbackException.java +++ b/framework/src/android/net/QosCallbackException.java @@ -46,8 +46,10 @@ public final class QosCallbackException extends Exception { EX_TYPE_FILTER_NONE, EX_TYPE_FILTER_NETWORK_RELEASED, EX_TYPE_FILTER_SOCKET_NOT_BOUND, + EX_TYPE_FILTER_SOCKET_NOT_CONNECTED, EX_TYPE_FILTER_NOT_SUPPORTED, EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, + EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED, }) @Retention(RetentionPolicy.SOURCE) public @interface ExceptionType {} @@ -65,10 +67,16 @@ public final class QosCallbackException extends Exception { public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2; /** {@hide} */ - public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3; + public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3; /** {@hide} */ - public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4; + public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6; /** * Creates exception based off of a type and message. Not all types of exceptions accept a @@ -83,12 +91,17 @@ public final class QosCallbackException extends Exception { return new QosCallbackException(new NetworkReleasedException()); case EX_TYPE_FILTER_SOCKET_NOT_BOUND: return new QosCallbackException(new SocketNotBoundException()); + case EX_TYPE_FILTER_SOCKET_NOT_CONNECTED: + return new QosCallbackException(new SocketNotConnectedException()); case EX_TYPE_FILTER_NOT_SUPPORTED: return new QosCallbackException(new UnsupportedOperationException( "This device does not support the specified filter")); case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED: return new QosCallbackException( new SocketLocalAddressChangedException()); + case EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED: + return new QosCallbackException( + new SocketRemoteAddressChangedException()); default: Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'"); return new QosCallbackException( diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java index 5c1c3cc639..b432644f70 100644 --- a/framework/src/android/net/QosFilter.java +++ b/framework/src/android/net/QosFilter.java @@ -90,5 +90,15 @@ public abstract class QosFilter { */ public abstract boolean matchesRemoteAddress(@NonNull InetAddress address, int startPort, int endPort); + + /** + * Determines whether or not the parameter will be matched with this filter. + * + * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS + * flow assigned on {@link Network}. + * @return whether the parameters match the socket type of the filter + * @hide + */ + public abstract boolean matchesProtocol(int protocol); } diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java index da3b2cf8ff..6e71fa3a3e 100644 --- a/framework/src/android/net/QosFilterParcelable.java +++ b/framework/src/android/net/QosFilterParcelable.java @@ -104,7 +104,7 @@ public final class QosFilterParcelable implements Parcelable { if (mQosFilter instanceof QosSocketFilter) { dest.writeInt(QOS_SOCKET_FILTER); final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter; - qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0); + qosSocketFilter.getQosSocketInfo().writeToParcelWithoutFd(dest, 0); return; } dest.writeInt(NO_FILTER_PRESENT); diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java index 69da7f4401..5ceeb67127 100644 --- a/framework/src/android/net/QosSocketFilter.java +++ b/framework/src/android/net/QosSocketFilter.java @@ -18,6 +18,13 @@ 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; @@ -74,19 +81,34 @@ public class QosSocketFilter extends QosFilter { * 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 = getAddressFromFileDescriptor(); - if (sa == null) { - return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND; + 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; } @@ -98,17 +120,14 @@ public class QosSocketFilter extends QosFilter { * @return the local address */ @Nullable - private InetSocketAddress getAddressFromFileDescriptor() { + private InetSocketAddress getLocalAddressFromFileDescriptor() { final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor(); - if (parcelFileDescriptor == null) return null; - final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor(); - if (fd == null) return null; final SocketAddress address; try { address = Os.getsockname(fd); - } catch (final ErrnoException e) { + } catch (ErrnoException e) { Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e); return null; } @@ -118,6 +137,31 @@ public class QosSocketFilter extends QosFilter { return null; } + /** + * The remote address of the socket's connected. + * + *
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. * @@ -155,6 +199,18 @@ public class QosSocketFilter extends QosFilter { 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 @@ -174,6 +230,7 @@ public class QosSocketFilter extends QosFilter { final int startPort, final int endPort) { return startPort <= filterSocketAddress.getPort() && endPort >= filterSocketAddress.getPort() - && filterSocketAddress.getAddress().equals(address); + && (address.isAnyLocalAddress() + || filterSocketAddress.getAddress().equals(address)); } } diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java index 39c2f334d3..49ac22b0a2 100644 --- a/framework/src/android/net/QosSocketInfo.java +++ b/framework/src/android/net/QosSocketInfo.java @@ -165,25 +165,28 @@ public final class QosSocketInfo implements Parcelable { /* Parcelable methods */ private QosSocketInfo(final Parcel in) { mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in)); - mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in); + final boolean withFd = in.readBoolean(); + if (withFd) { + mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in); + } else { + mParcelFileDescriptor = null; + } - final int localAddressLength = in.readInt(); - mLocalSocketAddress = readSocketAddress(in, localAddressLength); - - final int remoteAddressLength = in.readInt(); - mRemoteSocketAddress = remoteAddressLength == 0 ? null - : readSocketAddress(in, remoteAddressLength); + mLocalSocketAddress = readSocketAddress(in); + mRemoteSocketAddress = readSocketAddress(in); mSocketType = in.readInt(); } - private @NonNull InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) { - final byte[] address = new byte[addressLength]; - in.readByteArray(address); + private InetSocketAddress readSocketAddress(final Parcel in) { + final byte[] addrBytes = in.createByteArray(); + if (addrBytes == null) { + return null; + } final int port = in.readInt(); try { - return new InetSocketAddress(InetAddress.getByAddress(address), port); + return new InetSocketAddress(InetAddress.getByAddress(addrBytes), port); } catch (final UnknownHostException e) { /* This can never happen. UnknownHostException will never be thrown since the address provided is numeric and non-null. */ @@ -198,20 +201,35 @@ public final class QosSocketInfo implements Parcelable { @Override public void writeToParcel(@NonNull final Parcel dest, final int flags) { - mNetwork.writeToParcel(dest, 0); - mParcelFileDescriptor.writeToParcel(dest, 0); + writeToParcelInternal(dest, flags, /*includeFd=*/ true); + } - final byte[] localAddress = mLocalSocketAddress.getAddress().getAddress(); - dest.writeInt(localAddress.length); - dest.writeByteArray(localAddress); + /** + * Used when sending QosSocketInfo to telephony, which does not need access to the socket FD. + * @hide + */ + public void writeToParcelWithoutFd(@NonNull final Parcel dest, final int flags) { + writeToParcelInternal(dest, flags, /*includeFd=*/ false); + } + + private void writeToParcelInternal( + @NonNull final Parcel dest, final int flags, boolean includeFd) { + mNetwork.writeToParcel(dest, 0); + + if (includeFd) { + dest.writeBoolean(true); + mParcelFileDescriptor.writeToParcel(dest, 0); + } else { + dest.writeBoolean(false); + } + + dest.writeByteArray(mLocalSocketAddress.getAddress().getAddress()); dest.writeInt(mLocalSocketAddress.getPort()); if (mRemoteSocketAddress == null) { - dest.writeInt(0); + dest.writeByteArray(null); } else { - final byte[] remoteAddress = mRemoteSocketAddress.getAddress().getAddress(); - dest.writeInt(remoteAddress.length); - dest.writeByteArray(remoteAddress); + dest.writeByteArray(mRemoteSocketAddress.getAddress().getAddress()); dest.writeInt(mRemoteSocketAddress.getPort()); } dest.writeInt(mSocketType); diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java new file mode 100644 index 0000000000..fa2a615101 --- /dev/null +++ b/framework/src/android/net/SocketNotConnectedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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; + +/** + * Thrown when a previously bound socket becomes unbound. + * + * @hide + */ +public class SocketNotConnectedException extends Exception { + /** @hide */ + public SocketNotConnectedException() { + super("The socket is not connected"); + } +} diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java new file mode 100644 index 0000000000..ecaeebc208 --- /dev/null +++ b/framework/src/android/net/SocketRemoteAddressChangedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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; + +/** + * Thrown when the local address of the socket has changed. + * + * @hide + */ +public class SocketRemoteAddressChangedException extends Exception { + /** @hide */ + public SocketRemoteAddressChangedException() { + super("The remote address of the socket changed"); + } +}