IP2UNIX(1)

πŸ“¦
version 2.2.1 Β· 2 versions
, in opensuse-leap 16.0

πŸ’… IP2UNIX(1) β€” the ip2unix manual, bestie edition πŸ¦‹

okay so welcome to IP2Unix Manual, aka the page where we make IP sockets cosplay as Unix domain sockets. this is ip2unix and she is iconic, no cap. let's get into it ✨


🌸 NAME 🌸

ip2unix β€” turn IP sockets into Unix domain sockets. literally that's the whole gig. slay.


⭐ SYNOPSIS ⭐

ip2unix [-v...] [-p] {-r RULE | -f FILE} [-r RULE | -f FILE]... PROGRAM [ARGS...]
ip2unix [-v...] [-p] -c {-r RULE | -f FILE} [-r RULE | -f FILE]...
ip2unix -h
ip2unix --version

πŸŽ€ DESCRIPTION πŸŽ€

so what she does is: she executes a program and converts IP sockets into Unix domain sockets at runtime via LD_PRELOAD (go peek at ld.so(8) if you're curious, iykyk). she does this based on a list of rules β€” either you hand them over with the short command line options (check out RULE SPECIFICATION below, it's giving whole menu) or you give her a file with a list of rules separated by newlines.

the first matching rule is the main character: it causes ip2unix to replace the current IP socket with a Unix domain socket based on whatever options you gave. for example, if a path is specified, the Unix domain socket will bind or listen to the file you gave it. periodt. πŸ’…


✨ OPTIONS ✨

  • -c, --check β€” this one's just here to validate whether the rules you gave are correct 🫢 the program just prints all validation errors to stderr and exits with exit code 0 if validation was successful, and 1 if not. that's it. that's the vibe check.
  • -h, --help β€” show command line usage and dip. bye bestie πŸ‘‹
  • --version β€” show version information and exit. say hi to the version number for me πŸ’–
  • -p, --print β€” prints out the rules that are in effect in a tabular format πŸ”₯ and listen: if you do not want to actually run the PROGRAM, you can pair this with the -c option so she exits right after printing the rules. very demure, very mindful.
  • -r, --rule=RULE β€” a single rule for one particular socket to match. can be specified multiple times to add more rules. stack 'em up 🧚
  • -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 πŸ™ˆ and whitespace characters at the beginning of each line are stripped too. tidy queen.
  • -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. she's showing you everything. no secrets. πŸ’€

πŸ‘‘ 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. and here's the important bit: if a value contains a comma (,), it has to be escaped using a backslash (\) character. and if you want a verbatim backslash character, just use two consecutive backslashes instead. escape responsibly bestie 😌

πŸ¦‹ 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. options, options~
  • tcp | udp | stream | d[ata]gram β€” match the socket type, which currently is either a stream (alias: tcp) or datagram (aliases: dgram or udp). pick your fighter ✨
  • 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. generous queen πŸ‘‘

  • 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. absolute lifesaver.

the syntax for PATTERN is similar to glob(7) and allows the following wildcards:

  • ? β€” 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 ?
  • from-abstract=PATTERN β€” similar to from-unix but matches PATTERN against an abstract socket name. abstract bestie πŸ’œ

🌈 Actions 🌈

  • reject[=ERRNO] β€” reject calls to connect and bind with EACCES by default, or the ERRNO specified either via name or as an integer. we're saying no, but politely-ish.
  • 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 reject).

technically, this means that we bind to a Unix socket using a temporary file system path and unlink it shortly thereafter. poof, gone, but make it seamless ✨

  • ignore β€” prevent a socket from being converted to a Unix domain socket if this is set. useful to exempt specific sockets from being matched when another rule matches a broad scope. the "not you, bestie" of actions.
  • 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:

  • %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 path, 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. 😭 so yeah, this is your safety net.

  • abstract=NAME β€” convert the socket into an abstract namespace Unix domain socket. unlike the path action, the NAME provided here has no connection with file system pathnames. this also means that file system permissions do not apply. no rules, no limits, she's free πŸ’«

the placeholders supported in path are also supported here. 🫢

  • 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 FileDescriptorName (see systemd.socket(5)) systemd socket option. teamwork makes the dream work 🀝


πŸ”₯ 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. first come, first served, no cap.

if a listening socket is matched by the same rule multiple times, subsequent sockets are automatically 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. efficiency slay.

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:

$ 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:

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

this will result in two sockets:

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

the reason we blackhole subsequent sockets that lead to the same path is to make the common case less verbose to express. we love a shortcut πŸ’…

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:

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

🌸 EXAMPLES 🌸

okay bestie, this is the part where it stops being theory and starts being iconic πŸ’… let's actually run this thing:

πŸŽ€ Simple HTTP client/server

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

$ 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:

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

like yes, we are wrapping a whole http server in a vibe check and it works ✨


πŸ¦‹ 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
in,tcp,port=22,systemd=ssh

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

  1. All outgoing connections to port 53 (no matter if it’s TCP or UDP) will not be converted into Unix domain sockets.
  2. This rule will redirect all TCP connections except to port 53 (see above) to use the Unix domain socket at /run/some.socket.
  3. 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.
  4. 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.
  5. Trying to bind to port 80 on addresses other than abcd::1 will result in an EADDRINUSE error.
  6. It stops the TCP socket that would listen on port 22 from listening at all, and instead uses the systemd-provided file descriptor named ssh for operations like accept(2).

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

$ 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 \
          -r in,tcp,port=22,systemd=ssh

ngl that multi-line command is giving "group project but everyone actually did their part" 🌈


πŸ’€ LIMITATIONS πŸ’€

okay bestie, this is the fine print and it is not a vibe. read it before you get mad at the program.
  • The program uses LD_PRELOAD (see ld.so(8)), 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 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.

like, the workaround is right there 🫢 take it, bestie.


🧚 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.socket(5)

the whole friend group is referenced, we love to see it πŸ’–


πŸ‘‘ AUTHOR πŸ‘‘

Written by aszlig <aszlig@nix.build>


Copyright (C) 2018 aszlig. License LGPLv3: GNU LGPL version 3 only https://www.gnu.org/licenses/lgpl-3.0.html.

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


πŸ’– This masterpiece is from IP2Unix 2.2.1, dated November 2018 β€” periodt. 🫢

πŸ“¦ where this page comes from