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