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