import java.lang.*; import java.io.*; import edu.mit.media.hive.support.Debug; import gnu.regexp.*; /** A worker thread to do server queries **/ public class QueryServer implements Runnable { static final boolean debugging = false; static int debugSequence = 0; static Runtime rt = Runtime.getRuntime(); /** Pool we're a member of. **/ private WorkerPool workerPool; /** Spider we're working for. **/ private NTPSpider ntpSpider; /** Buffer to read output from xntpdc into. It's best if it all fits ** into this one buffer (80k is the biggest I've seen so far) **/ byte [] readBuffer = new byte [131072]; /** Command to run on the host. **/ private String queryCommand[] = { "/usr/sbin/xntpdc", "-n", "-c", "sysinfo", "-c", "peers", "-c", "monlist", "" }; // private String queryCommand[] = { "usleep", "" }; /** The job we're processing. **/ private String host = null; /** Regexp to extract IP addresses from output. **/ RE addressRE; public QueryServer(WorkerPool wp, NTPSpider spider) { workerPool = wp; ntpSpider = spider; // Build up a regexp to match IP addresses from output, as // well as our local stratum. Note: we don't use the // ^ to get beginning, because of multiline bugs in RE 1.0.8. // A dotted QUAD like 18.85.16.104 String dQ = "\\d+\\.\\d+\\.\\d+\\.\\d+"; // A return from the "peers" command of xntpdc, like // =18.85.13.107 18.85.16.104 3 1024 377 <...> String peerRE = "(?:\n[*+=.^~ -]?" + // character at start "(" + dQ + ")" + // address of peers "\\s+" + dQ + "\\s+" + // address of self "(\\d+)" + ")"; // peer stratum // A return from the "monlist" command of xntpdc, like // 18.85.2.171 123 18.85.16.104 <...> String monlistRE = "(?:\n(" + dQ + "))"; // A return from the "sysinfo" command of xntpdc, like // stratum: 4 String stratumRE = "(?:\nstratum:\\s+(\\d+))"; try { // Now we try to match any of these by chaining them. // processOutput() will use the positional parameters to access. addressRE = new RE(peerRE + "|" + monlistRE + "|" + stratumRE); } catch (REException ex) { Debug.error(ex); } } /** Ask the worker to process a specific host. Notify run() to ** start up if blocking. **/ public synchronized void processHost(String newHost) { if (host != null) Debug.warning("Told to work on " + newHost + " was already working on " + host); this.host = newHost; notify(); } /** Main loop - run jobs when given them by processHost(). **/ public synchronized void run() { while (true) { // block until host is set and we have a job while (host == null) { try { wait(); } catch (InterruptedException ex) { } } // Do the job doQuery(); // Record that we've done the job, add ourselves back to pool ntpSpider.noteDone(host); host = null; if (workerPool != null) workerPool.addWorker(this); } } /** App-specific logic - do an NTP server query. **/ public void doQuery() { if (debugging) { // just some testing of the spider logic. try { Thread.sleep(50 + (int)(Math.random() * 300)); } catch (InterruptedException ex) { } addEntry("" + debugSequence++, (int)(Math.random() * 16) + 1); return; } if (host == null) Debug.error("Told to do query, but host is null!"); OutputStream out = ntpSpider.getOutputStream(host); // Exec a process to do the query Process p = null; queryCommand[queryCommand.length-1] = host; try { p = rt.exec(queryCommand); } catch (Exception ex) { Debug.error(p); } // Read the input stream, copy it to the file InputStream in = p.getInputStream(); try { int count = 0, rc; while ((rc = in.read(readBuffer, count, readBuffer.length - count)) != -1) { count += rc; if (count >= readBuffer.length) { Debug.warning("Output too long, truncating."); p.destroy(); break; } } out.write(readBuffer, 0, count); processOutput(readBuffer, count); } catch (IOException ex) { Debug.error(ex); } // Close the output file and quit try { out.close(); } catch (IOException ex) { Debug.error(ex); } } /** Scan through output of xntpdc to get IP addresses for further ** spidering. **/ private void processOutput(byte [] readBuffer, int count) { int ourStratum = 2; // guess a stratum, set below // Do the match ByteArrayInputStream bais = new ByteArrayInputStream(readBuffer, 0, count); REMatchEnumeration e = addressRE.getMatchEnumeration(bais); // iterate over results while (e.hasMoreElements()) { REMatch match = (REMatch)e.nextElement(); if (match.getSubStartIndex(1) > 0) { // peers match int itsStratum = ourStratum; try { itsStratum = Integer.parseInt(match.toString(2)); } catch (NumberFormatException nfe) { Debug.warning("Parse error on " + match.toString(4)); } addEntry(match.toString(1), itsStratum); } else if (match.getSubStartIndex(3) > 0) { // monlist match addEntry(match.toString(3), ourStratum + 1); } else if (match.getSubStartIndex(4) > 0) { // stratum match try { ourStratum = Integer.parseInt(match.toString(4)); } catch (NumberFormatException nfe) { Debug.warning("Parse error on " + match.toString(4)); } } else { Debug.error("RE problem!"); } } } private void addEntry(String addr, int stratum) { ntpSpider.noteHost(addr, stratum); } /** Does not test threading. **/ public static void main(String args[]) { int i; QueryServer qs = new QueryServer(null, null); for (i = 0; i < args.length; i++) { qs.processHost(args[i]); qs.doQuery(); } } }