X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=libcore%2Fluni%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fharmony%2Fluni%2Fnet%2FPlainSocketImpl.java;fp=libcore%2Fluni%2Fsrc%2Fmain%2Fjava%2Forg%2Fapache%2Fharmony%2Fluni%2Fnet%2FPlainSocketImpl.java;h=2a36feed8fc6ad3c2ab3b46f53c1de725afc7787;hb=83fa27c1ec7fd1099f7dc78c076c0729c6747c44;hp=0000000000000000000000000000000000000000;hpb=4d4ffcf32094db29d5d45f296eb065e9a729934b;p=gb-231r1-is01%2FGingerbread_2.3.3_r1_IS01.git diff --git a/libcore/luni/src/main/java/org/apache/harmony/luni/net/PlainSocketImpl.java b/libcore/luni/src/main/java/org/apache/harmony/luni/net/PlainSocketImpl.java new file mode 100644 index 000000000..2a36feed8 --- /dev/null +++ b/libcore/luni/src/main/java/org/apache/harmony/luni/net/PlainSocketImpl.java @@ -0,0 +1,477 @@ +/* + * 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.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.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; + +/** + * A concrete connected-socket implementation. + */ +public class PlainSocketImpl extends SocketImpl { + + // 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; + + protected INetworkSystem netImpl = Platform.getNetworkSystem(); + + private boolean streaming = true; + + private boolean shutdownInput; + + private Proxy proxy; + + public PlainSocketImpl(FileDescriptor fd) { + this.fd = fd; + } + + public PlainSocketImpl(Proxy proxy) { + this(new FileDescriptor()); + this.proxy = proxy; + } + + public PlainSocketImpl() { + this(new FileDescriptor()); + } + + 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 (usingSocks()) { + ((PlainSocketImpl) newImpl).socksBind(); + ((PlainSocketImpl) newImpl).socksAccept(); + return; + } + + try { + if (newImpl instanceof PlainSocketImpl) { + PlainSocketImpl newPlainSocketImpl = (PlainSocketImpl) newImpl; + netImpl.accept(fd, newImpl, newPlainSocketImpl.getFileDescriptor()); + } else { + // if newImpl is not an instance of PlainSocketImpl, use + // reflection to get/set protected fields. + if (fdField == null) { + fdField = getSocketImplField("fd"); + } + FileDescriptor newFd = (FileDescriptor) fdField.get(newImpl); + netImpl.accept(fd, newImpl, newFd); + } + } catch (IllegalAccessException e) { + // empty + } + } + + private boolean usingSocks() { + return proxy != null && proxy.type() == Proxy.Type.SOCKS; + } + + /** + * gets SocketImpl field by reflection. + */ + private Field getSocketImplField(final String fieldName) { + return AccessController.doPrivileged(new PrivilegedAction() { + public Field run() { + Field field = null; + try { + field = SocketImpl.class.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new Error(e); + } + return field; + } + }); + } + + public void initLocalPort(int localPort) { + this.localport = localPort; + } + + public void initRemoteAddressAndPort(InetAddress remoteAddress, int remotePort) { + this.address = remoteAddress; + this.port = remotePort; + } + + private void checkNotClosed() throws IOException { + if (!fd.valid()) { + throw new SocketException("Socket is closed"); + } + } + + @Override + protected synchronized int available() throws IOException { + checkNotClosed(); + // 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) { + return 0; + } + return Platform.getFileSystem().ioctlAvailable(fd); + } + + @Override + protected void bind(InetAddress address, int port) throws IOException { + netImpl.bind(fd, address, port); + this.address = address; + if (port != 0) { + this.localport = port; + } else { + this.localport = netImpl.getSocketLocalPort(fd); + } + } + + @Override + protected void close() throws IOException { + synchronized (fd) { + if (fd.valid()) { + netImpl.close(fd); + fd = new FileDescriptor(); + } + } + } + + @Override + protected void connect(String aHost, int aPort) throws IOException { + connect(InetAddress.getByName(aHost), 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 && usingSocks()) { + socksConnect(anAddr, aPort, 0); + } else { + netImpl.connect(fd, normalAddr, aPort, timeout); + } + } 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; + netImpl.socket(fd, streaming); + } + + @Override protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + @Override + protected synchronized InputStream getInputStream() throws IOException { + checkNotClosed(); + return new SocketInputStream(this); + } + + @Override + public Object getOption(int optID) throws SocketException { + return netImpl.getSocketOption(fd, optID); + } + + @Override + protected synchronized OutputStream getOutputStream() throws IOException { + checkNotClosed(); + return new SocketOutputStream(this); + } + + @Override + protected void listen(int backlog) throws IOException { + if (usingSocks()) { + // Do nothing for a SOCKS connection. The listen occurs on the + // server during the bind. + return; + } + netImpl.listen(fd, backlog); + } + + @Override + public void setOption(int optID, Object val) throws SocketException { + netImpl.setSocketOption(fd, optID, val); + } + + /** + * 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(); + } + return InetAddress.getByName(proxyName); + } + + /** + * Connect using a SOCKS server. + */ + private void socksConnect(InetAddress applicationServerAddress, + int applicationServerPort, int timeout) throws IOException { + try { + netImpl.connect(fd, socksGetServerAddress(), socksGetServerPort(), timeout); + } catch (Exception e) { + throw new SocketException("SOCKS connection failed: " + e); + } + + 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, socksGetServerAddress(), socksGetServerPort(), 0); + } catch (Exception e) { + throw new IOException("Unable to connect to SOCKS server: " + e); + } + + // There must be a connection to an application host for the bind to + // work. + if (lastConnectedAddress == null) { + throw new SocketException("Invalid SOCKS client"); + } + + // 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]; + intToBytes(reply.getIP(), replyBytes, 0); + address = InetAddress.getByAddress(replyBytes); + } + localport = reply.getPort(); + } + + private static void intToBytes(int value, byte[] bytes, int start) { + /* + * Shift the int so the current byte is right-most Use a byte mask of + * 255 to single out the last byte. + */ + bytes[start] = (byte) ((value >> 24) & 255); + bytes[start + 1] = (byte) ((value >> 16) & 255); + bytes[start + 2] = (byte) ((value >> 8) & 255); + bytes[start + 3] = (byte) (value & 255); + } + + /** + * 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"); + + 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("Malformed reply from SOCKS server"); + } + return reply; + } + + @Override + protected void connect(SocketAddress remoteAddr, int timeout) + throws IOException { + InetSocketAddress inetAddr = (InetSocketAddress) remoteAddr; + connect(inetAddr.getAddress(), inetAddr.getPort(), timeout); + } + + @Override + protected boolean supportsUrgentData() { + return true; + } + + @Override + protected void sendUrgentData(int value) throws IOException { + netImpl.sendUrgentData(fd, (byte) value); + } + + FileDescriptor getFD() { + return fd; + } + + int read(byte[] buffer, int offset, int count) throws IOException { + if (shutdownInput) { + return -1; + } + int read = netImpl.read(fd, buffer, offset, count); + // 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.write(fd, buffer, offset, count); + } else { + return netImpl.send(fd, buffer, offset, count, port, address); + } + } +}