/* Copyright (c) 2002, 2012, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception <http://www.mysql.com/about/legal/licensing/foss-exception.html>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.jdbc; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; /** * Socket factory for vanilla TCP/IP sockets (the standard) * * @author Mark Matthews */ public class StandardSocketFactory implements SocketFactory, SocketMetadata { public static final String TCP_NO_DELAY_PROPERTY_NAME = "tcpNoDelay"; public static final String TCP_KEEP_ALIVE_DEFAULT_VALUE = "true"; public static final String TCP_KEEP_ALIVE_PROPERTY_NAME = "tcpKeepAlive"; public static final String TCP_RCV_BUF_PROPERTY_NAME = "tcpRcvBuf"; public static final String TCP_SND_BUF_PROPERTY_NAME = "tcpSndBuf"; public static final String TCP_TRAFFIC_CLASS_PROPERTY_NAME = "tcpTrafficClass"; public static final String TCP_RCV_BUF_DEFAULT_VALUE = "0"; public static final String TCP_SND_BUF_DEFAULT_VALUE = "0"; public static final String TCP_TRAFFIC_CLASS_DEFAULT_VALUE = "0"; public static final String TCP_NO_DELAY_DEFAULT_VALUE = "true"; /** Use reflection for pre-1.4 VMs */ private static Method setTraficClassMethod; static { try { setTraficClassMethod = Socket.class.getMethod("setTrafficClass", new Class[] { Integer.TYPE }); } catch (SecurityException e) { setTraficClassMethod = null; } catch (NoSuchMethodException e) { setTraficClassMethod = null; } } /** The hostname to connect to */ protected String host = null; /** The port number to connect to */ protected int port = 3306; /** The underlying TCP/IP socket to use */ protected Socket rawSocket = null; /** * Called by the driver after issuing the MySQL protocol handshake and * reading the results of the handshake. * * @throws SocketException * if a socket error occurs * @throws IOException * if an I/O error occurs * * @return The socket to use after the handshake */ public Socket afterHandshake() throws SocketException, IOException { return this.rawSocket; } /** * Called by the driver before issuing the MySQL protocol handshake. Should * return the socket instance that should be used during the handshake. * * @throws SocketException * if a socket error occurs * @throws IOException * if an I/O error occurs * * @return the socket to use before the handshake */ public Socket beforeHandshake() throws SocketException, IOException { return this.rawSocket; } /** * Configures socket properties based on properties from the connection * (tcpNoDelay, snd/rcv buf, traffic class, etc). * * @param props * @throws SocketException * @throws IOException */ private void configureSocket(Socket sock, Properties props) throws SocketException, IOException { try { sock.setTcpNoDelay(Boolean.valueOf( props.getProperty(TCP_NO_DELAY_PROPERTY_NAME, TCP_NO_DELAY_DEFAULT_VALUE)).booleanValue()); String keepAlive = props.getProperty(TCP_KEEP_ALIVE_PROPERTY_NAME, TCP_KEEP_ALIVE_DEFAULT_VALUE); if (keepAlive != null && keepAlive.length() > 0) { sock.setKeepAlive(Boolean.valueOf(keepAlive) .booleanValue()); } int receiveBufferSize = Integer.parseInt(props.getProperty( TCP_RCV_BUF_PROPERTY_NAME, TCP_RCV_BUF_DEFAULT_VALUE)); if (receiveBufferSize > 0) { sock.setReceiveBufferSize(receiveBufferSize); } int sendBufferSize = Integer.parseInt(props.getProperty( TCP_SND_BUF_PROPERTY_NAME, TCP_SND_BUF_DEFAULT_VALUE)); if (sendBufferSize > 0) { sock.setSendBufferSize(sendBufferSize); } int trafficClass = Integer.parseInt(props.getProperty( TCP_TRAFFIC_CLASS_PROPERTY_NAME, TCP_TRAFFIC_CLASS_DEFAULT_VALUE)); if (trafficClass > 0 && setTraficClassMethod != null) { setTraficClassMethod.invoke(sock, new Object[] { Integer.valueOf(trafficClass) }); } } catch (Throwable t) { unwrapExceptionToProperClassAndThrowIt(t); } } /** * @see com.mysql.jdbc.SocketFactory#createSocket(Properties) */ public Socket connect(String hostname, int portNumber, Properties props) throws SocketException, IOException { if (props != null) { this.host = hostname; this.port = portNumber; Method connectWithTimeoutMethod = null; Method socketBindMethod = null; Class<?> socketAddressClass = null; String localSocketHostname = props .getProperty("localSocketAddress"); String connectTimeoutStr = props.getProperty("connectTimeout"); int connectTimeout = 0; boolean wantsTimeout = (connectTimeoutStr != null && connectTimeoutStr.length() > 0 && !connectTimeoutStr .equals("0")); boolean wantsLocalBind = (localSocketHostname != null && localSocketHostname .length() > 0); boolean needsConfigurationBeforeConnect = socketNeedsConfigurationBeforeConnect(props); if (wantsTimeout || wantsLocalBind || needsConfigurationBeforeConnect) { if (connectTimeoutStr != null) { try { connectTimeout = Integer.parseInt(connectTimeoutStr); } catch (NumberFormatException nfe) { throw new SocketException("Illegal value '" + connectTimeoutStr + "' for connectTimeout"); } } try { // Have to do this with reflection, otherwise older JVMs // croak socketAddressClass = Class .forName("java.net.SocketAddress"); connectWithTimeoutMethod = Socket.class.getMethod( "connect", new Class[] { socketAddressClass, Integer.TYPE }); socketBindMethod = Socket.class.getMethod("bind", new Class[] { socketAddressClass }); } catch (NoClassDefFoundError noClassDefFound) { // ignore, we give a better error below if needed } catch (NoSuchMethodException noSuchMethodEx) { // ignore, we give a better error below if needed } catch (Throwable catchAll) { // ignore, we give a better error below if needed } if (wantsLocalBind && socketBindMethod == null) { throw new SocketException( "Can't specify \"localSocketAddress\" on JVMs older than 1.4"); } if (wantsTimeout && connectWithTimeoutMethod == null) { throw new SocketException( "Can't specify \"connectTimeout\" on JVMs older than 1.4"); } } if (this.host != null) { if (!(wantsLocalBind || wantsTimeout || needsConfigurationBeforeConnect)) { InetAddress[] possibleAddresses = InetAddress .getAllByName(this.host); Throwable caughtWhileConnecting = null; // Need to loop through all possible addresses, in case // someone has IPV6 configured (SuSE, for example...) for (int i = 0; i < possibleAddresses.length; i++) { try { this.rawSocket = new Socket(possibleAddresses[i], port); configureSocket(this.rawSocket, props); break; } catch (Exception ex) { caughtWhileConnecting = ex; } } if (rawSocket == null) { unwrapExceptionToProperClassAndThrowIt(caughtWhileConnecting); } } else { // must explicitly state this due to classloader issues // when running on older JVMs :( try { InetAddress[] possibleAddresses = InetAddress .getAllByName(this.host); Throwable caughtWhileConnecting = null; Object localSockAddr = null; Class<?> inetSocketAddressClass = null; Constructor<?> addrConstructor = null; try { inetSocketAddressClass = Class .forName("java.net.InetSocketAddress"); addrConstructor = inetSocketAddressClass .getConstructor(new Class[] { InetAddress.class, Integer.TYPE }); if (wantsLocalBind) { localSockAddr = addrConstructor .newInstance(new Object[] { InetAddress .getByName(localSocketHostname), new Integer(0 /* * use ephemeral * port */) }); } } catch (Throwable ex) { unwrapExceptionToProperClassAndThrowIt(ex); } // Need to loop through all possible addresses, in case // someone has IPV6 configured (SuSE, for example...) for (int i = 0; i < possibleAddresses.length; i++) { try { this.rawSocket = new Socket(); configureSocket(this.rawSocket, props); Object sockAddr = addrConstructor .newInstance(new Object[] { possibleAddresses[i], Integer.valueOf(port) }); // bind to the local port if not using the ephemeral port if (localSockAddr != null) { socketBindMethod.invoke(rawSocket, new Object[] { localSockAddr }); } connectWithTimeoutMethod.invoke(rawSocket, new Object[] { sockAddr, Integer.valueOf(connectTimeout) }); break; } catch (Exception ex) { this.rawSocket = null; caughtWhileConnecting = ex; } } if (this.rawSocket == null) { unwrapExceptionToProperClassAndThrowIt(caughtWhileConnecting); } } catch (Throwable t) { unwrapExceptionToProperClassAndThrowIt(t); } } return this.rawSocket; } } throw new SocketException("Unable to create socket"); } /** * Does the configureSocket() need to be called before the socket is * connect()d based on the properties supplied? * */ private boolean socketNeedsConfigurationBeforeConnect(Properties props) { int receiveBufferSize = Integer.parseInt(props.getProperty( TCP_RCV_BUF_PROPERTY_NAME, TCP_RCV_BUF_DEFAULT_VALUE)); if (receiveBufferSize > 0) { return true; } int sendBufferSize = Integer.parseInt(props.getProperty( TCP_SND_BUF_PROPERTY_NAME, TCP_SND_BUF_DEFAULT_VALUE)); if (sendBufferSize > 0) { return true; } int trafficClass = Integer.parseInt(props.getProperty( TCP_TRAFFIC_CLASS_PROPERTY_NAME, TCP_TRAFFIC_CLASS_DEFAULT_VALUE)); if (trafficClass > 0 && setTraficClassMethod != null) { return true; } return false; } private void unwrapExceptionToProperClassAndThrowIt( Throwable caughtWhileConnecting) throws SocketException, IOException { if (caughtWhileConnecting instanceof InvocationTargetException) { // Replace it with the target, don't use 1.4 chaining as this still // needs to run on older VMs caughtWhileConnecting = ((InvocationTargetException) caughtWhileConnecting) .getTargetException(); } if (caughtWhileConnecting instanceof SocketException) { throw (SocketException) caughtWhileConnecting; } if (caughtWhileConnecting instanceof IOException) { throw (IOException) caughtWhileConnecting; } throw new SocketException(caughtWhileConnecting.toString()); } public static final String IS_LOCAL_HOSTNAME_REPLACEMENT_PROPERTY_NAME = "com.mysql.jdbc.test.isLocalHostnameReplacement"; public boolean isLocallyConnected(com.mysql.jdbc.ConnectionImpl conn) throws SQLException { long threadId = conn.getId(); java.sql.Statement processListStmt = conn.getMetadataSafeStatement(); ResultSet rs = null; try { String processHost = null; rs = processListStmt.executeQuery("SHOW PROCESSLIST"); while (rs.next()) { long id = rs.getLong(1); if (threadId == id) { processHost = rs.getString(3); break; } } // "inject" for tests if (System.getProperty(IS_LOCAL_HOSTNAME_REPLACEMENT_PROPERTY_NAME) != null) { processHost = System .getProperty(IS_LOCAL_HOSTNAME_REPLACEMENT_PROPERTY_NAME); } else if (conn.getProperties().getProperty(IS_LOCAL_HOSTNAME_REPLACEMENT_PROPERTY_NAME) != null) { processHost = conn.getProperties().getProperty(IS_LOCAL_HOSTNAME_REPLACEMENT_PROPERTY_NAME); } if (processHost != null) { if (processHost.indexOf(":") != -1) { processHost = processHost.split(":")[0]; try { boolean isLocal = false; InetAddress whereMysqlThinksIConnectedFrom = InetAddress.getByName(processHost); SocketAddress remoteSocketAddr = rawSocket.getRemoteSocketAddress(); if (remoteSocketAddr instanceof InetSocketAddress) { InetAddress whereIConnectedTo = ((InetSocketAddress)remoteSocketAddr).getAddress(); isLocal = whereMysqlThinksIConnectedFrom.equals(whereIConnectedTo); } return isLocal; } catch (UnknownHostException e) { conn.getLog().logWarn( Messages.getString( "Connection.CantDetectLocalConnect", new Object[] { host }), e); return false; } } } return false; } finally { processListStmt.close(); } } }