| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.text.TextUtils; |
| |
| import com.android.internal.util.BitUtils; |
| |
| import java.nio.BufferUnderflowException; |
| import java.nio.ByteBuffer; |
| import java.text.DecimalFormat; |
| import java.text.FieldPosition; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.StringJoiner; |
| |
| /** |
| * Defines basic data for DNS protocol based on RFC 1035. |
| * Subclasses create the specific format used in DNS packet. |
| * |
| * @hide |
| */ |
| public abstract class DnsPacket { |
| public class DnsHeader { |
| private static final String TAG = "DnsHeader"; |
| public final int id; |
| public final int flags; |
| public final int rcode; |
| private final int[] mSectionCount; |
| |
| /** |
| * Create a new DnsHeader from a positioned ByteBuffer. |
| * |
| * The ByteBuffer must be in network byte order (which is the default). |
| * Reads the passed ByteBuffer from its current position and decodes a DNS header. |
| * When this constructor returns, the reading position of the ByteBuffer has been |
| * advanced to the end of the DNS header record. |
| * This is meant to chain with other methods reading a DNS response in sequence. |
| * |
| */ |
| DnsHeader(@NonNull ByteBuffer buf) throws BufferUnderflowException { |
| id = BitUtils.uint16(buf.getShort()); |
| flags = BitUtils.uint16(buf.getShort()); |
| rcode = flags & 0xF; |
| mSectionCount = new int[NUM_SECTIONS]; |
| for (int i = 0; i < NUM_SECTIONS; ++i) { |
| mSectionCount[i] = BitUtils.uint16(buf.getShort()); |
| } |
| } |
| |
| /** |
| * Get section count by section type. |
| */ |
| public int getSectionCount(int sectionType) { |
| return mSectionCount[sectionType]; |
| } |
| } |
| |
| public class DnsSection { |
| private static final int MAXNAMESIZE = 255; |
| private static final int MAXLABELSIZE = 63; |
| private static final int MAXLABELCOUNT = 128; |
| private static final int NAME_NORMAL = 0; |
| private static final int NAME_COMPRESSION = 0xC0; |
| private final DecimalFormat byteFormat = new DecimalFormat(); |
| private final FieldPosition pos = new FieldPosition(0); |
| |
| private static final String TAG = "DnsSection"; |
| |
| public final String dName; |
| public final int nsType; |
| public final int nsClass; |
| public final long ttl; |
| private final byte[] mRR; |
| |
| /** |
| * Create a new DnsSection from a positioned ByteBuffer. |
| * |
| * The ByteBuffer must be in network byte order (which is the default). |
| * Reads the passed ByteBuffer from its current position and decodes a DNS section. |
| * When this constructor returns, the reading position of the ByteBuffer has been |
| * advanced to the end of the DNS header record. |
| * This is meant to chain with other methods reading a DNS response in sequence. |
| * |
| */ |
| DnsSection(int sectionType, @NonNull ByteBuffer buf) |
| throws BufferUnderflowException, ParseException { |
| dName = parseName(buf, 0 /* Parse depth */); |
| if (dName.length() > MAXNAMESIZE) { |
| throw new ParseException("Parse name fail, name size is too long"); |
| } |
| nsType = BitUtils.uint16(buf.getShort()); |
| nsClass = BitUtils.uint16(buf.getShort()); |
| |
| if (sectionType != QDSECTION) { |
| ttl = BitUtils.uint32(buf.getInt()); |
| final int length = BitUtils.uint16(buf.getShort()); |
| mRR = new byte[length]; |
| buf.get(mRR); |
| } else { |
| ttl = 0; |
| mRR = null; |
| } |
| } |
| |
| /** |
| * Get a copy of rr. |
| */ |
| @Nullable public byte[] getRR() { |
| return (mRR == null) ? null : mRR.clone(); |
| } |
| |
| /** |
| * Convert label from {@code byte[]} to {@code String} |
| * |
| * It follows the same converting rule as native layer. |
| * (See ns_name.c in libc) |
| * |
| */ |
| private String labelToString(@NonNull byte[] label) { |
| final StringBuffer sb = new StringBuffer(); |
| for (int i = 0; i < label.length; ++i) { |
| int b = BitUtils.uint8(label[i]); |
| // Control characters and non-ASCII characters. |
| if (b <= 0x20 || b >= 0x7f) { |
| sb.append('\\'); |
| byteFormat.format(b, sb, pos); |
| } else if (b == '"' || b == '.' || b == ';' || b == '\\' |
| || b == '(' || b == ')' || b == '@' || b == '$') { |
| sb.append('\\'); |
| sb.append((char) b); |
| } else { |
| sb.append((char) b); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private String parseName(@NonNull ByteBuffer buf, int depth) throws |
| BufferUnderflowException, ParseException { |
| if (depth > MAXLABELCOUNT) throw new ParseException("Parse name fails, too many labels"); |
| final int len = BitUtils.uint8(buf.get()); |
| final int mask = len & NAME_COMPRESSION; |
| if (0 == len) { |
| return ""; |
| } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) { |
| throw new ParseException("Parse name fail, bad label type"); |
| } else if (mask == NAME_COMPRESSION) { |
| // Name compression based on RFC 1035 - 4.1.4 Message compression |
| final int offset = ((len & ~NAME_COMPRESSION) << 8) + BitUtils.uint8(buf.get()); |
| final int oldPos = buf.position(); |
| if (offset >= oldPos - 2) { |
| throw new ParseException("Parse compression name fail, invalid compression"); |
| } |
| buf.position(offset); |
| final String pointed = parseName(buf, depth + 1); |
| buf.position(oldPos); |
| return pointed; |
| } else { |
| final byte[] label = new byte[len]; |
| buf.get(label); |
| final String head = labelToString(label); |
| if (head.length() > MAXLABELSIZE) { |
| throw new ParseException("Parse name fail, invalid label length"); |
| } |
| final String tail = parseName(buf, depth + 1); |
| return TextUtils.isEmpty(tail) ? head : head + "." + tail; |
| } |
| } |
| } |
| |
| public static final int QDSECTION = 0; |
| public static final int ANSECTION = 1; |
| public static final int NSSECTION = 2; |
| public static final int ARSECTION = 3; |
| private static final int NUM_SECTIONS = ARSECTION + 1; |
| |
| private static final String TAG = DnsPacket.class.getSimpleName(); |
| |
| protected final DnsHeader mHeader; |
| protected final List<DnsSection>[] mSections; |
| |
| public static class ParseException extends Exception { |
| public ParseException(String msg) { |
| super(msg); |
| } |
| |
| public ParseException(String msg, Throwable cause) { |
| super(msg, cause); |
| } |
| } |
| |
| protected DnsPacket(@NonNull byte[] data) throws ParseException { |
| if (null == data) throw new ParseException("Parse header failed, null input data"); |
| final ByteBuffer buffer; |
| try { |
| buffer = ByteBuffer.wrap(data); |
| mHeader = new DnsHeader(buffer); |
| } catch (BufferUnderflowException e) { |
| throw new ParseException("Parse Header fail, bad input data", e); |
| } |
| |
| mSections = new ArrayList[NUM_SECTIONS]; |
| |
| for (int i = 0; i < NUM_SECTIONS; ++i) { |
| final int count = mHeader.getSectionCount(i); |
| if (count > 0) { |
| mSections[i] = new ArrayList(count); |
| } |
| for (int j = 0; j < count; ++j) { |
| try { |
| mSections[i].add(new DnsSection(i, buffer)); |
| } catch (BufferUnderflowException e) { |
| throw new ParseException("Parse section fail", e); |
| } |
| } |
| } |
| } |
| } |