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