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