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