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