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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 22 import java.io.EOFException; 23 import java.io.IOException; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.List; 27 28 /** 29 * A class holding data that can be included in a mDNS packet. 30 */ 31 public class MdnsPacket { 32 private static final String TAG = MdnsPacket.class.getSimpleName(); 33 34 public final int transactionId; 35 public final int flags; 36 @NonNull 37 public final List<MdnsRecord> questions; 38 @NonNull 39 public final List<MdnsRecord> answers; 40 @NonNull 41 public final List<MdnsRecord> authorityRecords; 42 @NonNull 43 public final List<MdnsRecord> additionalRecords; 44 MdnsPacket(int flags, @NonNull List<MdnsRecord> questions, @NonNull List<MdnsRecord> answers, @NonNull List<MdnsRecord> authorityRecords, @NonNull List<MdnsRecord> additionalRecords)45 public MdnsPacket(int flags, 46 @NonNull List<MdnsRecord> questions, 47 @NonNull List<MdnsRecord> answers, 48 @NonNull List<MdnsRecord> authorityRecords, 49 @NonNull List<MdnsRecord> additionalRecords) { 50 this(0, flags, questions, answers, authorityRecords, additionalRecords); 51 } 52 MdnsPacket(int transactionId, int flags, @NonNull List<MdnsRecord> questions, @NonNull List<MdnsRecord> answers, @NonNull List<MdnsRecord> authorityRecords, @NonNull List<MdnsRecord> additionalRecords)53 MdnsPacket(int transactionId, int flags, 54 @NonNull List<MdnsRecord> questions, 55 @NonNull List<MdnsRecord> answers, 56 @NonNull List<MdnsRecord> authorityRecords, 57 @NonNull List<MdnsRecord> additionalRecords) { 58 this.transactionId = transactionId; 59 this.flags = flags; 60 this.questions = Collections.unmodifiableList(questions); 61 this.answers = Collections.unmodifiableList(answers); 62 this.authorityRecords = Collections.unmodifiableList(authorityRecords); 63 this.additionalRecords = Collections.unmodifiableList(additionalRecords); 64 } 65 66 /** 67 * Exception thrown on parse errors. 68 */ 69 public static class ParseException extends IOException { 70 public final int code; 71 ParseException(int code, @NonNull String message, @Nullable Throwable cause)72 public ParseException(int code, @NonNull String message, @Nullable Throwable cause) { 73 super(message, cause); 74 this.code = code; 75 } 76 } 77 78 /** 79 * Parse the packet in the provided {@link MdnsPacketReader}. 80 */ 81 @NonNull parse(@onNull MdnsPacketReader reader)82 public static MdnsPacket parse(@NonNull MdnsPacketReader reader) throws ParseException { 83 final int transactionId; 84 final int flags; 85 try { 86 transactionId = reader.readUInt16(); 87 flags = reader.readUInt16(); 88 } catch (EOFException e) { 89 throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE, 90 "Reached the end of the mDNS response unexpectedly.", e); 91 } 92 return parseRecordsSection(reader, flags, transactionId); 93 } 94 95 /** 96 * Parse the records section of a mDNS packet in the provided {@link MdnsPacketReader}. 97 * 98 * The records section starts with the questions count, just after the packet flags. 99 */ parseRecordsSection(@onNull MdnsPacketReader reader, int flags, int transactionId)100 public static MdnsPacket parseRecordsSection(@NonNull MdnsPacketReader reader, int flags, 101 int transactionId) throws ParseException { 102 try { 103 final int numQuestions = reader.readUInt16(); 104 final int numAnswers = reader.readUInt16(); 105 final int numAuthority = reader.readUInt16(); 106 final int numAdditional = reader.readUInt16(); 107 108 final ArrayList<MdnsRecord> questions = parseRecords(reader, numQuestions, true); 109 final ArrayList<MdnsRecord> answers = parseRecords(reader, numAnswers, false); 110 final ArrayList<MdnsRecord> authority = parseRecords(reader, numAuthority, false); 111 final ArrayList<MdnsRecord> additional = parseRecords(reader, numAdditional, false); 112 113 return new MdnsPacket(transactionId, flags, questions, answers, authority, additional); 114 } catch (EOFException e) { 115 throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE, 116 "Reached the end of the mDNS response unexpectedly.", e); 117 } 118 } 119 parseRecords(@onNull MdnsPacketReader reader, int count, boolean isQuestion)120 private static ArrayList<MdnsRecord> parseRecords(@NonNull MdnsPacketReader reader, int count, 121 boolean isQuestion) 122 throws ParseException { 123 final ArrayList<MdnsRecord> records = new ArrayList<>(count); 124 for (int i = 0; i < count; ++i) { 125 final MdnsRecord record = parseRecord(reader, isQuestion); 126 if (record != null) { 127 records.add(record); 128 } 129 } 130 return records; 131 } 132 133 @Nullable parseRecord(@onNull MdnsPacketReader reader, boolean isQuestion)134 private static MdnsRecord parseRecord(@NonNull MdnsPacketReader reader, boolean isQuestion) 135 throws ParseException { 136 String[] name; 137 try { 138 name = reader.readLabels(); 139 } catch (IOException e) { 140 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_RECORD_NAME, 141 "Failed to read labels from mDNS response.", e); 142 } 143 144 final int type; 145 try { 146 type = reader.readUInt16(); 147 } catch (EOFException e) { 148 throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE, 149 "Reached the end of the mDNS response unexpectedly.", e); 150 } 151 152 switch (type) { 153 case MdnsRecord.TYPE_A: { 154 try { 155 return new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader, isQuestion); 156 } catch (IOException e) { 157 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_A_RDATA, 158 "Failed to read A record from mDNS response.", e); 159 } 160 } 161 162 case MdnsRecord.TYPE_AAAA: { 163 try { 164 return new MdnsInetAddressRecord(name, 165 MdnsRecord.TYPE_AAAA, reader, isQuestion); 166 } catch (IOException e) { 167 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_AAAA_RDATA, 168 "Failed to read AAAA record from mDNS response.", e); 169 } 170 } 171 172 case MdnsRecord.TYPE_PTR: { 173 try { 174 return new MdnsPointerRecord(name, reader, isQuestion); 175 } catch (IOException e) { 176 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_PTR_RDATA, 177 "Failed to read PTR record from mDNS response.", e); 178 } 179 } 180 181 case MdnsRecord.TYPE_SRV: { 182 try { 183 return new MdnsServiceRecord(name, reader, isQuestion); 184 } catch (IOException e) { 185 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_SRV_RDATA, 186 "Failed to read SRV record from mDNS response.", e); 187 } 188 } 189 190 case MdnsRecord.TYPE_TXT: { 191 try { 192 return new MdnsTextRecord(name, reader, isQuestion); 193 } catch (IOException e) { 194 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_TXT_RDATA, 195 "Failed to read TXT record from mDNS response.", e); 196 } 197 } 198 199 case MdnsRecord.TYPE_KEY: { 200 try { 201 return new MdnsKeyRecord(name, reader, isQuestion); 202 } catch (IOException e) { 203 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_KEY_RDATA, 204 "Failed to read KEY record from mDNS response.", e); 205 } 206 } 207 208 case MdnsRecord.TYPE_NSEC: { 209 try { 210 return new MdnsNsecRecord(name, reader, isQuestion); 211 } catch (IOException e) { 212 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_NSEC_RDATA, 213 "Failed to read NSEC record from mDNS response.", e); 214 } 215 } 216 217 case MdnsRecord.TYPE_ANY: { 218 try { 219 return new MdnsAnyRecord(name, reader); 220 } catch (IOException e) { 221 throw new ParseException(MdnsResponseErrorCode.ERROR_READING_ANY_RDATA, 222 "Failed to read TYPE_ANY record from mDNS response.", e); 223 } 224 } 225 226 default: { 227 try { 228 skipMdnsRecord(reader, isQuestion); 229 return null; 230 } catch (IOException e) { 231 throw new ParseException(MdnsResponseErrorCode.ERROR_SKIPPING_UNKNOWN_RECORD, 232 "Failed to skip mDNS record.", e); 233 } 234 } 235 } 236 } 237 skipMdnsRecord(@onNull MdnsPacketReader reader, boolean isQuestion)238 private static void skipMdnsRecord(@NonNull MdnsPacketReader reader, boolean isQuestion) 239 throws IOException { 240 reader.skip(2); // Skip the class 241 if (isQuestion) return; 242 // Skip TTL and data 243 reader.skip(4); 244 int dataLength = reader.readUInt16(); 245 reader.skip(dataLength); 246 } 247 } 248