Merge "Add MdnsPacketRepeater"

This commit is contained in:
Remi NGUYEN VAN
2022-11-29 07:51:22 +00:00
committed by Gerrit Code Review
5 changed files with 349 additions and 2 deletions

View File

@@ -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 com.android.server.connectivity.mdns;
import android.util.Log;
/**
* MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
*
* TODO: implement
*/
public class MdnsAdvertiser {
private static final String TAG = MdnsAdvertiser.class.getSimpleName();
public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
}

View File

@@ -0,0 +1,43 @@
/*
* 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 com.android.server.connectivity.mdns;
import java.util.Collections;
import java.util.List;
/**
* A class holding data that can be included in a mDNS packet.
*/
public class MdnsPacket {
public final int flags;
public final List<MdnsRecord> questions;
public final List<MdnsRecord> answers;
public final List<MdnsRecord> authorityRecords;
public final List<MdnsRecord> additionalRecords;
MdnsPacket(int flags,
List<MdnsRecord> questions,
List<MdnsRecord> answers,
List<MdnsRecord> authorityRecords,
List<MdnsRecord> additionalRecords) {
this.flags = flags;
this.questions = Collections.unmodifiableList(questions);
this.answers = Collections.unmodifiableList(answers);
this.authorityRecords = Collections.unmodifiableList(authorityRecords);
this.additionalRecords = Collections.unmodifiableList(additionalRecords);
}
}

View File

@@ -0,0 +1,179 @@
/*
* 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 com.android.server.connectivity.mdns;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.net.SocketAddress;
/**
* A class used to send several packets at given time intervals.
* @param <T> The type of the request providing packet repeating parameters.
*/
public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
private static final boolean DBG = MdnsAdvertiser.DBG;
@NonNull
private final MdnsReplySender mReplySender;
@NonNull
protected final Handler mHandler;
@Nullable
private final PacketRepeaterCallback<T> mCb;
/**
* Status callback from {@link MdnsPacketRepeater}.
*
* Callbacks are called on the {@link MdnsPacketRepeater} handler thread.
* @param <T> The type of the request providing packet repeating parameters.
*/
public interface PacketRepeaterCallback<T extends MdnsPacketRepeater.Request> {
/**
* Called when a packet was sent.
*/
default void onSent(int index, @NonNull T info) {}
/**
* Called when the {@link MdnsPacketRepeater} is done sending packets.
*/
default void onFinished(@NonNull T info) {}
}
/**
* A request to repeat packets.
*
* All methods are called in the looper thread.
*/
public interface Request {
/**
* Get a packet to send for one iteration.
*/
@NonNull
MdnsPacket getPacket(int index);
/**
* Get a set of destinations for the packet for one iteration.
*/
@NonNull
Iterable<SocketAddress> getDestinations(int index);
/**
* Get the delay in milliseconds until the next packet transmission.
*/
long getDelayMs(int nextIndex);
/**
* Get the number of packets that should be sent.
*/
int getNumSends();
}
/**
* Get the logging tag to use.
*/
@NonNull
protected abstract String getTag();
private final class ProbeHandler extends Handler {
ProbeHandler(@NonNull Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
final int index = msg.arg1;
final T request = (T) msg.obj;
if (index >= request.getNumSends()) {
if (mCb != null) {
mCb.onFinished(request);
}
return;
}
final MdnsPacket packet = request.getPacket(index);
final Iterable<SocketAddress> destinations = request.getDestinations(index);
if (DBG) {
Log.v(getTag(), "Sending packets to " + destinations + " for iteration "
+ index + " out of " + request.getNumSends());
}
for (SocketAddress destination : destinations) {
try {
mReplySender.sendNow(packet, destination);
} catch (IOException e) {
Log.e(getTag(), "Error sending packet to " + destination, e);
}
}
int nextIndex = index + 1;
// No need to go through the last handler loop if there's no callback to call
if (nextIndex < request.getNumSends() || mCb != null) {
// TODO: consider using AlarmManager / WakeupMessage to avoid missing sending during
// deep sleep; but this would affect battery life, and discovered services are
// likely not to be available since the device is in deep sleep anyway.
final long delay = request.getDelayMs(nextIndex);
sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
if (DBG) Log.v(getTag(), "Scheduled next packet in " + delay + "ms");
}
// Call onSent after scheduling the next run, to allow the callback to cancel it
if (mCb != null) {
mCb.onSent(index, request);
}
}
}
protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
@Nullable PacketRepeaterCallback<T> cb) {
mHandler = new ProbeHandler(looper);
mReplySender = replySender;
mCb = cb;
}
protected void startSending(int id, @NonNull T request, long initialDelayMs) {
if (DBG) {
Log.v(getTag(), "Starting send with id " + id + ", request "
+ request.getClass().getSimpleName() + ", delay " + initialDelayMs);
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs);
}
/**
* Stop sending the packets for the specified ID
* @return true if probing was in progress, false if this was a no-op
*/
public boolean stop(int id) {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException("stop can only be called from the looper thread");
}
// Since this is run on the looper thread, messages cannot be currently processing and are
// all in the handler queue; unless this method is called from a message, but the current
// message cannot be cancelled.
if (mHandler.hasMessages(id)) {
if (DBG) {
Log.v(getTag(), "Stopping send on id " + id);
}
mHandler.removeMessages(id);
return true;
}
return false;
}
}

View File

@@ -29,7 +29,7 @@ import java.util.Map;
public class MdnsPacketWriter {
private static final int MDNS_POINTER_MASK = 0xC000;
private final byte[] data;
private final Map<Integer, String[]> labelDictionary;
private final Map<Integer, String[]> labelDictionary = new HashMap<>();
private int pos = 0;
private int savedWritePos = -1;
@@ -44,7 +44,15 @@ public class MdnsPacketWriter {
}
data = new byte[maxSize];
labelDictionary = new HashMap<>();
}
/**
* Constructs a writer for a new packet.
*
* @param buffer The buffer to write to.
*/
public MdnsPacketWriter(byte[] buffer) {
data = buffer;
}
/** Returns the current write position. */

View File

@@ -0,0 +1,88 @@
/*
* 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 com.android.server.connectivity.mdns;
import android.annotation.NonNull;
import android.os.Looper;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.MulticastSocket;
import java.net.SocketAddress;
/**
* A class that handles sending mDNS replies to a {@link MulticastSocket}, possibly queueing them
* to be sent after some delay.
*
* TODO: implement sending after a delay, combining queued replies and duplicate answer suppression
*/
public class MdnsReplySender {
@NonNull
private final MulticastSocket mSocket;
@NonNull
private final Looper mLooper;
@NonNull
private final byte[] mPacketCreationBuffer;
public MdnsReplySender(@NonNull Looper looper,
@NonNull MulticastSocket socket, @NonNull byte[] packetCreationBuffer) {
mLooper = looper;
mSocket = socket;
mPacketCreationBuffer = packetCreationBuffer;
}
/**
* Send a packet immediately.
*
* Must be called on the looper thread used by the {@link MdnsReplySender}.
*/
public void sendNow(@NonNull MdnsPacket packet, @NonNull SocketAddress destination)
throws IOException {
if (Thread.currentThread() != mLooper.getThread()) {
throw new IllegalStateException("sendNow must be called in the handler thread");
}
// TODO: support packets over size (send in multiple packets with TC bit set)
final MdnsPacketWriter writer = new MdnsPacketWriter(mPacketCreationBuffer);
writer.writeUInt16(0); // Transaction ID (advertisement: 0)
writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
writer.writeUInt16(packet.questions.size()); // questions count
writer.writeUInt16(packet.answers.size()); // answers count
writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
writer.writeUInt16(packet.additionalRecords.size()); // additional records count
for (MdnsRecord record : packet.questions) {
record.write(writer, 0L);
}
for (MdnsRecord record : packet.answers) {
record.write(writer, 0L);
}
for (MdnsRecord record : packet.authorityRecords) {
record.write(writer, 0L);
}
for (MdnsRecord record : packet.additionalRecords) {
record.write(writer, 0L);
}
final int len = writer.getWritePosition();
final byte[] outBuffer = new byte[len];
System.arraycopy(mPacketCreationBuffer, 0, outBuffer, 0, len);
mSocket.send(new DatagramPacket(outBuffer, 0, len, destination));
}
}