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; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.text.TextUtils; 25 import android.util.ArraySet; 26 import android.util.Log; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.net.Inet6Address; 31 import java.net.UnknownHostException; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.Objects; 35 import java.util.Set; 36 import java.util.StringJoiner; 37 38 /** 39 * A class representing a configuration for multicast routing. 40 * 41 * Internal usage to Connectivity 42 * @hide 43 */ 44 // @SystemApi(client = MODULE_LIBRARIES) 45 public final class MulticastRoutingConfig implements Parcelable { 46 private static final String TAG = MulticastRoutingConfig.class.getSimpleName(); 47 48 /** Do not forward any multicast packets. */ 49 public static final int FORWARD_NONE = 0; 50 /** 51 * Forward only multicast packets with destination in the list of listening addresses. 52 * Ignore the min scope. 53 */ 54 public static final int FORWARD_SELECTED = 1; 55 /** 56 * Forward all multicast packets with scope greater or equal than the min scope. 57 * Ignore the list of listening addresses. 58 */ 59 public static final int FORWARD_WITH_MIN_SCOPE = 2; 60 61 /** @hide */ 62 @Retention(RetentionPolicy.SOURCE) 63 @IntDef(prefix = { "FORWARD_" }, value = { 64 FORWARD_NONE, 65 FORWARD_SELECTED, 66 FORWARD_WITH_MIN_SCOPE 67 }) 68 public @interface MulticastForwardingMode {} 69 70 /** 71 * Not a multicast scope, for configurations that do not use the min scope. 72 */ 73 public static final int MULTICAST_SCOPE_NONE = -1; 74 75 /** @hide */ 76 public static final MulticastRoutingConfig CONFIG_FORWARD_NONE = 77 new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null); 78 79 @MulticastForwardingMode 80 private final int mForwardingMode; 81 82 private final int mMinScope; 83 84 @NonNull 85 private final Set<Inet6Address> mListeningAddresses; 86 MulticastRoutingConfig(@ulticastForwardingMode final int mode, final int scope, @Nullable final Set<Inet6Address> addresses)87 private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope, 88 @Nullable final Set<Inet6Address> addresses) { 89 mForwardingMode = mode; 90 mMinScope = scope; 91 if (null != addresses) { 92 mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses)); 93 } else { 94 mListeningAddresses = Collections.emptySet(); 95 } 96 } 97 98 /** 99 * Returns the forwarding mode. 100 */ 101 @MulticastForwardingMode getForwardingMode()102 public int getForwardingMode() { 103 return mForwardingMode; 104 } 105 106 /** 107 * Returns the minimal group address scope that is allowed for forwarding. 108 * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE. 109 */ getMinimumScope()110 public int getMinimumScope() { 111 return mMinScope; 112 } 113 114 /** 115 * Returns the list of group addresses listened by the outgoing interface. 116 * The list will be empty if the forwarding mode is not FORWARD_SELECTED. 117 */ 118 @NonNull getListeningAddresses()119 public Set<Inet6Address> getListeningAddresses() { 120 return mListeningAddresses; 121 } 122 MulticastRoutingConfig(Parcel in)123 private MulticastRoutingConfig(Parcel in) { 124 mForwardingMode = in.readInt(); 125 mMinScope = in.readInt(); 126 final int count = in.readInt(); 127 final ArraySet<Inet6Address> listeningAddresses = new ArraySet<>(count); 128 final byte[] buffer = new byte[16]; // Size of an Inet6Address 129 for (int i = 0; i < count; ++i) { 130 in.readByteArray(buffer); 131 try { 132 listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer)); 133 } catch (UnknownHostException e) { 134 Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer)); 135 } 136 } 137 mListeningAddresses = Collections.unmodifiableSet(listeningAddresses); 138 } 139 140 @Override writeToParcel(@onNull Parcel dest, int flags)141 public void writeToParcel(@NonNull Parcel dest, int flags) { 142 dest.writeInt(mForwardingMode); 143 dest.writeInt(mMinScope); 144 dest.writeInt(mListeningAddresses.size()); 145 for (final Inet6Address addr : mListeningAddresses) { 146 dest.writeByteArray(addr.getAddress()); 147 } 148 } 149 150 @Override describeContents()151 public int describeContents() { 152 return 0; 153 } 154 155 @NonNull 156 public static final Creator<MulticastRoutingConfig> CREATOR = new Creator<>() { 157 @Override 158 public MulticastRoutingConfig createFromParcel(@NonNull Parcel in) { 159 return new MulticastRoutingConfig(in); 160 } 161 162 @Override 163 public MulticastRoutingConfig[] newArray(int size) { 164 return new MulticastRoutingConfig[size]; 165 } 166 }; 167 forwardingModeToString(final int forwardingMode)168 private static String forwardingModeToString(final int forwardingMode) { 169 switch (forwardingMode) { 170 case FORWARD_NONE: return "NONE"; 171 case FORWARD_SELECTED: return "SELECTED"; 172 case FORWARD_WITH_MIN_SCOPE: return "WITH_MIN_SCOPE"; 173 default: return "UNKNOWN"; 174 } 175 } 176 177 public static final class Builder { 178 @MulticastForwardingMode 179 private final int mForwardingMode; 180 private int mMinScope; 181 private final ArraySet<Inet6Address> mListeningAddresses; 182 183 // The two constructors with runtime checks for the mode and scope are arguably 184 // less convenient than three static factory methods, but API guidelines mandates 185 // that Builders are built with a constructor and not factory methods. 186 /** 187 * Create a new builder for forwarding mode FORWARD_NONE or FORWARD_SELECTED. 188 * 189 * <p>On a Builder for FORWARD_NONE, no properties can be set. 190 * <p>On a Builder for FORWARD_SELECTED, listening addresses can be added and removed 191 * but the minimum scope can't be set. 192 * 193 * @param mode {@link #FORWARD_NONE} or {@link #FORWARD_SELECTED}. Any other 194 * value will result in IllegalArgumentException. 195 * @see #Builder(int, int) 196 */ Builder(@ulticastForwardingMode final int mode)197 public Builder(@MulticastForwardingMode final int mode) { 198 if (FORWARD_NONE != mode && FORWARD_SELECTED != mode) { 199 if (FORWARD_WITH_MIN_SCOPE == mode) { 200 throw new IllegalArgumentException("FORWARD_WITH_MIN_SCOPE requires " 201 + "passing the scope as a second argument"); 202 } else { 203 throw new IllegalArgumentException("Unknown forwarding mode : " + mode); 204 } 205 } 206 mForwardingMode = mode; 207 mMinScope = MULTICAST_SCOPE_NONE; 208 mListeningAddresses = new ArraySet<>(); 209 } 210 211 /** 212 * Create a new builder for forwarding mode FORWARD_WITH_MIN_SCOPE. 213 * 214 * <p>On this Builder the scope can be set with {@link #setMinimumScope}, but 215 * listening addresses can't be added or removed. 216 * 217 * @param mode Must be {@link #FORWARD_WITH_MIN_SCOPE}. 218 * @param scope the minimum scope for this multicast routing config. 219 * @see Builder#Builder(int) 220 */ Builder(@ulticastForwardingMode final int mode, int scope)221 public Builder(@MulticastForwardingMode final int mode, int scope) { 222 if (FORWARD_WITH_MIN_SCOPE != mode) { 223 throw new IllegalArgumentException("Forwarding with a min scope must " 224 + "use forward mode FORWARD_WITH_MIN_SCOPE"); 225 } 226 mForwardingMode = mode; 227 mMinScope = scope; 228 mListeningAddresses = new ArraySet<>(); 229 } 230 231 /** 232 * Sets the minimum scope for this multicast routing config. 233 * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode. 234 * @return this builder 235 */ 236 @NonNull setMinimumScope(final int scope)237 public Builder setMinimumScope(final int scope) { 238 if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) { 239 throw new IllegalArgumentException("Can't set the scope on a builder in mode " 240 + modeToString(mForwardingMode)); 241 } 242 mMinScope = scope; 243 return this; 244 } 245 246 /** 247 * Add an address to the set of listening addresses. 248 * 249 * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. 250 * If this address was already added, this is a no-op. 251 * @return this builder 252 */ 253 @NonNull addListeningAddress(@onNull final Inet6Address address)254 public Builder addListeningAddress(@NonNull final Inet6Address address) { 255 if (FORWARD_SELECTED != mForwardingMode) { 256 throw new IllegalArgumentException("Can't add an address on a builder in mode " 257 + modeToString(mForwardingMode)); 258 } 259 // TODO : should we check that this is a multicast address ? 260 mListeningAddresses.add(address); 261 return this; 262 } 263 264 /** 265 * Remove an address from the set of listening addresses. 266 * 267 * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. 268 * If this address was not added, or was already removed, this is a no-op. 269 * @return this builder 270 */ 271 @NonNull clearListeningAddress(@onNull final Inet6Address address)272 public Builder clearListeningAddress(@NonNull final Inet6Address address) { 273 if (FORWARD_SELECTED != mForwardingMode) { 274 throw new IllegalArgumentException("Can't remove an address on a builder in mode " 275 + modeToString(mForwardingMode)); 276 } 277 mListeningAddresses.remove(address); 278 return this; 279 } 280 281 /** 282 * Build the config. 283 */ 284 @NonNull build()285 public MulticastRoutingConfig build() { 286 return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses); 287 } 288 } 289 modeToString(@ulticastForwardingMode final int mode)290 private static String modeToString(@MulticastForwardingMode final int mode) { 291 switch (mode) { 292 case FORWARD_NONE: return "FORWARD_NONE"; 293 case FORWARD_SELECTED: return "FORWARD_SELECTED"; 294 case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE"; 295 default: return "unknown multicast routing mode " + mode; 296 } 297 } 298 equals(Object other)299 public boolean equals(Object other) { 300 if (other == this) { 301 return true; 302 } else if (!(other instanceof MulticastRoutingConfig)) { 303 return false; 304 } else { 305 final MulticastRoutingConfig otherConfig = (MulticastRoutingConfig) other; 306 return mForwardingMode == otherConfig.mForwardingMode 307 && mMinScope == otherConfig.mMinScope 308 && mListeningAddresses.equals(otherConfig.mListeningAddresses); 309 } 310 } 311 hashCode()312 public int hashCode() { 313 return Objects.hash(mForwardingMode, mMinScope, mListeningAddresses); 314 } 315 toString()316 public String toString() { 317 final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}"); 318 319 resultJoiner.add("ForwardingMode:"); 320 resultJoiner.add(modeToString(mForwardingMode)); 321 322 if (mForwardingMode == FORWARD_WITH_MIN_SCOPE) { 323 resultJoiner.add("MinScope:"); 324 resultJoiner.add(Integer.toString(mMinScope)); 325 } 326 327 if (mForwardingMode == FORWARD_SELECTED && !mListeningAddresses.isEmpty()) { 328 resultJoiner.add("ListeningAddresses: ["); 329 resultJoiner.add(TextUtils.join(",", mListeningAddresses)); 330 resultJoiner.add("]"); 331 } 332 333 return resultJoiner.toString(); 334 } 335 } 336