/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.net.sdp;

import sun.net.NetHooks;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.*;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintStream;

import sun.net.sdp.SdpSupport;
import sun.security.action.GetPropertyAction;

A NetHooks provider that converts sockets from the TCP to SDP protocol prior to binding or connecting.
/** * A NetHooks provider that converts sockets from the TCP to SDP protocol prior * to binding or connecting. */
public class SdpProvider extends NetHooks.Provider { // maximum port private static final int MAX_PORT = 65535; // indicates if SDP is enabled and the rules for when the protocol is used private final boolean enabled; private final List<Rule> rules; // logging for debug purposes private PrintStream log; public SdpProvider() { Properties props = GetPropertyAction.privilegedGetProperties(); // if this property is not defined then there is nothing to do. String file = props.getProperty("com.sun.sdp.conf"); if (file == null) { this.enabled = false; this.rules = null; return; } // load configuration file List<Rule> list = null; try { list = loadRulesFromFile(file); } catch (IOException e) { fail("Error reading %s: %s", file, e.getMessage()); } // check if debugging is enabled PrintStream out = null; String logfile = props.getProperty("com.sun.sdp.debug"); if (logfile != null) { out = System.out; if (logfile.length() > 0) { try { out = new PrintStream(logfile); } catch (IOException ignore) { } } } this.enabled = !list.isEmpty(); this.rules = list; this.log = out; } // supported actions private static enum Action { BIND, CONNECT; } // a rule for matching a bind or connect request private static interface Rule { boolean match(Action action, InetAddress address, int port); } // rule to match port[-end] private static class PortRangeRule implements Rule { private final Action action; private final int portStart; private final int portEnd; PortRangeRule(Action action, int portStart, int portEnd) { this.action = action; this.portStart = portStart; this.portEnd = portEnd; } Action action() { return action; } @Override public boolean match(Action action, InetAddress address, int port) { return (action == this.action && port >= this.portStart && port <= this.portEnd); } } // rule to match address[/prefix] port[-end] private static class AddressPortRangeRule extends PortRangeRule { private final byte[] addressAsBytes; private final int prefixByteCount; private final byte mask; AddressPortRangeRule(Action action, InetAddress address, int prefix, int port, int end) { super(action, port, end); this.addressAsBytes = address.getAddress(); this.prefixByteCount = prefix >> 3; this.mask = (byte)(0xff << (8 - (prefix % 8))); } @Override public boolean match(Action action, InetAddress address, int port) { if (action != action()) return false; byte[] candidate = address.getAddress(); // same address type? if (candidate.length != addressAsBytes.length) return false; // check bytes for (int i=0; i<prefixByteCount; i++) { if (candidate[i] != addressAsBytes[i]) return false; } // check remaining bits if ((prefixByteCount < addressAsBytes.length) && ((candidate[prefixByteCount] & mask) != (addressAsBytes[prefixByteCount] & mask))) return false; return super.match(action, address, port); } } // parses port:[-end] private static int[] parsePortRange(String s) { int pos = s.indexOf('-'); try { int[] result = new int[2]; if (pos < 0) { boolean all = s.equals("*"); result[0] = all ? 0 : Integer.parseInt(s); result[1] = all ? MAX_PORT : result[0]; } else { String low = s.substring(0, pos); if (low.length() == 0) low = "*"; String high = s.substring(pos+1); if (high.length() == 0) high = "*"; result[0] = low.equals("*") ? 0 : Integer.parseInt(low); result[1] = high.equals("*") ? MAX_PORT : Integer.parseInt(high); } return result; } catch (NumberFormatException e) { return new int[0]; } } private static void fail(String msg, Object... args) { Formatter f = new Formatter(); f.format(msg, args); throw new RuntimeException(f.out().toString()); } // loads rules from the given file // Each non-blank/non-comment line must have the format: // ("bind" | "connect") 1*LWSP-char (hostname | ipaddress["/" prefix]) // 1*LWSP-char ("*" | port) [ "-" ("*" | port) ] private static List<Rule> loadRulesFromFile(String file) throws IOException { Scanner scanner = new Scanner(new File(file)); try { List<Rule> result = new ArrayList<>(); while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); // skip blank lines and comments if (line.length() == 0 || line.charAt(0) == '#') continue; // must have 3 fields String[] s = line.split("\\s+"); if (s.length != 3) { fail("Malformed line '%s'", line); continue; } // first field is the action ("bind" or "connect") Action action = null; for (Action a: Action.values()) { if (s[0].equalsIgnoreCase(a.name())) { action = a; break; } } if (action == null) { fail("Action '%s' not recognized", s[0]); continue; } // * port[-end] int[] ports = parsePortRange(s[2]); if (ports.length == 0) { fail("Malformed port range '%s'", s[2]); continue; } // match all addresses if (s[1].equals("*")) { result.add(new PortRangeRule(action, ports[0], ports[1])); continue; } // hostname | ipaddress[/prefix] int pos = s[1].indexOf('/'); try { if (pos < 0) { // hostname or ipaddress (no prefix) InetAddress[] addresses = InetAddress.getAllByName(s[1]); for (InetAddress address: addresses) { int prefix = (address instanceof Inet4Address) ? 32 : 128; result.add(new AddressPortRangeRule(action, address, prefix, ports[0], ports[1])); } } else { // ipaddress/prefix InetAddress address = InetAddress .getByName(s[1].substring(0, pos)); int prefix = -1; try { prefix = Integer.parseInt(s[1], pos + 1, s[1].length(), 10); if (address instanceof Inet4Address) { // must be 1-31 if (prefix < 0 || prefix > 32) prefix = -1; } else { // must be 1-128 if (prefix < 0 || prefix > 128) prefix = -1; } } catch (NumberFormatException e) { } if (prefix > 0) { result.add(new AddressPortRangeRule(action, address, prefix, ports[0], ports[1])); } else { fail("Malformed prefix '%s'", s[1]); continue; } } } catch (UnknownHostException uhe) { fail("Unknown host or malformed IP address '%s'", s[1]); continue; } } return result; } finally { scanner.close(); } } // converts unbound TCP socket to a SDP socket if it matches the rules private void convertTcpToSdpIfMatch(FileDescriptor fdObj, Action action, InetAddress address, int port) throws IOException { boolean matched = false; for (Rule rule: rules) { if (rule.match(action, address, port)) { SdpSupport.convertSocket(fdObj); matched = true; break; } } if (log != null) { String addr = (address instanceof Inet4Address) ? address.getHostAddress() : "[" + address.getHostAddress() + "]"; if (matched) { log.format("%s to %s:%d (socket converted to SDP protocol)\n", action, addr, port); } else { log.format("%s to %s:%d (no match)\n", action, addr, port); } } } @Override public void implBeforeTcpBind(FileDescriptor fdObj, InetAddress address, int port) throws IOException { if (enabled) convertTcpToSdpIfMatch(fdObj, Action.BIND, address, port); } @Override public void implBeforeTcpConnect(FileDescriptor fdObj, InetAddress address, int port) throws IOException { if (enabled) convertTcpToSdpIfMatch(fdObj, Action.CONNECT, address, port); } }