Awesome Open Source
Awesome Open Source

ip2unix(1)

ifndef::manmanual[] :doctitle: IP2Unix - Turn IP sockets into Unix domain sockets endif::[] :revdate: November 2018 ifndef::manmanual[] :toc: macro :numbered: :toc-title: endif::[]

ifdef::manmanual[] == Name

ip2unix - Turn IP sockets into Unix domain sockets

== Synopsis

:rule_or_file: pass:attributes,quotes[-r 'RULE' | -f 'FILE'] :rulespec: {{rule_or_file}} [{rule_or_file}]...

[verse] ip2unix [-v...] [-p] {rulespec} 'PROGRAM' ['ARGS'...] ip2unix [-v...] [-p] -c {rulespec} ip2unix -h ip2unix --version

endif::[]

ifndef::manmanual[] :man_url: http://man7.org/linux/man-pages :sysdman_url: https://www.freedesktop.org/software/systemd/man

:1: {man_url}/man8/ld.so.8.html#ENVIRONMENT :2: {man_url}/man2/accept.2.html :3: {sysdman_url}/systemd.socket.html :4: pass:attributes,quotes[{3}#FileDescriptorName=]

:xt_owner_url: {man_url}/man8/iptables-extensions.8.html :xt_owner: pass:attributes,quotes[{xt_owner_url}[iptables owner module]] :socat: pass:attributes,quotes[http://www.dest-unreach.org/socat/[socat]]

:LD_PRELOAD: pass:attributes,quotes[LD_PRELOAD ({1}[ld.so(8)])] :syscall_accept: pass:attributes,quotes[{2}[accept(2)]] :systemd_socket: pass:attributes,quotes[{3}[systemd.socket(5)]] :fdname: pass:attributes,quotes[{4}[FileDescriptorName]] :rulespec: <<rule-specification,Rule specification>> :copy: (C) 2018 aszlig endif::[] ifdef::manmanual[] :LD_PRELOAD: pass:quotes[LD_PRELOAD (see ld.so(8))] :syscall_accept: pass:quotes[accept(2)] :systemd_socket: pass:quotes[systemd.socket(5)] :fdname: pass:quotes[FileDescriptorName (see systemd.socket(5))] :rulespec: pass:quotes[RULE SPECIFICATION] :copy: (C) 2018 aszlig endif::[]

:lgpl_url: https://www.gnu.org/licenses/lgpl-3.0.html

ifndef::without-systemd[:systemd_comma: ,] ifdef::without-systemd[:systemd_comma:]

ifndef::without-systemd[:systemd_backslash: ] ifdef::without-systemd[:systemd_backslash:]

ifdef::manmanual[] == Description endif::manmanual[]

ifdef::env-github[] :hydra_url: https://headcounter.org/hydra :image_url: latest-finished/download/1/status.svg :lgtm_img_baseurl: https://img.shields.io/lgtm/grade/cpp/g/nixcloud/ip2unix.svg :lgtm_img_url: pass:attributes[{lgtm_img_baseurl}?logo=lgtm&logoWidth=18] :lgtm_url: https://lgtm.com/projects/g/nixcloud/ip2unix/context:cpp :badge_job_master: pass:attributes[{hydra_url}/job/ip2unix/master/badge] :badge_url_master: pass:attributes[{badge_job_master}/{image_url}] :latest_eval_master: pass:attributes[{hydra_url}/jobset/ip2unix/master] :badge_job_2_0: pass:attributes[{hydra_url}/job/ip2unix/release-2.0.x/badge] :badge_url_2_0: pass:attributes[{badge_job_2_0}/{image_url}] :latest_eval_2_0: pass:attributes[{hydra_url}/jobset/ip2unix/release-2.0.x] image:https://builtwithnix.org/badge.svg["built with nix", link="https://builtwithnix.org/"] image:{badge_url_master}["master build status", link="{latest_eval_master}"] image:{badge_url_2_0}["2.0.x build status", link="{latest_eval_2_0}"] image:{lgtm_img_url}["Language grade: C/C++", link="{lgtm_url}"] endif::env-github[]

Executes a program and converts IP to Unix domain sockets at runtime via {LD_PRELOAD} based on a list of rules, either given via short command line options (see {rulespec}) or via a file containing a list of rules separated via newline. The first matching rule causes ip2unix to replace the current IP socket with a Unix domain socket based on the options given. For example if a <<rule-socket-path,path>> is specified, the Unix domain socket will bind or listen to the file given.

ifndef::manmanual[]

[discrete] == Problem statement

A lot of programs are designed to only work with IP sockets, however very few of them allow to communicate via Unix domain sockets. Unix domain sockets usually are just files, so standard Unix file permissions apply to them.

IP sockets also have the disadvantage that other programs on the same host are able to connect to them, unless you use complicated netfilter rules or network namespaces.

So if you either have a multi-user system or just want to separate privileges, Unix domain sockets are a good way to achieve that.

Another very common use case in nowadays systems is when you're using systemd and want to use socket units for services that don't support socket activation. Apart from getting rid of the necessity to specify explicit dependencies, this is also very useful for privilege separation, since a lot of services can be run in a separate network namespace.

The systemd use case is also useful even when not using Unix domain sockets in socket units, since it allows to add IP-based socket activation to services that don't support it.

[discrete] == Short example

Let's say you have a small HTTP server you want to make available behind a HTTP reverse proxy.

[source,sh-session]

$ ip2unix -r path=/run/my-http-server.socket my-http-server

This will simply convert all IP sockets to the Unix domain socket available at /run/my-http-server.socket. If you use a web server like https://nginx.org/[nginx], you can use the following directive to connect to that socket:

[source,nginx]

proxy_pass http://unix:/run/my-http-server.socket;

More examples can be found further below in section <<examples,Examples>>.

A short summary of all the options is available via ip2unix --help or man ip2unix if you want to see all the details and options available.

ifndef::manmanual[] [discrete] = Table of Contents

toc::[] endif::[]

== Build from source

See link:INSTALL.adoc[INSTALL.adoc] for information on how to build and/or install ip2unix on your system.

endif::[]

ifdef::manmanual[]

== Options

-c, --check:: This is to validate whether the given rules are correct and the program just prints all validation errors to stderr and exits with exit code 0 if validation was successful and 1 if not.

-h, --help:: Show command line usage and exit.

--version:: Show version information and exit.

-p, --print:: Print out the rules that are in effect in a tabular format. If you do not want to run the 'PROGRAM', you can use the -c option to exit after printing the rules.

-r, --rule='RULE':: A single rule for one particular socket to match. Can be specified multiple times to add more rules.

-f, --file='FILE':: Read rules from 'FILE', which contains a newline-separated list of rules as specified via -r. Empty lines as well as lines starting with # are skipped. Whitespace characters at the beginning of each line are stripped as well.

-v, --verbose:: Increases the level of verbosity, according to the following table:

'FATAL' (default);; Only prints fatal errors that causes the program to terminate. 'ERROR' (-v);; Also print errors that are recoverable. 'WARNING' (-vv);; Also print messages that might indicate possible problems. 'INFO' (-vvv);; Also print informational messages about ip2unix behavior. 'DEBUG' (-vvvv);; Also show messages about ip2unix internals along with source information. 'TRACE' (-vvvvv);; Print every log message possible.

endif::[]

== Rule specification

Arguments specified via -r contain a comma-separated list of either flags or options. If a value contains a comma (,), it has to be escaped using a backslash (\) character. If you want to have a verbatim backslash character just use two consecutive backslashes instead.

The following flags are available:

in | out:: Whether this rule applies to a server-side socket (in), a client-side socket (out) or both if neither in nor out is specified.

tcp | udp:: Specifies the IP type, which currently is either tcp for TCP sockets, udp for UDP sockets or if it is not defined it matches both UDP and TCP sockets.

ifndef::without-systemd[] systemd[='FD_NAME']:: Use the socket passed along via file descriptor by systemd instead of creating one. + An optional file descriptor name ('FD_NAME') can be specified to distinguish between several socket units. This corresponds to the {fdname} systemd socket option. endif::[]

[[reject]]reject[='ERRNO']:: Reject calls to connect and bind with EACCES by default or the 'ERRNO' specified either via name or as an integer.

[[blackhole]]blackhole:: Turn the socket into a Unix domain socket but do not make it available for clients to connect. This is useful to deactive certain sockets without causing errors in the application (unlike <<reject,reject>>). + Technically, this means that we bind to a Unix socket using a temporary file system path and unlink it shortly thereafter.

ignore:: Prevents a socket from being converted to a Unix domain socket if this is set. This is useful to exempt specific sockets from being matched when another rule matches a broad scope.

These options are available:

addr[ess]='ADDRESS':: The IP address to match, which can be either an IPv4 or an IPv6 address.

port='PORT'[-'PORT_END']:: UDP or TCP port number which for outgoing connections specifies the target port and for incomping connections the port that the socket is bound to. + If a range is specified by separating two port numbers via -, the given range is matched instead of just a single port. The range is inclusive, so if 2000-3000 is specified, both port 2000 and port 3000 are matched as well.

[[rule-socket-path]]path='SOCKET_PATH':: The path to the socket file to either bind or connect to. + Placeholders are allowed here and are substituted accordingly: + [horizontal] %p;; port number or unknown if not an IP socket %a;; IP address or unknown if not an IP socket %t;; socket type (tcp, udp or unknown if it's neither a stream nor datagram socket) %%;; verbatim %

== Rule matching behaviour

Each rule is matched in the specified order and the first socket (regardless of specificity) that matches is either turned into a Unix domain socket, blackholed, rejected or ignored depending on the action specified.

If a listening socket is matched by the same rule multiple times, subsequent sockets are automatically <<blackhole,blackholed>> (that is, deactivated without the application noticing). The reason for doing this is that it requires fewer rules for common things, such as for example handling services that bind to both IPv4 and IPv6 addresses.

Let's say we have someprogram, which binds to +127.0.0.1:1234+ and +[::1]:1234+ in that order. All we need to do here is match on port 1234 and only the first (+127.0.0.1:1234+) socket will actually bind to +/foo/bar+, the second (+[::1]:1234+) will be blackholed and is not reachable:

[source,sh-session]

$ ip2unix -r in,port=1234,path=/foo/bar someprogram

Note that this is only the case if both end up using the same socket path. If instead something like this is used, none of the two sockets is blackholed:

[source,sh-session]

$ ip2unix -r in,port=1234,path=/foo/bar-%a someprogram

This will result in two sockets:

. +/foo/bar-127.0.0.1+ for the socket originally binding to +127.0.0.1:1234+. . +/foo/bar-::1+ for the socket originally binding to +[::1]:1234+.

The reason we blackhole subsequent sockets that lead to the same part is to make the common case less verbose to express.

If we would not blackhole the socket and the matcher would simply fall through to the next rule, the following would be required to achieve the same behaviour that we have in the first example:

[source,sh-session]

$ ip2unix -r in,port=1234,path=/foo/bar -r in,port=1234,blackhole someprogram

== Examples

=== Simple HTTP client/server

The following command spawns a small test web server listening on /tmp/test.socket:

[source,sh-session]

$ ip2unix -r in,path=/tmp/test.socket python3 -m http.server 8000

This connects to the above test server listening on /tmp/test.socket and should show a directory listing:

[source,sh-session]

$ ip2unix -r out,path=/tmp/test.socket curl http://1.2.3.4/

=== More complicated example

For example the following could be put into a file given by the -f command line argument:


out,port=53,ignore out,tcp,path=/run/some.socket in,addr=1.2.3.4,path=/run/another.socket in,port=80,address=abcd::1,blackhole in,port=80,reject=EADDRINUSE ifndef::without-systemd[] in,tcp,port=22,systemd=ssh endif::without-systemd[]

Each line corresponds to a single rule, that is processed in order of appearance and the above example would result in the following:

. All outgoing connections to port 53 (no matter if it's TCP or UDP) will not be converted into Unix domain sockets. . This rule will redirect all TCP connections except to port 53 (see above) to use the Unix domain socket at /run/some.socket. . Matches the socket that listens to any port on the IPv4 address 1.2.3.4 and instead binds it to the Unix domain socket at /run/another.socket. . The application may bind to the IPv6 address abcd::1 on port 80 but it will not receive any connections, because no socket path exists. . Trying to bind to port 80 on addresses other than abcd::1 will result in an EADDRINUSE error. ifndef::without-systemd[] . Will prevent the TCP socket that would listen on port 22 to not listen at all and instead use the systemd-provided file descriptor named ssh for operations like {syscall_accept}. endif::[]

The same can be achieved solely using -r commandline arguments:

[source,sh-session] [subs="attributes"]

$ ip2unix -r out,port=53,ignore
-r out,tcp,path=/run/some.socket
-r in,addr=1.2.3.4,path=/run/another.socket
-r in,port=80,address=abcd::1,blackhole
-r in,port=80,reject=EADDRINUSE {systemd_backslash} ifndef::without-systemd[] -r in,tcp,port=22,systemd=ssh endif::without-systemd[]

== Limitations

  • The program uses {LD_PRELOAD}, so it will only work with programs that are dynamically linked against the C library. Using ip2unix on statically linked executables or on executables that don't use the socket family functions of the C library (like Go programs) will not work at the moment.

  • If a client which is already using Unix datagram sockets sends packets via sendto or sendmsg to a socket provided by ip2unix without binding first, ip2unix is not able to identify the peer and will subsequently reject the packet. This is not the case when using ip2unix itself on the the client side and it also does not seem to be very common as the author so far did not find such an application in the wild.

However, if this really is an issue to you, the recommended workaround is either to use ip2unix to wrap the client (if it supports IP sockets) or fix the server to natively use Unix domain sockets.

ifdef::manmanual[]

== See also

accept(2), bind(2), connect(2), listen(2), recvfrom(2), recvmsg(2), sendmsg(2), sendto(2), socket(2), unix(7){systemd_comma} ifndef::without-systemd[systemd.socket(5)]

endif::[]

ifndef::manmanual[]

== Frequently Asked Questions

=== Isn't this functionality already covered by {socat}?

The {socat} tool has a very different purpose: It is essentially a way of connecting streams between different address types. Apart from a myriad of options, it supports quite a lot of address types and it's really good at providing great flexibility to connect bidirectional streams.

However what it doesn't do is change the behaviour of the target application, which is what ip2unix does.

For example, if you have an application that listens to TCP port 1234, you can use {socat} to create a Unix domain socket listening on foo.sock and proxying all requests to TCP port 1234:

[source,sh-session]

$ socat UNIX-LISTEN:foo.sock,fork TCP:localhost:1234

Here, the application will still listen to TCP port 1234, but we now have two additional sockets (Unix inbound and TCP/IP outbound) we need to take care of.

ip2unix on the other side redirects the C library calls of the application in question, so that TCP port 1234 will not be bound in the first place and instead the application directly binds to a Unix domain socket.

This not only allows for better privilege separation (because local users need file system access permissions to the socket file) but also involves less overhead since only one socket (the listening socket of the application itself) is used.

=== Yes, but can't this still be done via {socat} and the {xt_owner}?

Of course you could use iptables to only allow access to the user running socat. But again, this still needs additional sockets and also still doesn't decrease the attack surface by a large margin (eg. there could be race conditions in loading iptables rules or simply human error specifying the rules).

Not binding to an IP socket in the first place however gets rid of that attack surface, since you can't attack things that don't exist.

== Similar projects

https://cwrap.org/socket_wrapper.html[socket_wrapper]::

The goal is a different one here and its main use is testing. Instead of using rules, socket_wrapper turns all of the IP sockets into Unix sockets and uses a central directory to do the mapping. + Containing all Unix sockets into one directory has the nice effect that it is easy to map any address/port combination to Unix sockets. While this is way easier to implement than our approach it has the drawback that everything is contained and no IP communication is possible anymore.

== Thanks

Special thanks to the https://nlnet.nl/[NLnet foundation] for sponsoring the initial work on this project.

endif::[]

ifdef::manmanual[]

== Author

Written by aszlig [email protected]

endif::[]

== Copyright

Copyright {copy}. License LGPLv3: GNU LGPL version 3 only <{lgpl_url}>.

This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.


Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
c-plus-plus (17,646
linux (2,333
systemd (51
sockets (34
gnu (20

Find Open Source By Browsing 7,000 Topics Across 59 Categories