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