• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.MdnsSocket.MULTICAST_IPV4_ADDRESS;
20 import static com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV6_ADDRESS;
21 
22 import android.annotation.NonNull;
23 import android.annotation.RequiresApi;
24 import android.net.LinkAddress;
25 import android.net.util.SocketUtils;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.ParcelFileDescriptor;
30 import android.system.ErrnoException;
31 import android.system.Os;
32 import android.system.OsConstants;
33 
34 import com.android.net.module.util.SharedLog;
35 
36 import java.io.FileDescriptor;
37 import java.io.IOException;
38 import java.net.DatagramPacket;
39 import java.net.InetSocketAddress;
40 import java.net.MulticastSocket;
41 import java.net.NetworkInterface;
42 import java.util.List;
43 
44 /**
45  * {@link MdnsInterfaceSocket} provides a similar interface to {@link MulticastSocket} and binds to
46  * an available multicast network interfaces.
47  *
48  * <p>This isn't thread safe and should be always called on the same thread unless specified
49  * otherwise.
50  *
51  * @see MulticastSocket for javadoc of each public method.
52  * @see MulticastSocket for javadoc of each public method.
53  */
54 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
55 public class MdnsInterfaceSocket {
56     private static final String TAG = MdnsInterfaceSocket.class.getSimpleName();
57     @NonNull private final MulticastSocket mMulticastSocket;
58     @NonNull private final NetworkInterface mNetworkInterface;
59     @NonNull private final MulticastPacketReader mPacketReader;
60     @NonNull private final ParcelFileDescriptor mFileDescriptor;
61     @NonNull private final SharedLog mSharedLog;
62     private boolean mJoinedIpv4 = false;
63     private boolean mJoinedIpv6 = false;
64 
MdnsInterfaceSocket(@onNull NetworkInterface networkInterface, int port, @NonNull Looper looper, @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog)65     public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port,
66             @NonNull Looper looper, @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog)
67             throws IOException {
68         mNetworkInterface = networkInterface;
69         mMulticastSocket = new MulticastSocket(port);
70         // RFC Spec: https://tools.ietf.org/html/rfc6762. Time to live is set 255
71         mMulticastSocket.setTimeToLive(255);
72         mMulticastSocket.setNetworkInterface(networkInterface);
73 
74         // Bind socket to the interface for receiving from that interface only.
75         mFileDescriptor = ParcelFileDescriptor.fromDatagramSocket(mMulticastSocket);
76         try {
77             final FileDescriptor fd = mFileDescriptor.getFileDescriptor();
78             final int flags = Os.fcntlInt(fd, OsConstants.F_GETFL, 0);
79             Os.fcntlInt(fd, OsConstants.F_SETFL, flags | OsConstants.SOCK_NONBLOCK);
80             SocketUtils.bindSocketToInterface(fd, mNetworkInterface.getName());
81         } catch (ErrnoException e) {
82             throw new IOException("Error setting socket options", e);
83         }
84 
85         mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
86                 new Handler(looper), packetReadBuffer);
87         mPacketReader.start();
88 
89         mSharedLog = sharedLog;
90     }
91 
92     /**
93      * Sends a datagram packet from this socket.
94      *
95      * <p>This method could be used on any thread.
96      */
send(@onNull DatagramPacket packet)97     public void send(@NonNull DatagramPacket packet) throws IOException {
98         mMulticastSocket.send(packet);
99     }
100 
hasIpv4Address(@onNull List<LinkAddress> addresses)101     private static boolean hasIpv4Address(@NonNull List<LinkAddress> addresses) {
102         for (LinkAddress address : addresses) {
103             if (address.isIpv4()) return true;
104         }
105         return false;
106     }
107 
hasIpv6Address(@onNull List<LinkAddress> addresses)108     private static boolean hasIpv6Address(@NonNull List<LinkAddress> addresses) {
109         for (LinkAddress address : addresses) {
110             if (address.isIpv6()) return true;
111         }
112         return false;
113     }
114 
115     /*** Joins both IPv4 and IPv6 multicast groups. */
joinGroup(@onNull List<LinkAddress> addresses)116     public void joinGroup(@NonNull List<LinkAddress> addresses) {
117         maybeJoinIpv4(addresses);
118         maybeJoinIpv6(addresses);
119     }
120 
joinGroup(@onNull InetSocketAddress multicastAddress)121     private boolean joinGroup(@NonNull InetSocketAddress multicastAddress) {
122         try {
123             mMulticastSocket.joinGroup(multicastAddress, mNetworkInterface);
124             return true;
125         } catch (IOException e) {
126             // The address may have just been removed
127             mSharedLog.e("Error joining multicast group for " + mNetworkInterface, e);
128             return false;
129         }
130     }
131 
maybeJoinIpv4(@onNull List<LinkAddress> addresses)132     private void maybeJoinIpv4(@NonNull List<LinkAddress> addresses) {
133         final boolean hasAddr = hasIpv4Address(addresses);
134         if (!mJoinedIpv4 && hasAddr) {
135             mJoinedIpv4 = joinGroup(MULTICAST_IPV4_ADDRESS);
136         } else if (!hasAddr) {
137             // Lost IPv4 address
138             mJoinedIpv4 = false;
139         }
140     }
141 
maybeJoinIpv6(@onNull List<LinkAddress> addresses)142     private void maybeJoinIpv6(@NonNull List<LinkAddress> addresses) {
143         final boolean hasAddr = hasIpv6Address(addresses);
144         if (!mJoinedIpv6 && hasAddr) {
145             mJoinedIpv6 = joinGroup(MULTICAST_IPV6_ADDRESS);
146         } else if (!hasAddr) {
147             // Lost IPv6 address
148             mJoinedIpv6 = false;
149         }
150     }
151 
152     /*** Destroy the socket */
destroy()153     public void destroy() {
154         mPacketReader.stop();
155         try {
156             mFileDescriptor.close();
157         } catch (IOException e) {
158             mSharedLog.e("Close file descriptor failed.");
159         }
160         mMulticastSocket.close();
161     }
162 
163     /**
164      * Add a handler to receive callbacks when reads the packet from socket. If the handler is
165      * already set, this is a no-op.
166      */
addPacketHandler(@onNull MulticastPacketReader.PacketHandler handler)167     public void addPacketHandler(@NonNull MulticastPacketReader.PacketHandler handler) {
168         mPacketReader.addPacketHandler(handler);
169     }
170 
171     /**
172      * Remove a handler added via {@link #addPacketHandler}. If the handler is not present, this is
173      * a no-op.
174      */
removePacketHandler(@onNull MulticastPacketReader.PacketHandler handler)175     public void removePacketHandler(@NonNull MulticastPacketReader.PacketHandler handler) {
176         mPacketReader.removePacketHandler(handler);
177     }
178 
179     /**
180      * Returns the network interface that this socket is bound to.
181      *
182      * <p>This method could be used on any thread.
183      */
getInterface()184     public NetworkInterface getInterface() {
185         return mNetworkInterface;
186     }
187 
188     /*** Returns whether this socket has joined IPv4 group */
hasJoinedIpv4()189     public boolean hasJoinedIpv4() {
190         return mJoinedIpv4;
191     }
192 
193     /*** Returns whether this socket has joined IPv6 group */
hasJoinedIpv6()194     public boolean hasJoinedIpv6() {
195         return mJoinedIpv6;
196     }
197 }
198