/* * @(#) src/net/sf/ivmaidns/dns/DNSConnection.java -- * Class for DNS TCP connection. ** * Copyright (c) 1999-2001 Ivan Maidanski <ivmai@mail.ru> * All rights reserved. */ /* * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. ** * This software is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License (GPL) for more details. ** * Linking this library statically or dynamically with other modules is * making a combined work based on this library. Thus, the terms and * conditions of the GNU General Public License cover the whole * combination. ** * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent * modules, and to copy and distribute the resulting executable under * terms of your choice, provided that you also meet, for each linked * independent module, the terms and conditions of the license of that * module. An independent module is a module which is not derived from * or based on this library. If you modify this library, you may extend * this exception to your version of the library, but you are not * obligated to do so. If you do not wish to do so, delete this * exception statement from your version. */ package net.sf.ivmaidns.dns; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; /** * Class for DNS TCP connection (client/server-side). ** * @version 3.0 * @author Ivan Maidanski */ public final class DNSConnection { /** * NOTE: Standard 'domain' service TCP/UDP port. */ public static final int PORT = 53; /** * NOTE: The maximum length of DNS message. ** * @since 3.0 */ public static final int MAX_MSG_LEN = 0xFFFF; /** * NOTE: If listener == null then no listening is performed. * listener is shared among all instances of this class. ** * @since 3.0 */ protected static ServerSocket listener; /** * NOTE: If socket == null then connection is closed. */ protected Socket socket; protected byte[] msgBytes; protected int msgLen; /** * NOTE: These are input and output streams. ** * @since 3.0 */ protected BufferedInputStream in; protected OutputStream out; /** * NOTE: Buffer for writing message length. ** * @since 3.0 */ protected final byte[] lenBuf = new byte[2]; /** * NOTE: socket is closed initially. ** * @since 2.2 */ public DNSConnection() {} /** * NOTE: Start listening (if not already) on DNS port for incoming * TCP connections. If this port is already busy then BindException * (subclass of SocketException) is thrown. If SecurityException is * caught then SocketException is thrown. ** * @since 3.0 */ public static void listen() throws IOException { ServerSocket curListener; if ((curListener = listener) == null) { try { listener = new ServerSocket(PORT); } catch (SecurityException e) { throw new SocketException("SecurityException: listen()"); } } curListener = null; } /** * NOTE: Stop listening on DNS port for incoming TCP connections. ** * @since 3.0 */ public static void stopListening() { ServerSocket curListener; if ((curListener = listener) != null) { listener = null; try { curListener.close(); } catch (IOException e) {} curListener = null; } } /** * NOTE: old connection should be closed. Wait for any incoming * connection and accept it. If listening is not active or if * SecurityException is caught then SocketException is thrown. If * waiting fails then InterruptedIOException is thrown. Must be * synchronized outside. ** * @since 3.0 */ public void openIncoming() throws IOException { ServerSocket curListener; if ((curListener = listener) != null) { try { Socket socket = curListener.accept(); BufferedInputStream in = new BufferedInputStream(socket.getInputStream(), DNSMsgHeader.UDP_PACKET_LEN); this.out = socket.getOutputStream(); this.in = in; this.msgBytes = null; this.socket = socket; return; } catch (SecurityException e) {} } throw new SocketException(curListener == null ? "Not listening" : "SecurityException: accept()"); } /** * NOTE: old connection should be closed. server must be != null. If * server is down or unreachable then NoRouteToHostException * (subclass of SocketException) is thrown. If connection is * remotely refused then ConnectException (subclass of * SocketException) is thrown. If SecurityException is caught then * SocketException is thrown. Must be synchronized outside. ** * @since 2.2 */ public void open(InetAddress server) throws NullPointerException, IOException { server.hashCode(); try { Socket socket = new Socket(server, PORT); BufferedInputStream in = new BufferedInputStream(socket.getInputStream(), DNSMsgHeader.UDP_PACKET_LEN); this.out = socket.getOutputStream(); this.in = in; this.socket = socket; } catch (SecurityException e) { throw new SocketException("SecurityException: connect(" + server.getHostAddress() + ")"); } this.msgBytes = null; } /** * NOTE: Result != null unless connection is closed. ** * @since 2.0 */ public final InetAddress getInetAddress() { Socket socket; InetAddress address = null; if ((socket = this.socket) != null) address = socket.getInetAddress(); return address; } /** * NOTE: msgBytes must be != null. If msgBytes array is too large * then it is truncated. msgBytes array is not changed anyway. Data * is flushed. Must be synchronized outside. */ public void send(byte[] msgBytes) throws NullPointerException, IOException { int msgLen; if ((msgLen = msgBytes.length) >= MAX_MSG_LEN) msgLen = MAX_MSG_LEN; OutputStream out; if ((out = this.out) == null) throw new SocketException("Connection closed"); byte[] lenBuf = this.lenBuf; lenBuf[0] = (byte)(msgLen >> 8); lenBuf[1] = (byte)msgLen; out.write(lenBuf, 0, 2); out.write(msgBytes, 0, msgLen); out.flush(); } /** * NOTE: If !wait and no message received yet then result == null. * InterruptedIOException and EOFException may be thrown (only if * wait is true). Connection remains valid even if IOException is * thrown. Must be synchronized outside. */ public byte[] receive(boolean wait) throws IOException { byte[] msgBytes; int msgLen, len; BufferedInputStream in; if ((in = this.in) == null) throw new SocketException("Connection closed"); if ((msgLen = this.msgLen) <= 0) msgLen = 0; if ((msgBytes = this.msgBytes) == null) { do { if (!wait && in.available() <= 0) return null; else if ((len = in.read()) < 0) throw new EOFException(); else if (msgLen <= 0) this.msgLen = msgLen = len + 1; else break; } while (true); if ((msgLen = ((msgLen - 1) << 8) | len) <= 0) msgLen = 0; this.msgBytes = msgBytes = new byte[msgLen]; msgLen = 0; } for (int avail; (len = msgBytes.length - (this.msgLen = msgLen)) > 0; msgLen += len) if (!wait && (avail = in.available()) < len && (len = avail) <= 0) return null; else if ((len = in.read(msgBytes, msgLen, len)) < 0) throw new EOFException(); this.msgBytes = null; this.msgLen = 0; return msgBytes; } /** * NOTE: Must be synchronized outside. */ public void close() { Socket socket; if ((socket = this.socket) != null) { this.socket = null; this.in = null; this.out = null; this.msgBytes = null; try { socket.close(); } catch (IOException e) {} socket = null; } } /** * NOTE: header must be != null, records must be != null and * records[index] != null for any index. records length may be not * adequate to header. Names compression is performed (relatively to * the name of the first record). records array is not changed * anyway. Result != null. ** * @since 3.0 */ public static final byte[] encode(DNSMsgHeader header, DNSRecord[] records) throws NullPointerException { int msgLen = DNSMsgHeader.HEADER_LEN, totalCount = records.length; int capacity = DNSMsgHeader.UDP_PACKET_LEN; byte[] msgBytes, newMsgBytes; header.putTo(msgBytes = new byte[capacity]); for (int index = 0, qdCount = header.getQdCount(), recLen; index < totalCount; index++) { DNSRecord rec; if ((recLen = (rec = records[index]).getTotalLen()) > capacity - msgLen) { if ((recLen += msgLen) <= 0) capacity = -1 >>> 1; else if ((capacity += capacity >> 1) <= recLen) capacity = recLen; System.arraycopy(msgBytes, 0, newMsgBytes = new byte[capacity], 0, msgLen); msgBytes = newMsgBytes; } msgLen = rec.putTo(msgBytes, msgLen, index >= qdCount, DNSMsgHeader.HEADER_LEN); } if (capacity > msgLen) { System.arraycopy(msgBytes, 0, newMsgBytes = new byte[msgLen], 0, msgLen); msgBytes = newMsgBytes; } return msgBytes; } /** * NOTE: msgBytes must be != null. If DNS message header is bad then * result == null. Else result != null and result[index] != null for * any index (the result length may be less than that declared in * the decoded header). msgBytes array is not changed anyway. ** * @since 3.0 */ public static final DNSRecord[] decode(byte[] msgBytes) throws NullPointerException { DNSRecord[] records = null; if (msgBytes.length >= DNSMsgHeader.HEADER_LEN) { int totalCount = DNSMsgHeader.getTotalCount(msgBytes), index = 0; int[] ofsRef = new int[1]; ofsRef[0] = DNSMsgHeader.HEADER_LEN; records = new DNSRecord[totalCount]; try { for (int qdCount = DNSMsgHeader.getQdCount(msgBytes); index < totalCount && ofsRef[0] < msgBytes.length; index++) records[index] = new DNSRecord(msgBytes, ofsRef, index >= qdCount); } catch (IllegalArgumentException e) {} if (index < totalCount) { DNSRecord[] newRecords; System.arraycopy(records, 0, newRecords = new DNSRecord[index], 0, index); records = newRecords; } } return records; } }