├── 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.