Add SOCKS support (for tunneling CA via ssh)

Bug #1012788 reported by till
This bug affects 1 person
Affects Status Importance Assigned to Milestone

Bug Description

The attached patch implements a new function (as promised at the collaboration
meeting last spring)

int epicsConnectViaProxy(SOCKET, const struct sockaddr, osiSocklen_t);

which is 'plug-in' compatible with ordinary 'connect'. However, if a SOCKS proxy
server is found for the destination address then this routine connects to
the proxy server and requests the proxy to establish the connection to the
real destination.

The proxy selector can itself be exchanged (global function pointer variable);
the default implementation simply checks if the environment variable EPICS_SOCKS_PROXY
is set to a valid address:
<host> [ ':' <port> ]
If it is then <host>:<port> (port defaults to 1080) is used as the proxy (for
all destinations). If no proxy is defined (i.e., if the proxy selector routine
returns NULL) then a direct connection is established. I.e., without a
proxy being defined 'epicsConnectViaProxy()' behaves exactly like ordinary

The CA client library has been modified (by the patch) to use epicsConnectViaProxy()
instead of ::connect().

This feature is mainly of interest with SSH's SOCKS proxy server feature. It
then becomes easy to tunnel CA connections. E.g.,:

ssh -D1080

caget SOME_PV

Where PV must be hosted on 'some-ioc'.

The patch is even more useful in combination with the 'caxy' program
which can proxy UDP search requests and CA beacons...

Tags: wishlist
Revision history for this message
till (till-straumann) wrote :
Revision history for this message
Jeff Hill (johill-lanl) wrote :

Hi Till,

Thanks for your efforts!

o I can imagine two programs using this facility in the same program simultaneously. For example, in the IOC
we might have both ca and asyn using it and need to configure the proxy address independently for both entities.

Perhaps what you should do is change the API so that the cac class constructor creates a proxy
create agent using a global factory that requires the EPICS
environment variable as its sole parameter. That way we can have EPICS_CA_SOCKS_PROXY and
ASYN_SOCKS_PROXY (or whatever the correct name is with asyn) in use at the same time. This approach would also
address my second bullet below. Once we we have created a proxy connect agent them it can be invoked in
tcpiiu.cpp each time that we connect w/o needing to pass in parameters.

(void) pProxyConnect->connect(); /* exception thrown on failure */

Of course the above API is C++. You can of course get the same thing in C as follows (if you prefer that).

extern NetworkProxy * epicsNetworkProxyCreate ( const ENV_PARAM * );
extern int epicsNetworkProxyConnect ( NetworkProxy * );
extern void epicsNetworkProxyDestroy ( NetworkProxy * );

Of course we would probably need to pass in the address of the connect proxy when creating a tcpiiu, and
cache that address within tcpiiu, but only ~cac would destroy the connect proxy.

o The epicsDefaultSocksProxySelector API returns a pointer to a shared structure which isn't released by the caller. This can cause concurrency issues later on if this structure was changing and multiple threads are using it simultaneously? Its a little more overhead but IMHO a better option would be just to return the structure by value, or better yet have the caller pass the address of the destination structure for the proxy's address. Furthermore, if the epicsSocksProxySelect just wraps back the destination address, which is passed in, then when there is no proxy we can simplify the code below, and have no if test to see if the returned structure is nill and also only one call to connect. The ca client code currently uses the osiSockAddr union defined in osiSock.h to wrap socket addresses so that we can have some flexibility wrt to different address structures in the future.

+ if ( ! ( proxy = epicsSocksProxySelect(addr, &desVers) ) ) {
+ return connect( sockfd, addr, addrlen );
+ }
+ /* Connect to proxy */
+ if ( (rval = connect( sockfd, proxy, sizeof(*proxy)) ) ) {
+ epicsSocketConvertErrnoToString(buf, sizeof(buf));
+ errlogPrintf("epicsConnectViaProxy() -- Unable to connect to proxy: %s\n", buf);
+ return rval;
+ }

Revision history for this message
till (till-straumann) wrote :
Download full text (3.8 KiB)

Hi Jeff.

Thanks for your comments. If you don't mind, let's discuss the details

Regarding your first bullet:
I don't think that having 'application-specific' proxies makes much sense.
After all, proxified connections exist at the TCP/transport level and the
application should be entirely agnostic.
I believe what you mean is that we should have *destination*-specific
proxies (which is what almost all other software dealing with SOCKS
and multiple proxies does: proxifiers like tsocks, dante, the eclipse
proxy selector etc. do it that way).

After all: if ASYN and CA happen to want to connect to the same subnet
then they can use the same proxy. If ASYN talks to a different subnet
then it should use a different proxy.

That is what the proxy selector is for. It should select a proxy for
the presented destination based on a set of rules.

E.g., you could have rules

asyn-subnet/24:1000-9999 -> proxy1
ca-subnet/22 -> proxy2

and any connection to 'asyn-subnet' with netmask and within
port range 1000-9999 would go through 'proxy1'

any connection to 'ca-subnet' with netmask (any port)
would go via 'proxy2'.

In practice, the 'proxies' are probably always SSH ports on 'localhost'

ssh -D1080 proxy1
ssh -D1081 proxy2

with the 'proxy1' and 'proxy2' settings in the 'rules' of the above
example then being 'localhost:1080' and 'localhost:1081', respectively.

The current 'default' proxy selector doesn't allow you to do that.
It just lets you define a single proxy which is used for all connections
or none at all.

However, it would not be not very hard to improve the default selector
(and already possible with the current patch for you to provide your
own selector).

This brings us to the second bullet:
The pointers returned by the proxy selector really point to static
objects which are created when the proxy selector parses it's rules
(the current default selector is initialized once and creates a
struct sockaddr using the info from EPICS_SOCKS_PROXY).

Therefore, I do not see any concurrency problems and thus
no need for locking or copying values. The objects referenced
by the pointer returned by the proxy selector are strictly
read-only and guaranteed to never change.

Finally - while I see that you can get away with a single
call to 'connect()' you still need a test (which then would be
a little bit more complicated) because you need to
skip the SOCKS negotiation if you have a direct connection.

Regarding the use of osiSockAddr - it would of course be
the first choice to use by the proxy selector but it would
require an OSI version of 'connect'. Since 'epicsConnectViaProxy()'
should be a 'plug-in' replacement for 'connect' the type of
object chosen is 'struct sockaddr'.

Summarizing, I still believe that the current API makes sense
(it is also not really my invention but as I said: several packages
implementing SOCKS use a similar approach) but I can see
that a more powerful (default) proxy selector could be desirable
(but I IMHO the current implementation is still much better
than nothing).

What would you think about an implementation which
would understand


rules: rule [ ',' ...


Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Till, are you still using/pursuing this work?

Changed in epics-base:
status: New → Incomplete
importance: Undecided → Wishlist
assignee: nobody → mdavidsaver (mdavidsaver)
Revision history for this message
till (till-straumann) wrote : Re: [Bug 1012788] Re: Add SOCKS support (for tunneling CA via ssh)

On 12/03/2015 02:14 PM, mdavidsaver wrote:
> Till, are you still using/pursuing this work?
I'm using it all the time (to tunnel into slac). I'm not doing
anything actively, iirc the patch is 'stable'.
> ** Changed in: epics-base
> Status: New => Incomplete
> ** Changed in: epics-base
> Importance: Undecided => Wishlist
> ** Changed in: epics-base
> Assignee: (unassigned) => mdavidsaver (mdavidsaver)

Revision history for this message
Andrew Johnson (anj) wrote :

I am wondering whether this bug should be closed as "Opinion".

I accept that the patch works and is stable, but I don't recall anyone else calling for it to be merged or asking for this functionality in Base (it is currently possible to ssh-tunnel CA connections to individual IOCs by setting EPICS_CA_NAME_SERVERS). We would require some additional work on documentation and self-test code before we could accept it for a Base release if a site does come forward and ask for it.

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.