1 /*
2  * Copyright (C) 2021 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.Network;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.net.module.util.SharedLog;
25 
26 import java.io.IOException;
27 import java.net.DatagramPacket;
28 import java.net.InetSocketAddress;
29 import java.net.MulticastSocket;
30 import java.net.SocketException;
31 import java.util.List;
32 
33 /**
34  * {@link MdnsSocket} provides a similar interface to {@link MulticastSocket} and binds to all
35  * available multi-cast network interfaces.
36  *
37  * @see MulticastSocket for javadoc of each public method.
38  */
39 public class MdnsSocket {
40     static final int INTERFACE_INDEX_UNSPECIFIED = -1;
41     public static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
42             new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
43     public static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
44             new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
45     private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
46     private final MulticastSocket multicastSocket;
47     private boolean isOnIPv6OnlyNetwork;
48     private final SharedLog sharedLog;
49 
MdnsSocket( @onNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port, SharedLog sharedLog)50     public MdnsSocket(
51             @NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port,
52             SharedLog sharedLog)
53             throws IOException {
54         this(multicastNetworkInterfaceProvider, new MulticastSocket(port), sharedLog);
55     }
56 
57     @VisibleForTesting
MdnsSocket(@onNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, MulticastSocket multicastSocket, SharedLog sharedLog)58     MdnsSocket(@NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider,
59             MulticastSocket multicastSocket, SharedLog sharedLog) throws IOException {
60         this.multicastNetworkInterfaceProvider = multicastNetworkInterfaceProvider;
61         this.multicastSocket = multicastSocket;
62         this.sharedLog = sharedLog;
63         // RFC Spec: https://tools.ietf.org/html/rfc6762
64         // Time to live is set 255, which is similar to the jMDNS implementation.
65         multicastSocket.setTimeToLive(255);
66 
67         // TODO (changed when importing code): consider tagging the socket for data usage
68         isOnIPv6OnlyNetwork = false;
69     }
70 
send(DatagramPacket packet)71     public void send(DatagramPacket packet) throws IOException {
72         List<NetworkInterfaceWrapper> networkInterfaces =
73                 multicastNetworkInterfaceProvider.getMulticastNetworkInterfaces();
74         for (NetworkInterfaceWrapper networkInterface : networkInterfaces) {
75             multicastSocket.setNetworkInterface(networkInterface.getNetworkInterface());
76             multicastSocket.send(packet);
77         }
78     }
79 
receive(DatagramPacket packet)80     public void receive(DatagramPacket packet) throws IOException {
81         multicastSocket.receive(packet);
82     }
83 
joinGroup()84     public void joinGroup() throws IOException {
85         List<NetworkInterfaceWrapper> networkInterfaces =
86                 multicastNetworkInterfaceProvider.getMulticastNetworkInterfaces();
87         InetSocketAddress multicastAddress = MULTICAST_IPV4_ADDRESS;
88         if (multicastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(networkInterfaces)) {
89             isOnIPv6OnlyNetwork = true;
90             multicastAddress = MULTICAST_IPV6_ADDRESS;
91         } else {
92             isOnIPv6OnlyNetwork = false;
93         }
94         for (NetworkInterfaceWrapper networkInterface : networkInterfaces) {
95             multicastSocket.joinGroup(multicastAddress, networkInterface.getNetworkInterface());
96             if (!isOnIPv6OnlyNetwork) {
97                 multicastSocket.joinGroup(
98                         MULTICAST_IPV6_ADDRESS, networkInterface.getNetworkInterface());
99             }
100         }
101     }
102 
leaveGroup()103     public void leaveGroup() throws IOException {
104         List<NetworkInterfaceWrapper> networkInterfaces =
105                 multicastNetworkInterfaceProvider.getMulticastNetworkInterfaces();
106         InetSocketAddress multicastAddress = MULTICAST_IPV4_ADDRESS;
107         if (multicastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(networkInterfaces)) {
108             multicastAddress = MULTICAST_IPV6_ADDRESS;
109         }
110         for (NetworkInterfaceWrapper networkInterface : networkInterfaces) {
111             multicastSocket.leaveGroup(multicastAddress, networkInterface.getNetworkInterface());
112             if (!isOnIPv6OnlyNetwork) {
113                 multicastSocket.leaveGroup(
114                         MULTICAST_IPV6_ADDRESS, networkInterface.getNetworkInterface());
115             }
116         }
117     }
118 
close()119     public void close() {
120         // This is a race with the use of the file descriptor (b/27403984).
121         multicastSocket.close();
122     }
123 
124     /**
125      * Returns the index of the network interface that this socket is bound to. If the interface
126      * cannot be determined, returns -1.
127      */
getInterfaceIndex()128     public int getInterfaceIndex() {
129         if (multicastSocket.isClosed()) {
130             sharedLog.e("Socket is closed");
131             return -1;
132         }
133         try {
134             return multicastSocket.getNetworkInterface().getIndex();
135         } catch (SocketException | NullPointerException e) {
136             sharedLog.e("Failed to retrieve interface index for socket.", e);
137             return -1;
138         }
139     }
140 
141     /**
142      * Returns the available network that this socket is used to, or null if the network is unknown.
143      */
144     @Nullable
getNetwork()145     public Network getNetwork() {
146         return multicastNetworkInterfaceProvider.getAvailableNetwork();
147     }
148 
isOnIPv6OnlyNetwork()149     public boolean isOnIPv6OnlyNetwork() {
150         return isOnIPv6OnlyNetwork;
151     }
152 }