https://github.com/nixcloud/ip2unix
Turn IP sockets into Unix domain sockets
https://github.com/nixcloud/ip2unix
ld-preload linux sockets systemd unix-domain-socket
Last synced: 3 months ago
JSON representation
Turn IP sockets into Unix domain sockets
- Host: GitHub
- URL: https://github.com/nixcloud/ip2unix
- Owner: nixcloud
- License: lgpl-3.0
- Created: 2018-10-12T06:19:22.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-07-07T11:44:38.000Z (11 months ago)
- Last Synced: 2024-07-07T12:54:22.521Z (11 months ago)
- Topics: ld-preload, linux, sockets, systemd, unix-domain-socket
- Language: C++
- Homepage:
- Size: 508 KB
- Stars: 354
- Watchers: 10
- Forks: 10
- Open Issues: 7
-
Metadata Files:
- Readme: README.adoc
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
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[]
== Nameip2unix - 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=]
:5: {man_url}/man7/glob.7.html: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)]]
:man_glob: pass:attributes,quotes[{5}[*glob*(7)]]
:fdname: pass:attributes,quotes[{4}[FileDescriptorName]]
:rulespec: <>
: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)]
:man_glob: pass:quotes[*glob*(7)]
: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[]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
<> is specified, the Unix domain socket will bind or
listen to the file given.ifndef::manmanual[]
[discrete]
== Problem statementA 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 exampleLet'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 <>.
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 Contentstoc::[]
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 print fatal errors which cause program termination.
'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 matches and a
single action of what to do when a match is found. 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.=== Matches
The following matches are available:*in* | *out*::
Match either a server-side socket (`in`), a client-side socket (`out`) or both
if neither `in` nor `out` is specified.*tcp* | *udp* | *stream* | *d*[*ata*]*gram*::
Match the socket type, which currently is either a `stream` (alias: `tcp`)
or `datagram` (aliases: `dgram` or `udp`).*addr*[*ess*]='ADDRESS'::
The IP address to match, which can be either an IPv4 or an IPv6 address.*port*='PORT'[-'PORT_END']::
Match the UDP or TCP port number which for outgoing connections specifies the
target port and for incoming 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-from-unix]]*from-unix*='PATTERN'::
Use an existing Unix domain socket with a filename matching 'PATTERN'. This is
useful for example if a program hardcodes the socket path and you want to
change it.
+
The syntax for 'PATTERN' is similar to {man_glob} and allows the following
wildcards:
+
[horizontal]
*?*;; Match any single character except `/`
***;; Match zero or more characters except `/`
****;; Match zero or more path components, eg. `a/**/z` matches `a/z`,
`a/b/z`, `a/b/cde/z`, `a/b/c/c/d/z` and so on
*[…]*;; Match a single character via a series of either verbatim
characters or ranges, eg. `[a-cijq-s]` matches either `a`, `b`,
`c`, `i`, `j`, `q`, `r` or `s`
*[!…]*;; Same as above, but negates the match
*\X*;; Remove the special meaning of the character 'X',
eg. `\?` literally matches `?`ifndef::without-abstract[]
*from-abstract*='PATTERN'::
Similar to <> but matches 'PATTERN' against an
abstract socket name.
endif::[]=== Actions
[[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 deactivate certain sockets without
causing errors in the application (unlike <>).
+
Technically, this means that we *bind* to a Unix socket using a temporary file
system path and *unlink* it shortly thereafter.*ignore*::
Prevent 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.[[rule-socket-path]]*path*='SOCKET_PATH'::
Convert the socket into a Unix domain socket at the file specified by
'SOCKET_PATH', which is either created during bind or used as the target when
connecting.
+
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 `%`*noremove*::
If this flag is given in conjunction with a <>, the
socket file is not removed when the socket is closed.
+
This works around an issue with more complex programs that spawn subprocesses
or threads without sharing memory or cloning the file descriptor table. In some
scenarios *ip2unix* might be unable to correctly track sockets and might
accidentally remove the socket file too early.ifndef::without-abstract[]
*abstract*='NAME'::
Convert the socket into an abstract namespace Unix domain socket. Unlike the
<> action, the 'NAME' provided here has no connection
with file system pathnames. This also means that file system permissions do not
apply.
+
The placeholders supported in <> are also supported
here.
endif::[]ifndef::without-systemd[]
*systemd*[='FD_NAME']::
Use an existing socket file descriptor provided by systemd instead of creating
a new socket.
+
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::[]== 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 <> (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),
*glob*(7),
*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.https://github.com/mildred/force-bind-seccomp[force-bind-seccomp]::
Very similar in nature but instead of focusing on Unix domain sockets it allows
to replace *bind* arguments for IP sockets. Unlike *ip2unix* however, it uses
seccomp BPF in conjunction with *ptrace*, so it's much more effective if you
have to deal eg. with a statically linked program (see
<> above).
+
The rule matching syntax also is very similar and it also has a way to force
programs to use systemd socket activation. If Unix domain sockets are not what
you want, you might want to give it a try.== 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
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.