├── src ├── test │ ├── resources │ │ └── simplelogger.properties │ └── java │ │ └── ManualTest.java └── main │ └── java │ └── org │ └── rnorth │ └── tcpunixsocketproxy │ ├── DaemonThreadFactory.java │ ├── ProxyPump.java │ └── TcpToUnixSocketProxy.java ├── .idea ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── modules.xml ├── compiler.xml └── misc.xml ├── LICENSE ├── .gitignore ├── unixsocketproxy.iml ├── README.md └── pom.xml /src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=TRACE -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/org/rnorth/tcpunixsocketproxy/DaemonThreadFactory.java: -------------------------------------------------------------------------------- 1 | package org.rnorth.tcpunixsocketproxy; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * Basic factory that produces named daemon threads. 8 | */ 9 | class DaemonThreadFactory implements ThreadFactory { 10 | 11 | private final AtomicInteger THREAD_ID_COUNTER = new AtomicInteger(); 12 | 13 | @Override 14 | public Thread newThread(Runnable r) { 15 | final Thread thread = new Thread(r); 16 | thread.setName("tcp-unix-socket-proxy-daemon-thread-" + THREAD_ID_COUNTER.getAndIncrement()); 17 | thread.setDaemon(true); 18 | return thread; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/ManualTest.java: -------------------------------------------------------------------------------- 1 | import org.rnorth.tcpunixsocketproxy.TcpToUnixSocketProxy; 2 | import org.slf4j.Logger; 3 | import org.slf4j.LoggerFactory; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.net.InetSocketAddress; 8 | 9 | /** 10 | * Simple test class for manual testing purposes. 11 | */ 12 | public class ManualTest { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(ManualTest.class); 15 | 16 | public static void main(String[] args) throws IOException, InterruptedException { 17 | 18 | TcpToUnixSocketProxy proxy = new TcpToUnixSocketProxy(new File("/var/run/docker.sock")); 19 | 20 | InetSocketAddress address = proxy.start(); 21 | logger.info("Listening on {}:{}", address.getHostName(), address.getPort()); 22 | Thread.sleep(60_000); 23 | proxy.stop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Richard North 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | 13 | ### JetBrains template 14 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 15 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 16 | 17 | # User-specific stuff: 18 | .idea/workspace.xml 19 | .idea/tasks.xml 20 | .idea/dictionaries 21 | .idea/vcs.xml 22 | .idea/jsLibraryMappings.xml 23 | 24 | # Sensitive or high-churn files: 25 | .idea/dataSources.ids 26 | .idea/dataSources.xml 27 | .idea/dataSources.local.xml 28 | .idea/sqlDataSources.xml 29 | .idea/dynamic.xml 30 | .idea/uiDesigner.xml 31 | 32 | # Gradle: 33 | .idea/gradle.xml 34 | .idea/libraries 35 | 36 | # Mongo Explorer plugin: 37 | .idea/mongoSettings.xml 38 | 39 | ## File-based project format: 40 | *.iws 41 | 42 | ## Plugin-specific files: 43 | 44 | # IntelliJ 45 | /out/ 46 | 47 | # mpeltonen/sbt-idea plugin 48 | .idea_modules/ 49 | 50 | # JIRA plugin 51 | atlassian-ide-plugin.xml 52 | 53 | # Crashlytics plugin (for Android Studio and IntelliJ) 54 | com_crashlytics_export_strings.xml 55 | crashlytics.properties 56 | crashlytics-build.properties 57 | fabric.properties 58 | 59 | -------------------------------------------------------------------------------- /unixsocketproxy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Maven 22 | 23 | 24 | 25 | 26 | CFML 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TcpToUnixSocketProxy 2 | 3 | Listens on a TCP port and proxies connections to a UNIX domain socket. 4 | 5 | Like `socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/tmp/docker.sock`, 6 | except this is literally the only thing this program does. 7 | 8 | Purposefully simplistic in implementation, potentially buggy or 9 | suboptimal in performance, may contain nuts. 10 | 11 | This proxy uses Christian Kohlschütter's [junixsocket](https://github.com/kohlschutter/junixsocket) library 12 | for interaction with Unix sockets. 13 | 14 | ## Rationale 15 | 16 | This was implemented as a short term workaround for incompatibility between netty and Docker for Mac beta's 17 | use of unix domain sockets on OS X - described [here](https://github.com/docker-java/docker-java/issues/537). 18 | 19 | ### Caveats 20 | 21 | This proxy uses a deliberately simple blocking I/O with threads model, purely for simplicity. This clearly 22 | eliminates the performance advantages of using netty in docker-java. However, I feel that in most cases the 23 | performance of the docker API is not a critical factor, and a working but slow solution is preferable in the 24 | short term. Still, once [kqueue support for netty](https://github.com/netty/netty/issues/2448) is in place, that 25 | will become the better solution. 26 | 27 | This library is only useful on OS X; Linux unix socket support through epoll is well supported by netty. 28 | 29 | ## Usage 30 | 31 | Instantiate a proxy instance: 32 | 33 | TcpToUnixSocketProxy proxy = new TcpToUnixSocketProxy(new File("/var/run/docker.sock")); 34 | 35 | Start it, and obtain the listening address (localhost with a random port by default): 36 | 37 | InetSocketAddress address = proxy.start(); 38 | 39 | Use the proxy by connecting to localhost on the port given by `address.getPort()`. 40 | 41 | Then when the proxy is no longer needed: 42 | 43 | proxy.stop(); 44 | 45 | ## License 46 | 47 | See [LICENSE](LICENSE). 48 | 49 | ## Copyright 50 | 51 | Copyright (c) 2016 Richard North. -------------------------------------------------------------------------------- /src/main/java/org/rnorth/tcpunixsocketproxy/ProxyPump.java: -------------------------------------------------------------------------------- 1 | package org.rnorth.tcpunixsocketproxy; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.net.Socket; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | 13 | /** 14 | * General purpose proxy between two {@link Socket}s. Blocking I/O for raw simplicity; not intended to 15 | * be particularly performant. 16 | */ 17 | class ProxyPump { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ProxyPump.class); 20 | private static final int COPY_BUFFER_SIZE = 4096; 21 | private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(new DaemonThreadFactory()); 22 | 23 | /** 24 | * Proxy incoming traffic from the client socket to the server socket (and responses 25 | * back). 26 | * 27 | * @param clientSocket the socket the client is connecting to (e.g. a local {@link java.net.ServerSocket}) 28 | * @param serverSocket the socket for connections to the (remote) server 29 | * @throws IOException if sockets cannot be streamed to/from 30 | */ 31 | 32 | public ProxyPump(final Socket clientSocket, final Socket serverSocket) throws IOException { 33 | 34 | InputStream fromClient = clientSocket.getInputStream(); 35 | OutputStream toClient = clientSocket.getOutputStream(); 36 | InputStream fromServer = serverSocket.getInputStream(); 37 | OutputStream toServer = serverSocket.getOutputStream(); 38 | 39 | // Start to copy data from client to server 40 | EXECUTOR.submit(() -> { 41 | copyUntilFailure(fromClient, toServer); 42 | logger.trace("C->S died, closing sockets"); 43 | quietlyClose(serverSocket); 44 | quietlyClose(clientSocket); 45 | }); 46 | 47 | // Start to copy data back from server to client 48 | EXECUTOR.submit(() -> { 49 | copyUntilFailure(fromServer, toClient); 50 | logger.trace("S->C died, closing sockets"); 51 | quietlyClose(serverSocket); 52 | quietlyClose(clientSocket); 53 | }); 54 | } 55 | 56 | /* 57 | * Copy data from a from stream to a to stream, until the from stream ends. 58 | */ 59 | private void copyUntilFailure(InputStream from, OutputStream to) { 60 | byte[] buffer = new byte[COPY_BUFFER_SIZE]; 61 | 62 | int read; 63 | try { 64 | while ((read = from.read(buffer)) != -1) { 65 | to.write(buffer, 0, read); 66 | to.flush(); 67 | } 68 | } catch (IOException ignored) { 69 | // just return 70 | } 71 | } 72 | 73 | /* 74 | * Close a socket without handling exceptions, which also closes its streams. 75 | */ 76 | private void quietlyClose(Socket socket) { 77 | if (socket != null && !socket.isClosed()) { 78 | try { 79 | socket.close(); 80 | } catch (IOException ignored) { 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/rnorth/tcpunixsocketproxy/TcpToUnixSocketProxy.java: -------------------------------------------------------------------------------- 1 | package org.rnorth.tcpunixsocketproxy; 2 | 3 | import org.newsclub.net.unix.AFUNIXSocket; 4 | import org.newsclub.net.unix.AFUNIXSocketAddress; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.net.InetSocketAddress; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | 14 | /** 15 | * Listens on a TCP port and proxies connections to a UNIX domain socket. 16 | *

17 | * Like socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/tmp/docker.sock, except this is literally the only 18 | * thing this program does. 19 | *

20 | * Purposefully simplistic in implementation, using blocking I/O for simplicity over performance. 21 | * 22 | * @author Richard North <rich.north@gmail.com> 23 | */ 24 | public class TcpToUnixSocketProxy { 25 | 26 | private final String listenHostname; 27 | private final int listenPort; 28 | private final File unixSocketFile; 29 | private ServerSocket listenSocket; 30 | 31 | private static final Logger logger = LoggerFactory.getLogger(ProxyPump.class); 32 | private Thread acceptThread; 33 | private boolean running = true; 34 | 35 | /** 36 | * Create an instance of the proxy that will listen for TCP connections on localhost on a 37 | * random available port. 38 | *

This should be the usual method of instantiating a proxy.

39 | * @param unixSocketFile the local unix domain socket as a File 40 | */ 41 | public TcpToUnixSocketProxy(File unixSocketFile) { 42 | 43 | if (!unixSocketFile.exists()) { 44 | throw new IllegalArgumentException("Socket file does not exist: " + unixSocketFile); 45 | } 46 | 47 | this.listenHostname = "localhost"; 48 | this.listenPort = 0; 49 | this.unixSocketFile = unixSocketFile; 50 | } 51 | /** 52 | * Create an instance of the proxy. 53 | * @param listenHostname hostname to listen on 54 | * @param listenPort port to listen on, or 0 if an available port should be allocated 55 | * @param unixSocketFile the local unix domain socket as a File 56 | */ 57 | public TcpToUnixSocketProxy(String listenHostname, int listenPort, File unixSocketFile) { 58 | 59 | if (!unixSocketFile.exists()) { 60 | throw new IllegalArgumentException("Socket file does not exist: " + unixSocketFile); 61 | } 62 | 63 | this.listenHostname = listenHostname; 64 | this.listenPort = listenPort; 65 | this.unixSocketFile = unixSocketFile; 66 | } 67 | 68 | /** 69 | * Start the proxy 70 | * @return the proxy's listening socket address 71 | * @throws IOException on socket binding failure 72 | */ 73 | public InetSocketAddress start() throws IOException { 74 | 75 | listenSocket = new ServerSocket(); 76 | listenSocket.bind(new InetSocketAddress(listenHostname, listenPort)); 77 | 78 | logger.debug("Listening on {} and proxying to {}", listenSocket.getLocalSocketAddress(), unixSocketFile.getAbsolutePath()); 79 | 80 | acceptThread = new Thread(() -> { 81 | while (running) { 82 | try { 83 | Socket incomingSocket = listenSocket.accept(); 84 | logger.debug("Accepting incoming connection from {}", incomingSocket.getRemoteSocketAddress()); 85 | 86 | AFUNIXSocket outgoingSocket = AFUNIXSocket.newInstance(); 87 | outgoingSocket.connect(new AFUNIXSocketAddress(unixSocketFile)); 88 | 89 | new ProxyPump(incomingSocket, outgoingSocket); 90 | } catch (IOException ignored) { 91 | } 92 | } 93 | }); 94 | acceptThread.setDaemon(true); 95 | acceptThread.setName("tcp-unix-proxy-accept-thread"); 96 | acceptThread.start(); 97 | 98 | return (InetSocketAddress) listenSocket.getLocalSocketAddress(); 99 | } 100 | 101 | /** 102 | * Stop the proxy. 103 | */ 104 | public void stop() { 105 | try { 106 | running = false; 107 | listenSocket.close(); 108 | } catch (IOException ignored) { 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.rnorth 8 | tcp-unix-socket-proxy 9 | 1.0.2-SNAPSHOT 10 | 11 | TCP to Unix Socket Proxy 12 | 13 | Listens on a TCP port and proxies connections to a UNIX domain socket. 14 | 15 | https://github.com/rnorth/tcp-unix-socket-proxy 16 | 17 | 18 | MIT 19 | http://opensource.org/licenses/MIT 20 | 21 | 22 | 23 | 24 | rnorth 25 | Richard North 26 | rich.north@gmail.com 27 | 28 | 29 | 30 | 31 | 32 | com.kohlschutter.junixsocket 33 | junixsocket-native-common 34 | 2.0.4 35 | 36 | 37 | com.kohlschutter.junixsocket 38 | junixsocket-common 39 | 2.0.4 40 | 41 | 42 | org.slf4j 43 | slf4j-api 44 | 1.7.21 45 | 46 | 47 | org.slf4j 48 | slf4j-simple 49 | 1.7.21 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | maven-compiler-plugin 58 | 3.5.1 59 | 60 | 1.8 61 | 1.8 62 | 63 | 64 | 65 | org.codehaus.mojo 66 | versions-maven-plugin 67 | 2.2 68 | 69 | 70 | maven-scm-plugin 71 | 1.9.4 72 | 73 | ${project.artifactId}-${project.version} 74 | 75 | 76 | 77 | maven-source-plugin 78 | 3.0.0 79 | 80 | 81 | attach-sources 82 | deploy 83 | 84 | jar-no-fork 85 | 86 | 87 | 88 | 89 | 90 | maven-javadoc-plugin 91 | 2.10.3 92 | 93 | 94 | attach-javadocs 95 | deploy 96 | 97 | jar 98 | 99 | 100 | 101 | 102 | 103 | maven-deploy-plugin 104 | 2.8.2 105 | 106 | 107 | deploy 108 | deploy 109 | 110 | deploy 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | UTF-8 120 | 121 | 122 | 123 | scm:git:https://github.com/rnorth/tcp-unix-socket-proxy.git 124 | scm:git:git@github.com:rnorth/tcp-unix-socket-proxy.git 125 | https://github.com/rnorth/tcp-unix-socket-proxy 126 | HEAD 127 | 128 | 129 | 130 | 131 | bintray 132 | https://api.bintray.com/maven/richnorth/maven/tcp-unix-socket-proxy 133 | 134 | 135 | --------------------------------------------------------------------------------