1 /* 2 * Copyright (C) 2022 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 com.android.server.connectivity.mdns; 18 19 import static com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR; 20 import static com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresApi; 25 import android.os.Build; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.Message; 29 30 import com.android.net.module.util.SharedLog; 31 32 import java.io.IOException; 33 import java.net.InetSocketAddress; 34 35 /** 36 * A class used to send several packets at given time intervals. 37 * @param <T> The type of the request providing packet repeating parameters. 38 */ 39 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 40 public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> { 41 private static final InetSocketAddress[] ALL_ADDRS = new InetSocketAddress[] { 42 IPV4_SOCKET_ADDR, IPV6_SOCKET_ADDR 43 }; 44 45 @NonNull 46 private final MdnsReplySender mReplySender; 47 @NonNull 48 protected final Handler mHandler; 49 @Nullable 50 private final PacketRepeaterCallback<T> mCb; 51 @NonNull 52 private final SharedLog mSharedLog; 53 private final boolean mEnableDebugLog; 54 55 /** 56 * Status callback from {@link MdnsPacketRepeater}. 57 * 58 * Callbacks are called on the {@link MdnsPacketRepeater} handler thread. 59 * @param <T> The type of the request providing packet repeating parameters. 60 */ 61 public interface PacketRepeaterCallback<T extends MdnsPacketRepeater.Request> { 62 /** 63 * Called when a packet was sent. 64 */ onSent(int index, @NonNull T info, int sentPacketCount)65 default void onSent(int index, @NonNull T info, int sentPacketCount) {} 66 67 /** 68 * Called when the {@link MdnsPacketRepeater} is done sending packets. 69 */ onFinished(@onNull T info)70 default void onFinished(@NonNull T info) {} 71 } 72 73 /** 74 * A request to repeat packets. 75 * 76 * All methods are called in the looper thread. 77 */ 78 public interface Request { 79 /** 80 * Get a packet to send for one iteration. 81 */ 82 @NonNull getPacket(int index)83 MdnsPacket getPacket(int index); 84 85 /** 86 * Get the delay in milliseconds until the next packet transmission. 87 */ getDelayMs(int nextIndex)88 long getDelayMs(int nextIndex); 89 90 /** 91 * Get the number of packets that should be sent. 92 */ getNumSends()93 int getNumSends(); 94 } 95 96 private final class ProbeHandler extends Handler { ProbeHandler(@onNull Looper looper)97 ProbeHandler(@NonNull Looper looper) { 98 super(looper); 99 } 100 101 @Override handleMessage(@onNull Message msg)102 public void handleMessage(@NonNull Message msg) { 103 final int index = msg.arg1; 104 final T request = (T) msg.obj; 105 106 if (index >= request.getNumSends()) { 107 if (mCb != null) { 108 mCb.onFinished(request); 109 } 110 return; 111 } 112 113 final MdnsPacket packet = request.getPacket(index); 114 if (mEnableDebugLog) { 115 mSharedLog.v("Sending packets for iteration " + index + " out of " 116 + request.getNumSends() + " for ID " + msg.what); 117 } 118 // Send to both v4 and v6 addresses; the reply sender will take care of ignoring the 119 // send when the socket has not joined the relevant group. 120 int sentPacketCount = 0; 121 for (InetSocketAddress destination : ALL_ADDRS) { 122 try { 123 sentPacketCount += mReplySender.sendNow(packet, destination); 124 } catch (IOException e) { 125 mSharedLog.e("Error sending packet to " + destination, e); 126 } 127 } 128 129 int nextIndex = index + 1; 130 // No need to go through the last handler loop if there's no callback to call 131 if (nextIndex < request.getNumSends() || mCb != null) { 132 // TODO: consider using AlarmManager / WakeupMessage to avoid missing sending during 133 // deep sleep; but this would affect battery life, and discovered services are 134 // likely not to be available since the device is in deep sleep anyway. 135 final long delay = request.getDelayMs(nextIndex); 136 sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay); 137 if (mEnableDebugLog) mSharedLog.v("Scheduled next packet in " + delay + "ms"); 138 } 139 140 // Call onSent after scheduling the next run, to allow the callback to cancel it 141 if (mCb != null) { 142 mCb.onSent(index, request, sentPacketCount); 143 } 144 } 145 } 146 MdnsPacketRepeater(@onNull Looper looper, @NonNull MdnsReplySender replySender, @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog, boolean enableDebugLog)147 protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender, 148 @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog, 149 boolean enableDebugLog) { 150 mHandler = new ProbeHandler(looper); 151 mReplySender = replySender; 152 mCb = cb; 153 mSharedLog = sharedLog; 154 mEnableDebugLog = enableDebugLog; 155 } 156 startSending(int id, @NonNull T request, long initialDelayMs)157 protected void startSending(int id, @NonNull T request, long initialDelayMs) { 158 if (mEnableDebugLog) { 159 mSharedLog.v("Starting send with id " + id + ", request " 160 + request.getClass().getSimpleName() + ", delay " + initialDelayMs); 161 } 162 mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs); 163 } 164 165 /** 166 * Stop sending the packets for the specified ID 167 * @return true if probing was in progress, false if this was a no-op 168 */ stop(int id)169 public boolean stop(int id) { 170 if (mHandler.getLooper().getThread() != Thread.currentThread()) { 171 throw new IllegalStateException("stop can only be called from the looper thread"); 172 } 173 // Since this is run on the looper thread, messages cannot be currently processing and are 174 // all in the handler queue; unless this method is called from a message, but the current 175 // message cannot be cancelled. 176 if (mHandler.hasMessages(id)) { 177 if (mEnableDebugLog) { 178 mSharedLog.v("Stopping send on id " + id); 179 } 180 mHandler.removeMessages(id); 181 return true; 182 } 183 return false; 184 } 185 } 186