/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.luni.net; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketImpl; import java.net.SocketOptions; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.security.AccessController; import java.security.PrivilegedAction; import org.apache.harmony.luni.platform.INetworkSystem; import org.apache.harmony.luni.platform.Platform; import org.apache.harmony.luni.util.Msg; /** * A concrete connected-socket implementation. */ public class PlainSocketImpl extends SocketImpl { // Const copy from socket static final int MULTICAST_IF = 1; static final int MULTICAST_TTL = 2; static final int TCP_NODELAY = 4; static final int FLAG_SHUTDOWN = 8; // For SOCKS support. A SOCKS bind() uses the last // host connected to in its request. static private InetAddress lastConnectedAddress; static private int lastConnectedPort; private static Field fdField; private static Field localportField; private boolean tcpNoDelay = true; /** * used to store the trafficClass value which is simply returned as the * value that was set. We also need it to pass it to methods that specify an * address packets are going to be sent to */ private int trafficClass; protected INetworkSystem netImpl = Platform.getNetworkSystem(); public int receiveTimeout = 0; public boolean streaming = true; public boolean shutdownInput; Proxy proxy; public PlainSocketImpl() { super(); fd = new FileDescriptor(); } public PlainSocketImpl(FileDescriptor fd) { super(); this.fd = fd; } /** * creates an instance with specified proxy. */ public PlainSocketImpl(Proxy proxy) { this(); this.proxy = proxy; } public PlainSocketImpl(FileDescriptor fd, int localport, InetAddress addr, int port) { super(); this.fd = fd; this.localport = localport; this.address = addr; this.port = port; } @Override protected void accept(SocketImpl newImpl) throws IOException { if (NetUtil.usingSocks(proxy)) { ((PlainSocketImpl) newImpl).socksBind(); ((PlainSocketImpl) newImpl).socksAccept(); return; } try { if (newImpl instanceof PlainSocketImpl) { PlainSocketImpl newPlainSocketImpl = (PlainSocketImpl) newImpl; // BEGIN android-changed // call accept instead of acceptStreamImpl (native impl is identical) netImpl.accept(fd, newImpl, newPlainSocketImpl .getFileDescriptor(), receiveTimeout); // END android-changed newPlainSocketImpl.setLocalport(getLocalPort()); } else { // if newImpl is not an instance of PlainSocketImpl, use // reflection to get/set protected fields. if (null == fdField) { fdField = getSocketImplField("fd"); //$NON-NLS-1$ } FileDescriptor newFd = (FileDescriptor) fdField.get(newImpl); // BEGIN android-changed // call accept instead of acceptStreamImpl (native impl is identical) netImpl.accept(fd, newImpl, newFd, receiveTimeout); // END android-cahnged if (null == localportField) { localportField = getSocketImplField("localport"); //$NON-NLS-1$ } localportField.setInt(newImpl, getLocalPort()); } } catch (InterruptedIOException e) { throw new SocketTimeoutException(e.getMessage()); } catch (IllegalAccessException e) { // empty } } /** * gets SocketImpl field by reflection. */ private Field getSocketImplField(final String fieldName) { return AccessController.doPrivileged(new PrivilegedAction<Field>() { public Field run() { Field field = null; try { field = SocketImpl.class.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException e) { throw new Error(e); } return field; } }); } @Override protected synchronized int available() throws IOException { // we need to check if the input has been shutdown. If so // we should return that there is no data to be read if (shutdownInput == true) { return 0; } return netImpl.availableStream(fd); } @Override protected void bind(InetAddress anAddr, int aPort) throws IOException { netImpl.bind(fd, anAddr, aPort); // PlainSocketImpl2.socketBindImpl2(fd, aPort, anAddr); address = anAddr; if (0 != aPort) { localport = aPort; } else { localport = netImpl.getSocketLocalPort(fd, NetUtil .preferIPv6Addresses()); } } @Override protected void close() throws IOException { synchronized (fd) { if (fd.valid()) { if ((netImpl.getSocketFlags() & FLAG_SHUTDOWN) != 0) { try { shutdownOutput(); } catch (Exception e) { } } netImpl.socketClose(fd); fd = new FileDescriptor(); } } } @Override protected void connect(String aHost, int aPort) throws IOException { InetAddress anAddr = netImpl.getHostByName(aHost, NetUtil .preferIPv6Addresses()); connect(anAddr, aPort); } @Override protected void connect(InetAddress anAddr, int aPort) throws IOException { connect(anAddr, aPort, 0); } /** * Connects this socket to the specified remote host address/port. * * @param anAddr * the remote host address to connect to * @param aPort * the remote port to connect to * @param timeout * a timeout where supported. 0 means no timeout * @throws IOException * if an error occurs while connecting */ private void connect(InetAddress anAddr, int aPort, int timeout) throws IOException { InetAddress normalAddr = anAddr.isAnyLocalAddress() ? InetAddress.getLocalHost() : anAddr; try { if (streaming) { if (NetUtil.usingSocks(proxy)) { socksConnect(anAddr, aPort, 0); } else { if (timeout == 0) { netImpl.connect(fd, trafficClass, normalAddr, aPort); } else { netImpl.connectStreamWithTimeoutSocket(fd, aPort, timeout, trafficClass, normalAddr); } } } else { netImpl.connectDatagram(fd, aPort, trafficClass, normalAddr); } } catch (ConnectException e) { throw new ConnectException(anAddr + ":" + aPort + " - " + e.getMessage()); } super.address = normalAddr; super.port = aPort; } @Override protected void create(boolean streaming) throws IOException { this.streaming = streaming; if (streaming) { netImpl.createStreamSocket(fd, NetUtil.preferIPv4Stack()); } else { netImpl.createDatagramSocket(fd, NetUtil.preferIPv4Stack()); } } @Override protected void finalize() throws IOException { close(); } @Override protected synchronized InputStream getInputStream() throws IOException { if (!fd.valid()) { throw new SocketException(Msg.getString("K003d")); } return new SocketInputStream(this); } @Override public Object getOption(int optID) throws SocketException { if (optID == SocketOptions.SO_TIMEOUT) { return Integer.valueOf(receiveTimeout); } else if (optID == SocketOptions.IP_TOS) { return Integer.valueOf(trafficClass); } else { // Call the native first so there will be // an exception if the socket if closed. Object result = netImpl.getSocketOption(fd, optID); if (optID == SocketOptions.TCP_NODELAY && (netImpl.getSocketFlags() & TCP_NODELAY) != 0) { return Boolean.valueOf(tcpNoDelay); } return result; } } @Override protected synchronized OutputStream getOutputStream() throws IOException { if (!fd.valid()) { throw new SocketException(Msg.getString("K003d")); //$NON-NLS-1$ } return new SocketOutputStream(this); } @Override protected void listen(int backlog) throws IOException { if (NetUtil.usingSocks(proxy)) { // Do nothing for a SOCKS connection. The listen occurs on the // server during the bind. return; } netImpl.listenStreamSocket(fd, backlog); } @Override public void setOption(int optID, Object val) throws SocketException { if (optID == SocketOptions.SO_TIMEOUT) { receiveTimeout = ((Integer) val).intValue(); } else { try { netImpl.setSocketOption(fd, optID, val); if (optID == SocketOptions.TCP_NODELAY && (netImpl.getSocketFlags() & TCP_NODELAY) != 0) { tcpNoDelay = ((Boolean) val).booleanValue(); } } catch (SocketException e) { // we don't throw an exception for IP_TOS even if the platform // won't let us set the requested value if (optID != SocketOptions.IP_TOS) { throw e; } } /* * save this value as it is actually used differently for IPv4 and * IPv6 so we cannot get the value using the getOption. The option * is actually only set for IPv4 and a masked version of the value * will be set as only a subset of the values are allowed on the * socket. Therefore we need to retain it to return the value that * was set. We also need the value to be passed into a number of * natives so that it can be used properly with IPv6 */ if (optID == SocketOptions.IP_TOS) { trafficClass = ((Integer) val).intValue(); } } } /** * Gets the SOCKS proxy server port. */ private int socksGetServerPort() { // get socks server port from proxy. It is unnecessary to check // "socksProxyPort" property, since proxy setting should only be // determined by ProxySelector. InetSocketAddress addr = (InetSocketAddress) proxy.address(); return addr.getPort(); } /** * Gets the InetAddress of the SOCKS proxy server. */ private InetAddress socksGetServerAddress() throws UnknownHostException { String proxyName; // get socks server address from proxy. It is unnecessary to check // "socksProxyHost" property, since all proxy setting should be // determined by ProxySelector. InetSocketAddress addr = (InetSocketAddress) proxy.address(); proxyName = addr.getHostName(); if (null == proxyName) { proxyName = addr.getAddress().getHostAddress(); } InetAddress anAddr = netImpl.getHostByName(proxyName, NetUtil .preferIPv6Addresses()); return anAddr; } /** * Connect using a SOCKS server. */ private void socksConnect(InetAddress applicationServerAddress, int applicationServerPort, int timeout) throws IOException { try { if (timeout == 0) { netImpl.connect(fd, trafficClass, socksGetServerAddress(), socksGetServerPort()); } else { netImpl.connectStreamWithTimeoutSocket(fd, socksGetServerPort(), timeout, trafficClass, socksGetServerAddress()); } } catch (Exception e) { throw new SocketException(Msg.getString("K003e", e)); //$NON-NLS-1$ } socksRequestConnection(applicationServerAddress, applicationServerPort); lastConnectedAddress = applicationServerAddress; lastConnectedPort = applicationServerPort; } /** * Request a SOCKS connection to the application server given. If the * request fails to complete successfully, an exception is thrown. */ private void socksRequestConnection(InetAddress applicationServerAddress, int applicationServerPort) throws IOException { socksSendRequest(Socks4Message.COMMAND_CONNECT, applicationServerAddress, applicationServerPort); Socks4Message reply = socksReadReply(); if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) { throw new IOException(reply.getErrorString(reply .getCommandOrResult())); } } /** * Perform an accept for a SOCKS bind. */ public void socksAccept() throws IOException { Socks4Message reply = socksReadReply(); if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) { throw new IOException(reply.getErrorString(reply .getCommandOrResult())); } } /** * Shutdown the input portion of the socket. */ @Override protected void shutdownInput() throws IOException { shutdownInput = true; netImpl.shutdownInput(fd); } /** * Shutdown the output portion of the socket. */ @Override protected void shutdownOutput() throws IOException { netImpl.shutdownOutput(fd); } /** * Bind using a SOCKS server. */ private void socksBind() throws IOException { try { netImpl.connect(fd, trafficClass, socksGetServerAddress(), socksGetServerPort()); } catch (Exception e) { throw new IOException(Msg.getString("K003f", e)); //$NON-NLS-1$ } // There must be a connection to an application host for the bind to // work. if (lastConnectedAddress == null) { throw new SocketException(Msg.getString("K0040")); //$NON-NLS-1$ } // Use the last connected address and port in the bind request. socksSendRequest(Socks4Message.COMMAND_BIND, lastConnectedAddress, lastConnectedPort); Socks4Message reply = socksReadReply(); if (reply.getCommandOrResult() != Socks4Message.RETURN_SUCCESS) { throw new IOException(reply.getErrorString(reply .getCommandOrResult())); } // A peculiarity of socks 4 - if the address returned is 0, use the // original socks server address. if (reply.getIP() == 0) { address = socksGetServerAddress(); } else { // IPv6 support not yet required as // currently the Socks4Message.getIP() only returns int, // so only works with IPv4 4byte addresses byte[] replyBytes = new byte[4]; NetUtil.intToBytes(reply.getIP(), replyBytes, 0); address = InetAddress.getByAddress(replyBytes); } localport = reply.getPort(); } /** * Send a SOCKS V4 request. */ private void socksSendRequest(int command, InetAddress address, int port) throws IOException { Socks4Message request = new Socks4Message(); request.setCommandOrResult(command); request.setPort(port); request.setIP(address.getAddress()); request.setUserId("default"); //$NON-NLS-1$ getOutputStream().write(request.getBytes(), 0, request.getLength()); } /** * Read a SOCKS V4 reply. */ private Socks4Message socksReadReply() throws IOException { Socks4Message reply = new Socks4Message(); int bytesRead = 0; while (bytesRead < Socks4Message.REPLY_LENGTH) { int count = getInputStream().read(reply.getBytes(), bytesRead, Socks4Message.REPLY_LENGTH - bytesRead); if (-1 == count) { break; } bytesRead += count; } if (Socks4Message.REPLY_LENGTH != bytesRead) { throw new SocketException(Msg.getString("KA011")); //$NON-NLS-1$ } return reply; } @Override protected void connect(SocketAddress remoteAddr, int timeout) throws IOException { InetSocketAddress inetAddr = (InetSocketAddress) remoteAddr; connect(inetAddr.getAddress(), inetAddr.getPort(), timeout); } /** * Answer if the socket supports urgent data. */ @Override protected boolean supportsUrgentData() { return !streaming || netImpl.supportsUrgentData(fd); } @Override protected void sendUrgentData(int value) throws IOException { netImpl.sendUrgentData(fd, (byte) value); } FileDescriptor getFD() { return fd; } private void setLocalport(int localport) { this.localport = localport; } int read(byte[] buffer, int offset, int count) throws IOException { if (shutdownInput) { return -1; } // BEGIN android-changed // call receiveStream() instead of read() int read = netImpl.receiveStream(fd, buffer, offset, count, receiveTimeout); // END android-changed // Return of zero bytes for a blocking socket means a timeout occurred if (read == 0) { throw new SocketTimeoutException(); } // Return of -1 indicates the peer was closed if (read == -1) { shutdownInput = true; } return read; } int write(byte[] buffer, int offset, int count) throws IOException { if (!streaming) { return netImpl.sendDatagram2(fd, buffer, offset, count, port, address); } // BEGIN android-changed // call sendStream() instead of write() return netImpl.sendStream(fd, buffer, offset, count); // END android-changed } }