1 /*
2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.jndi.dns;
27 
28 import java.io.IOException;
29 import java.net.DatagramSocket;
30 import java.net.DatagramPacket;
31 import java.net.InetAddress;
32 import java.net.InetSocketAddress;
33 import java.net.Socket;
34 import java.net.SocketTimeoutException;
35 import java.security.SecureRandom;
36 import javax.naming.*;
37 
38 import java.util.Collections;
39 import java.util.Map;
40 import java.util.HashMap;
41 
42 import sun.security.jca.JCAUtil;
43 
44 // Some of this code began life as part of sun.javaos.net.DnsClient
45 // originally by sritchie@eng 1/96.  It was first hacked up for JNDI
46 // use by caveh@eng 6/97.
47 
48 
49 /**
50  * The DnsClient class performs DNS client operations in support of DnsContext.
51  *
52  */
53 
54 public class DnsClient {
55 
56     // DNS packet header field offsets
57     private static final int IDENT_OFFSET = 0;
58     private static final int FLAGS_OFFSET = 2;
59     private static final int NUMQ_OFFSET  = 4;
60     private static final int NUMANS_OFFSET = 6;
61     private static final int NUMAUTH_OFFSET = 8;
62     private static final int NUMADD_OFFSET = 10;
63     private static final int DNS_HDR_SIZE = 12;
64 
65     // DNS response codes
66     private static final int NO_ERROR       = 0;
67     private static final int FORMAT_ERROR   = 1;
68     private static final int SERVER_FAILURE = 2;
69     private static final int NAME_ERROR     = 3;
70     private static final int NOT_IMPL       = 4;
71     private static final int REFUSED        = 5;
72 
73     private static final String[] rcodeDescription = {
74         "No error",
75         "DNS format error",
76         "DNS server failure",
77         "DNS name not found",
78         "DNS operation not supported",
79         "DNS service refused"
80     };
81 
82     private static final int DEFAULT_PORT = 53;
83     private static final int TRANSACTION_ID_BOUND = 0x10000;
84     private static final SecureRandom random = JCAUtil.getSecureRandom();
85     private InetAddress[] servers;
86     private int[] serverPorts;
87     private int timeout;                // initial timeout on UDP and TCP queries in ms
88     private int retries;                // number of UDP retries
89 
90     private final Object udpSocketLock = new Object();
91     private static final DNSDatagramSocketFactory factory =
92             new DNSDatagramSocketFactory(random);
93 
94     // Requests sent
95     private Map<Integer, ResourceRecord> reqs;
96 
97     // Responses received
98     private Map<Integer, byte[]> resps;
99 
100     //-------------------------------------------------------------------------
101 
102     /*
103      * Each server is of the form "server[:port]".  IPv6 literal host names
104      * include delimiting brackets.
105      * "timeout" is the initial timeout interval (in ms) for queries,
106      * and "retries" gives the number of retries per server.
107      */
DnsClient(String[] servers, int timeout, int retries)108     public DnsClient(String[] servers, int timeout, int retries)
109             throws NamingException {
110         this.timeout = timeout;
111         this.retries = retries;
112         this.servers = new InetAddress[servers.length];
113         serverPorts = new int[servers.length];
114 
115         for (int i = 0; i < servers.length; i++) {
116 
117             // Is optional port given?
118             int colon = servers[i].indexOf(':',
119                                            servers[i].indexOf(']') + 1);
120 
121             serverPorts[i] = (colon < 0)
122                 ? DEFAULT_PORT
123                 : Integer.parseInt(servers[i].substring(colon + 1));
124             String server = (colon < 0)
125                 ? servers[i]
126                 : servers[i].substring(0, colon);
127             try {
128                 this.servers[i] = InetAddress.getByName(server);
129             } catch (java.net.UnknownHostException e) {
130                 NamingException ne = new ConfigurationException(
131                         "Unknown DNS server: " + server);
132                 ne.setRootCause(e);
133                 throw ne;
134             }
135         }
136         reqs = Collections.synchronizedMap(
137             new HashMap<Integer, ResourceRecord>());
138         resps = Collections.synchronizedMap(new HashMap<Integer, byte[]>());
139     }
140 
getDatagramSocket()141     DatagramSocket getDatagramSocket() throws NamingException {
142         try {
143             return factory.open();
144         } catch (java.net.SocketException e) {
145             NamingException ne = new ConfigurationException();
146             ne.setRootCause(e);
147             throw ne;
148         }
149     }
150 
151     @SuppressWarnings("deprecation")
finalize()152     protected void finalize() {
153         close();
154     }
155 
156     // A lock to access the request and response queues in tandem.
157     private Object queuesLock = new Object();
158 
close()159     public void close() {
160         synchronized (queuesLock) {
161             reqs.clear();
162             resps.clear();
163         }
164     }
165 
166     /*
167      * If recursion is true, recursion is requested on the query.
168      * If auth is true, only authoritative responses are accepted; other
169      * responses throw NameNotFoundException.
170      */
query(DnsName fqdn, int qclass, int qtype, boolean recursion, boolean auth)171     ResourceRecords query(DnsName fqdn, int qclass, int qtype,
172                           boolean recursion, boolean auth)
173             throws NamingException {
174 
175         int xid;
176         Packet pkt;
177         ResourceRecord collision;
178 
179         do {
180             // Generate a random transaction ID
181             xid = random.nextInt(TRANSACTION_ID_BOUND);
182             pkt = makeQueryPacket(fqdn, xid, qclass, qtype, recursion);
183 
184             // enqueue the outstanding request
185             collision = reqs.putIfAbsent(xid, new ResourceRecord(pkt.getData(),
186                 pkt.length(), Header.HEADER_SIZE, true, false));
187 
188         } while (collision != null);
189 
190         Exception caughtException = null;
191         boolean[] doNotRetry = new boolean[servers.length];
192 
193         try {
194             //
195             // The UDP retry strategy is to try the 1st server, and then
196             // each server in order. If no answer, double the timeout
197             // and try each server again.
198             //
199             for (int retry = 0; retry < retries; retry++) {
200 
201                 // Try each name server.
202                 for (int i = 0; i < servers.length; i++) {
203                     if (doNotRetry[i]) {
204                         continue;
205                     }
206 
207                     // send the request packet and wait for a response.
208                     try {
209                         if (debug) {
210                             dprint("SEND ID (" + (retry + 1) + "): " + xid);
211                         }
212 
213                         byte[] msg = null;
214                         msg = doUdpQuery(pkt, servers[i], serverPorts[i],
215                                             retry, xid);
216                         //
217                         // If the matching response is not got within the
218                         // given timeout, check if the response was enqueued
219                         // by some other thread, if not proceed with the next
220                         // server or retry.
221                         //
222                         if (msg == null) {
223                             if (resps.size() > 0) {
224                                 msg = lookupResponse(xid);
225                             }
226                             if (msg == null) { // try next server or retry
227                                 continue;
228                             }
229                         }
230                         Header hdr = new Header(msg, msg.length);
231 
232                         if (auth && !hdr.authoritative) {
233                             caughtException = new NameNotFoundException(
234                                     "DNS response not authoritative");
235                             doNotRetry[i] = true;
236                             continue;
237                         }
238                         if (hdr.truncated) {  // message is truncated -- try TCP
239 
240                             // Try each server, starting with the one that just
241                             // provided the truncated message.
242                             int retryTimeout = (timeout * (1 << retry));
243                             for (int j = 0; j < servers.length; j++) {
244                                 int ij = (i + j) % servers.length;
245                                 if (doNotRetry[ij]) {
246                                     continue;
247                                 }
248                                 try {
249                                     Tcp tcp =
250                                         new Tcp(servers[ij], serverPorts[ij], retryTimeout);
251                                     byte[] msg2;
252                                     try {
253                                         msg2 = doTcpQuery(tcp, pkt);
254                                     } finally {
255                                         tcp.close();
256                                     }
257                                     Header hdr2 = new Header(msg2, msg2.length);
258                                     if (hdr2.query) {
259                                         throw new CommunicationException(
260                                             "DNS error: expecting response");
261                                     }
262                                     checkResponseCode(hdr2);
263 
264                                     if (!auth || hdr2.authoritative) {
265                                         // Got a valid response
266                                         hdr = hdr2;
267                                         msg = msg2;
268                                         break;
269                                     } else {
270                                         doNotRetry[ij] = true;
271                                     }
272                                 } catch (Exception e) {
273                                     // Try next server, or use UDP response
274                                 }
275                             } // servers
276                         }
277                         return new ResourceRecords(msg, msg.length, hdr, false);
278 
279                     } catch (IOException e) {
280                         if (debug) {
281                             dprint("Caught IOException:" + e);
282                         }
283                         if (caughtException == null) {
284                             caughtException = e;
285                         }
286                         // Use reflection to allow pre-1.4 compilation.
287                         // This won't be needed much longer.
288                         if (e.getClass().getName().equals(
289                                 "java.net.PortUnreachableException")) {
290                             doNotRetry[i] = true;
291                         }
292                     } catch (NameNotFoundException e) {
293                         // This is authoritative, so return immediately
294                         throw e;
295                     } catch (CommunicationException e) {
296                         if (caughtException == null) {
297                             caughtException = e;
298                         }
299                     } catch (NamingException e) {
300                         if (caughtException == null) {
301                             caughtException = e;
302                         }
303                         doNotRetry[i] = true;
304                     }
305                 } // servers
306             } // retries
307 
308         } finally {
309             reqs.remove(xid); // cleanup
310         }
311 
312         if (caughtException instanceof NamingException) {
313             throw (NamingException) caughtException;
314         }
315         // A network timeout or other error occurred.
316         NamingException ne = new CommunicationException("DNS error");
317         ne.setRootCause(caughtException);
318         throw ne;
319     }
320 
queryZone(DnsName zone, int qclass, boolean recursion)321     ResourceRecords queryZone(DnsName zone, int qclass, boolean recursion)
322             throws NamingException {
323 
324         int xid = random.nextInt(TRANSACTION_ID_BOUND);
325 
326         Packet pkt = makeQueryPacket(zone, xid, qclass,
327                                      ResourceRecord.QTYPE_AXFR, recursion);
328         Exception caughtException = null;
329 
330         // Try each name server.
331         for (int i = 0; i < servers.length; i++) {
332             try {
333                 Tcp tcp = new Tcp(servers[i], serverPorts[i], timeout);
334                 byte[] msg;
335                 try {
336                     msg = doTcpQuery(tcp, pkt);
337                     Header hdr = new Header(msg, msg.length);
338                     // Check only rcode as per
339                     // draft-ietf-dnsext-axfr-clarify-04
340                     checkResponseCode(hdr);
341                     ResourceRecords rrs =
342                         new ResourceRecords(msg, msg.length, hdr, true);
343                     if (rrs.getFirstAnsType() != ResourceRecord.TYPE_SOA) {
344                         throw new CommunicationException(
345                                 "DNS error: zone xfer doesn't begin with SOA");
346                     }
347 
348                     if (rrs.answer.size() == 1 ||
349                             rrs.getLastAnsType() != ResourceRecord.TYPE_SOA) {
350                         // The response is split into multiple DNS messages.
351                         do {
352                             msg = continueTcpQuery(tcp);
353                             if (msg == null) {
354                                 throw new CommunicationException(
355                                         "DNS error: incomplete zone transfer");
356                             }
357                             hdr = new Header(msg, msg.length);
358                             checkResponseCode(hdr);
359                             rrs.add(msg, msg.length, hdr);
360                         } while (rrs.getLastAnsType() !=
361                                  ResourceRecord.TYPE_SOA);
362                     }
363 
364                     // Delete the duplicate SOA record.
365                     rrs.answer.removeElementAt(rrs.answer.size() - 1);
366                     return rrs;
367 
368                 } finally {
369                     tcp.close();
370                 }
371 
372             } catch (IOException e) {
373                 caughtException = e;
374             } catch (NameNotFoundException e) {
375                 throw e;
376             } catch (NamingException e) {
377                 caughtException = e;
378             }
379         }
380         if (caughtException instanceof NamingException) {
381             throw (NamingException) caughtException;
382         }
383         NamingException ne = new CommunicationException(
384                 "DNS error during zone transfer");
385         ne.setRootCause(caughtException);
386         throw ne;
387     }
388 
389 
390     /**
391      * Tries to retrieve a UDP packet matching the given xid
392      * received within the timeout.
393      * If a packet with different xid is received, the received packet
394      * is enqueued with the corresponding xid in 'resps'.
395      */
doUdpQuery(Packet pkt, InetAddress server, int port, int retry, int xid)396     private byte[] doUdpQuery(Packet pkt, InetAddress server,
397                                      int port, int retry, int xid)
398             throws IOException, NamingException {
399 
400         int minTimeout = 50; // msec after which there are no retries.
401 
402         synchronized (udpSocketLock) {
403             try (DatagramSocket udpSocket = getDatagramSocket()) {
404                 DatagramPacket opkt = new DatagramPacket(
405                         pkt.getData(), pkt.length(), server, port);
406                 DatagramPacket ipkt = new DatagramPacket(new byte[8000], 8000);
407                 // Packets may only be sent to or received from this server address
408                 udpSocket.connect(server, port);
409                 int pktTimeout = (timeout * (1 << retry));
410                 try {
411                     udpSocket.send(opkt);
412 
413                     // timeout remaining after successive 'receive()'
414                     int timeoutLeft = pktTimeout;
415                     int cnt = 0;
416                     do {
417                         if (debug) {
418                            cnt++;
419                             dprint("Trying RECEIVE(" +
420                                     cnt + ") retry(" + (retry + 1) +
421                                     ") for:" + xid  + "    sock-timeout:" +
422                                     timeoutLeft + " ms.");
423                         }
424                         udpSocket.setSoTimeout(timeoutLeft);
425                         long start = System.currentTimeMillis();
426                         udpSocket.receive(ipkt);
427                         long end = System.currentTimeMillis();
428 
429                         byte[] data = ipkt.getData();
430                         if (isMatchResponse(data, xid)) {
431                             return data;
432                         }
433                         timeoutLeft = pktTimeout - ((int) (end - start));
434                     } while (timeoutLeft > minTimeout);
435 
436                 } finally {
437                     udpSocket.disconnect();
438                 }
439                 return null; // no matching packet received within the timeout
440             }
441         }
442     }
443 
444     /*
445      * Sends a TCP query, and returns the first DNS message in the response.
446      */
doTcpQuery(Tcp tcp, Packet pkt)447     private byte[] doTcpQuery(Tcp tcp, Packet pkt) throws IOException {
448 
449         int len = pkt.length();
450         // Send 2-byte message length, then send message.
451         tcp.out.write(len >> 8);
452         tcp.out.write(len);
453         tcp.out.write(pkt.getData(), 0, len);
454         tcp.out.flush();
455 
456         byte[] msg = continueTcpQuery(tcp);
457         if (msg == null) {
458             throw new IOException("DNS error: no response");
459         }
460         return msg;
461     }
462 
463     /*
464      * Returns the next DNS message from the TCP socket, or null on EOF.
465      */
continueTcpQuery(Tcp tcp)466     private byte[] continueTcpQuery(Tcp tcp) throws IOException {
467 
468         int lenHi = tcp.read();      // high-order byte of response length
469         if (lenHi == -1) {
470             return null;        // EOF
471         }
472         int lenLo = tcp.read();      // low-order byte of response length
473         if (lenLo == -1) {
474             throw new IOException("Corrupted DNS response: bad length");
475         }
476         int len = (lenHi << 8) | lenLo;
477         byte[] msg = new byte[len];
478         int pos = 0;                    // next unfilled position in msg
479         while (len > 0) {
480             int n = tcp.read(msg, pos, len);
481             if (n == -1) {
482                 throw new IOException(
483                         "Corrupted DNS response: too little data");
484             }
485             len -= n;
486             pos += n;
487         }
488         return msg;
489     }
490 
makeQueryPacket(DnsName fqdn, int xid, int qclass, int qtype, boolean recursion)491     private Packet makeQueryPacket(DnsName fqdn, int xid,
492                                    int qclass, int qtype, boolean recursion) {
493         int qnameLen = fqdn.getOctets();
494         int pktLen = DNS_HDR_SIZE + qnameLen + 4;
495         Packet pkt = new Packet(pktLen);
496 
497         short flags = recursion ? Header.RD_BIT : 0;
498 
499         pkt.putShort(xid, IDENT_OFFSET);
500         pkt.putShort(flags, FLAGS_OFFSET);
501         pkt.putShort(1, NUMQ_OFFSET);
502         pkt.putShort(0, NUMANS_OFFSET);
503         pkt.putInt(0, NUMAUTH_OFFSET);
504 
505         makeQueryName(fqdn, pkt, DNS_HDR_SIZE);
506         pkt.putShort(qtype, DNS_HDR_SIZE + qnameLen);
507         pkt.putShort(qclass, DNS_HDR_SIZE + qnameLen + 2);
508 
509         return pkt;
510     }
511 
512     // Builds a query name in pkt according to the RFC spec.
makeQueryName(DnsName fqdn, Packet pkt, int off)513     private void makeQueryName(DnsName fqdn, Packet pkt, int off) {
514 
515         // Loop through labels, least-significant first.
516         for (int i = fqdn.size() - 1; i >= 0; i--) {
517             String label = fqdn.get(i);
518             int len = label.length();
519 
520             pkt.putByte(len, off++);
521             for (int j = 0; j < len; j++) {
522                 pkt.putByte(label.charAt(j), off++);
523             }
524         }
525         if (!fqdn.hasRootLabel()) {
526             pkt.putByte(0, off);
527         }
528     }
529 
530     //-------------------------------------------------------------------------
531 
lookupResponse(Integer xid)532     private byte[] lookupResponse(Integer xid) throws NamingException {
533         //
534         // Check the queued responses: some other thread in between
535         // received the response for this request.
536         //
537         if (debug) {
538             dprint("LOOKUP for: " + xid +
539                 "\tResponse Q:" + resps);
540         }
541         byte[] pkt;
542         if ((pkt = resps.get(xid)) != null) {
543             checkResponseCode(new Header(pkt, pkt.length));
544             synchronized (queuesLock) {
545                 resps.remove(xid);
546                 reqs.remove(xid);
547             }
548 
549             if (debug) {
550                 dprint("FOUND (" + Thread.currentThread() +
551                     ") for:" + xid);
552             }
553         }
554         return pkt;
555     }
556 
557     /*
558      * Checks the header of an incoming DNS response.
559      * Returns true if it matches the given xid and throws a naming
560      * exception, if appropriate, based on the response code.
561      *
562      * Also checks that the domain name, type and class in the response
563      * match those in the original query.
564      */
isMatchResponse(byte[] pkt, int xid)565     private boolean isMatchResponse(byte[] pkt, int xid)
566                 throws NamingException {
567 
568         Header hdr = new Header(pkt, pkt.length);
569         if (hdr.query) {
570             throw new CommunicationException("DNS error: expecting response");
571         }
572 
573         if (!reqs.containsKey(xid)) { // already received, ignore the response
574             return false;
575         }
576 
577         // common case- the request sent matches the subsequent response read
578         if (hdr.xid == xid) {
579             if (debug) {
580                 dprint("XID MATCH:" + xid);
581             }
582             checkResponseCode(hdr);
583             if (!hdr.query && hdr.numQuestions == 1) {
584 
585                 ResourceRecord rr = new ResourceRecord(pkt, pkt.length,
586                     Header.HEADER_SIZE, true, false);
587 
588                 // Retrieve the original query
589                 ResourceRecord query = reqs.get(xid);
590                 int qtype = query.getType();
591                 int qclass = query.getRrclass();
592                 DnsName qname = query.getName();
593 
594                 // Check that the type/class/name in the query section of the
595                 // response match those in the original query
596                 if ((qtype == ResourceRecord.QTYPE_STAR ||
597                     qtype == rr.getType()) &&
598                     (qclass == ResourceRecord.QCLASS_STAR ||
599                     qclass == rr.getRrclass()) &&
600                     qname.equals(rr.getName())) {
601 
602                     if (debug) {
603                         dprint("MATCH NAME:" + qname + " QTYPE:" + qtype +
604                             " QCLASS:" + qclass);
605                     }
606 
607                     // Remove the response for the xid if received by some other
608                     // thread.
609                     synchronized (queuesLock) {
610                         resps.remove(xid);
611                         reqs.remove(xid);
612                     }
613                     return true;
614 
615                 } else {
616                     if (debug) {
617                         dprint("NO-MATCH NAME:" + qname + " QTYPE:" + qtype +
618                             " QCLASS:" + qclass);
619                     }
620                 }
621             }
622             return false;
623         }
624 
625         //
626         // xid mis-match: enqueue the response, it may belong to some other
627         // thread that has not yet had a chance to read its response.
628         // enqueue only the first response, responses for retries are ignored.
629         //
630         synchronized (queuesLock) {
631             if (reqs.containsKey(hdr.xid)) { // enqueue only the first response
632                 resps.put(hdr.xid, pkt);
633             }
634         }
635 
636         if (debug) {
637             dprint("NO-MATCH SEND ID:" +
638                                 xid + " RECVD ID:" + hdr.xid +
639                                 "    Response Q:" + resps +
640                                 "    Reqs size:" + reqs.size());
641         }
642         return false;
643     }
644 
645     /*
646      * Throws an exception if appropriate for the response code of a
647      * given header.
648      */
checkResponseCode(Header hdr)649     private void checkResponseCode(Header hdr) throws NamingException {
650 
651         int rcode = hdr.rcode;
652         if (rcode == NO_ERROR) {
653             return;
654         }
655         String msg = (rcode < rcodeDescription.length)
656             ? rcodeDescription[rcode]
657             : "DNS error";
658         msg += " [response code " + rcode + "]";
659 
660         switch (rcode) {
661         case SERVER_FAILURE:
662             throw new ServiceUnavailableException(msg);
663         case NAME_ERROR:
664             throw new NameNotFoundException(msg);
665         case NOT_IMPL:
666         case REFUSED:
667             throw new OperationNotSupportedException(msg);
668         case FORMAT_ERROR:
669         default:
670             throw new NamingException(msg);
671         }
672     }
673 
674     //-------------------------------------------------------------------------
675 
676     private static final boolean debug = false;
677 
dprint(String mess)678     private static void dprint(String mess) {
679         if (debug) {
680             System.err.println("DNS: " + mess);
681         }
682     }
683 
684 }
685 
686 class Tcp {
687 
688     private final Socket sock;
689     private final java.io.InputStream in;
690     final java.io.OutputStream out;
691     private int timeoutLeft;
692 
Tcp(InetAddress server, int port, int timeout)693     Tcp(InetAddress server, int port, int timeout) throws IOException {
694         sock = new Socket();
695         try {
696             long start = System.currentTimeMillis();
697             sock.connect(new InetSocketAddress(server, port), timeout);
698             timeoutLeft = (int) (timeout - (System.currentTimeMillis() - start));
699             if (timeoutLeft <= 0)
700                 throw new SocketTimeoutException();
701 
702             sock.setTcpNoDelay(true);
703             out = new java.io.BufferedOutputStream(sock.getOutputStream());
704             in = new java.io.BufferedInputStream(sock.getInputStream());
705         } catch (Exception e) {
706             try {
707                 sock.close();
708             } catch (IOException ex) {
709                 e.addSuppressed(ex);
710             }
711             throw e;
712         }
713     }
714 
close()715     void close() throws IOException {
716         sock.close();
717     }
718 
719     private interface SocketReadOp {
read()720         int read() throws IOException;
721     }
722 
readWithTimeout(SocketReadOp reader)723     private int readWithTimeout(SocketReadOp reader) throws IOException {
724         if (timeoutLeft <= 0)
725             throw new SocketTimeoutException();
726 
727         sock.setSoTimeout(timeoutLeft);
728         long start = System.currentTimeMillis();
729         try {
730             return reader.read();
731         }
732         finally {
733             timeoutLeft -= System.currentTimeMillis() - start;
734         }
735     }
736 
read()737     int read() throws IOException {
738         return readWithTimeout(() -> in.read());
739     }
740 
read(byte b[], int off, int len)741     int read(byte b[], int off, int len) throws IOException {
742         return readWithTimeout(() -> in.read(b, off, len));
743     }
744 }
745 
746 /*
747  * javaos emulation -cj
748  */
749 class Packet {
750         byte buf[];
751 
Packet(int len)752         Packet(int len) {
753                 buf = new byte[len];
754         }
755 
Packet(byte data[], int len)756         Packet(byte data[], int len) {
757                 buf = new byte[len];
758                 System.arraycopy(data, 0, buf, 0, len);
759         }
760 
putInt(int x, int off)761         void putInt(int x, int off) {
762                 buf[off + 0] = (byte)(x >> 24);
763                 buf[off + 1] = (byte)(x >> 16);
764                 buf[off + 2] = (byte)(x >> 8);
765                 buf[off + 3] = (byte)x;
766         }
767 
putShort(int x, int off)768         void putShort(int x, int off) {
769                 buf[off + 0] = (byte)(x >> 8);
770                 buf[off + 1] = (byte)x;
771         }
772 
putByte(int x, int off)773         void putByte(int x, int off) {
774                 buf[off] = (byte)x;
775         }
776 
putBytes(byte src[], int src_offset, int dst_offset, int len)777         void putBytes(byte src[], int src_offset, int dst_offset, int len) {
778                 System.arraycopy(src, src_offset, buf, dst_offset, len);
779         }
780 
length()781         int length() {
782                 return buf.length;
783         }
784 
getData()785         byte[] getData() {
786                 return buf;
787         }
788 }
789