1 /* 2 * Copyright (c) 2009, 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 sun.net.sdp; 27 28 import sun.net.NetHooks; 29 import java.net.InetAddress; 30 import java.net.Inet4Address; 31 import java.net.UnknownHostException; 32 import java.util.*; 33 import java.io.File; 34 import java.io.FileDescriptor; 35 import java.io.IOException; 36 import java.io.PrintStream; 37 import java.security.AccessController; 38 39 import sun.net.sdp.SdpSupport; 40 import sun.security.action.GetPropertyAction; 41 42 /** 43 * A NetHooks provider that converts sockets from the TCP to SDP protocol prior 44 * to binding or connecting. 45 */ 46 47 public class SdpProvider extends NetHooks.Provider { 48 // maximum port 49 private static final int MAX_PORT = 65535; 50 51 // indicates if SDP is enabled and the rules for when the protocol is used 52 private final boolean enabled; 53 private final List<Rule> rules; 54 55 // logging for debug purposes 56 private PrintStream log; 57 58 public SdpProvider() { 59 // if this property is not defined then there is nothing to do. 60 String file = AccessController.doPrivileged( 61 new GetPropertyAction("com.sun.sdp.conf")); 62 if (file == null) { 63 this.enabled = false; 64 this.rules = null; 65 return; 66 } 67 68 // load configuration file 69 List<Rule> list = null; 70 if (file != null) { 71 try { 72 list = loadRulesFromFile(file); 73 } catch (IOException e) { 74 fail("Error reading %s: %s", file, e.getMessage()); 75 } 76 } 77 78 // check if debugging is enabled 79 PrintStream out = null; 80 String logfile = AccessController.doPrivileged( 81 new GetPropertyAction("com.sun.sdp.debug")); 82 if (logfile != null) { 83 out = System.out; 84 if (logfile.length() > 0) { 85 try { 86 out = new PrintStream(logfile); 87 } catch (IOException ignore) { } 88 } 89 } 90 91 this.enabled = !list.isEmpty(); 92 this.rules = list; 93 this.log = out; 94 } 95 96 // supported actions 97 private static enum Action { 98 BIND, 99 CONNECT; 100 } 101 102 // a rule for matching a bind or connect request 103 private static interface Rule { 104 boolean match(Action action, InetAddress address, int port); 105 } 106 107 // rule to match port[-end] 108 private static class PortRangeRule implements Rule { 109 private final Action action; 110 private final int portStart; 111 private final int portEnd; 112 PortRangeRule(Action action, int portStart, int portEnd) { 113 this.action = action; 114 this.portStart = portStart; 115 this.portEnd = portEnd; 116 } 117 Action action() { 118 return action; 119 } 120 @Override 121 public boolean match(Action action, InetAddress address, int port) { 122 return (action == this.action && 123 port >= this.portStart && 124 port <= this.portEnd); 125 } 126 } 127 128 // rule to match address[/prefix] port[-end] 129 private static class AddressPortRangeRule extends PortRangeRule { 130 private final byte[] addressAsBytes; 131 private final int prefixByteCount; 132 private final byte mask; 133 AddressPortRangeRule(Action action, InetAddress address, 134 int prefix, int port, int end) 135 { 136 super(action, port, end); 137 this.addressAsBytes = address.getAddress(); 138 this.prefixByteCount = prefix >> 3; 139 this.mask = (byte)(0xff << (8 - (prefix % 8))); 140 } 141 @Override 142 public boolean match(Action action, InetAddress address, int port) { 143 if (action != action()) 144 return false; 145 byte[] candidate = address.getAddress(); 146 // same address type? 147 if (candidate.length != addressAsBytes.length) 148 return false; 149 // check bytes 150 for (int i=0; i<prefixByteCount; i++) { 151 if (candidate[i] != addressAsBytes[i]) 152 return false; 153 } 154 // check remaining bits 155 if ((prefixByteCount < addressAsBytes.length) && 156 ((candidate[prefixByteCount] & mask) != 157 (addressAsBytes[prefixByteCount] & mask))) 158 return false; 159 return super.match(action, address, port); 160 } 161 } 162 163 // parses port:[-end] 164 private static int[] parsePortRange(String s) { 165 int pos = s.indexOf('-'); 166 try { 167 int[] result = new int[2]; 168 if (pos < 0) { 169 boolean all = s.equals("*"); 170 result[0] = all ? 0 : Integer.parseInt(s); 171 result[1] = all ? MAX_PORT : result[0]; 172 } else { 173 String low = s.substring(0, pos); 174 if (low.length() == 0) low = "*"; 175 String high = s.substring(pos+1); 176 if (high.length() == 0) high = "*"; 177 result[0] = low.equals("*") ? 0 : Integer.parseInt(low); 178 result[1] = high.equals("*") ? MAX_PORT : Integer.parseInt(high); 179 } 180 return result; 181 } catch (NumberFormatException e) { 182 return new int[0]; 183 } 184 } 185 186 private static void fail(String msg, Object... args) { 187 Formatter f = new Formatter(); 188 f.format(msg, args); 189 throw new RuntimeException(f.out().toString()); 190 } 191 192 // loads rules from the given file 193 // Each non-blank/non-comment line must have the format: 194 // ("bind" | "connect") 1*LWSP-char (hostname | ipaddress["/" prefix]) 195 // 1*LWSP-char ("*" | port) [ "-" ("*" | port) ] 196 private static List<Rule> loadRulesFromFile(String file) 197 throws IOException 198 { 199 Scanner scanner = new Scanner(new File(file)); 200 try { 201 List<Rule> result = new ArrayList<Rule>(); 202 while (scanner.hasNextLine()) { 203 String line = scanner.nextLine().trim(); 204 205 // skip blank lines and comments 206 if (line.length() == 0 || line.charAt(0) == '#') 207 continue; 208 209 // must have 3 fields 210 String[] s = line.split("\\s+"); 211 if (s.length != 3) { 212 fail("Malformed line '%s'", line); 213 continue; 214 } 215 216 // first field is the action ("bind" or "connect") 217 Action action = null; 218 for (Action a: Action.values()) { 219 if (s[0].equalsIgnoreCase(a.name())) { 220 action = a; 221 break; 222 } 223 } 224 if (action == null) { 225 fail("Action '%s' not recognized", s[0]); 226 continue; 227 } 228 229 // * port[-end] 230 int[] ports = parsePortRange(s[2]); 231 if (ports.length == 0) { 232 fail("Malformed port range '%s'", s[2]); 233 continue; 234 } 235 236 // match all addresses 237 if (s[1].equals("*")) { 238 result.add(new PortRangeRule(action, ports[0], ports[1])); 239 continue; 240 } 241 242 // hostname | ipaddress[/prefix] 243 int pos = s[1].indexOf('/'); 244 try { 245 if (pos < 0) { 246 // hostname or ipaddress (no prefix) 247 InetAddress[] addresses = InetAddress.getAllByName(s[1]); 248 for (InetAddress address: addresses) { 249 int prefix = 250 (address instanceof Inet4Address) ? 32 : 128; 251 result.add(new AddressPortRangeRule(action, address, 252 prefix, ports[0], ports[1])); 253 } 254 } else { 255 // ipaddress/prefix 256 InetAddress address = InetAddress 257 .getByName(s[1].substring(0, pos)); 258 int prefix = -1; 259 try { 260 prefix = Integer.parseInt(s[1].substring(pos+1)); 261 if (address instanceof Inet4Address) { 262 // must be 1-31 263 if (prefix < 0 || prefix > 32) prefix = -1; 264 } else { 265 // must be 1-128 266 if (prefix < 0 || prefix > 128) prefix = -1; 267 } 268 } catch (NumberFormatException e) { 269 } 270 271 if (prefix > 0) { 272 result.add(new AddressPortRangeRule(action, 273 address, prefix, ports[0], ports[1])); 274 } else { 275 fail("Malformed prefix '%s'", s[1]); 276 continue; 277 } 278 } 279 } catch (UnknownHostException uhe) { 280 fail("Unknown host or malformed IP address '%s'", s[1]); 281 continue; 282 } 283 } 284 return result; 285 } finally { 286 scanner.close(); 287 } 288 } 289 290 // converts unbound TCP socket to a SDP socket if it matches the rules 291 private void convertTcpToSdpIfMatch(FileDescriptor fdObj, 292 Action action, 293 InetAddress address, 294 int port) 295 throws IOException 296 { 297 boolean matched = false; 298 for (Rule rule: rules) { 299 if (rule.match(action, address, port)) { 300 SdpSupport.convertSocket(fdObj); 301 matched = true; 302 break; 303 } 304 } 305 if (log != null) { 306 String addr = (address instanceof Inet4Address) ? 307 address.getHostAddress() : "[" + address.getHostAddress() + "]"; 308 if (matched) { 309 log.format("%s to %s:%d (socket converted to SDP protocol)\n", action, addr, port); 310 } else { 311 log.format("%s to %s:%d (no match)\n", action, addr, port); 312 } 313 } 314 } 315 316 @Override 317 public void implBeforeTcpBind(FileDescriptor fdObj, 318 InetAddress address, 319 int port) 320 throws IOException 321 { 322 if (enabled) 323 convertTcpToSdpIfMatch(fdObj, Action.BIND, address, port); 324 } 325 326 @Override 327 public void implBeforeTcpConnect(FileDescriptor fdObj, 328 InetAddress address, 329 int port) 330 throws IOException 331 { 332 if (enabled) 333 convertTcpToSdpIfMatch(fdObj, Action.CONNECT, address, port); 334 } 335 }