This is terribly incomplete for now and some things noted below not yet work.
However the examples work as advertized.
| master | dev |
|---|---|
Shell tool to pass FDs via Unix domain sockets.
Of course you can archive a similar result completely different.
The important thing here is, that passfd can terminate after the FDs are passed,
such that the FD then can be handled by the program in question directly
without some intermediate data-pumping process.
However this works for programs which can receive or send FDs like:
ssh: Can receive FDs fromProxyCommand.netcat: Can send FDs (iE. when used asProxyCommand)
In contrast to special purpose programs like above, passfd can act in all directions:
- receive FDs from itself or other programs like
netcat - send FDs to itself or other programs like
ssh - Fork programs with some passed FDs (like pipes).
- Create some socket and pass it somewhere else (however
netcathas much more options for this)
git clone https://github.com/hilbix/passfd.git
cd passfd
make
sudo make install
Then to see a short Usage:
passfd
To see the long Usage:
passfd h
passfd also solves the problem where you cannot pass an FD via exec() for some reason.
For example if "parent" wants to pass something to "grandchild", but "child" closes all FDs in between.
passfd has following modes:
-
Mode "direct" means: Create a socket and pass this to some already open other unix socket
-
Mode "in" means: Send one or more FDs of the current process to a (new) unix socket
-
Mode "pipe" means: Receive one or more FDs from (new) socket and pass them to (open) socket
-
Mode "out" means: Receive one or more FDs and exec a program which then can use them
-
Mode "x" means: Start of bidirectional pipes (not yet implemented)
-
Mode "y" means: Middle of bidirectional pipes (not yet implemented)
-
Mode "x" means: End of bidirectional pipes (not yet implemented)
passfd modifiers mode socket fds.. -- command args..
modifiers (unused: bgjm)
alikeaccept: create new listening socket, which must not existblikebackground: do not wait for termination of commandllikelisten: create listening socket, which is overwritten if it already existsclikeconnect: connect to socketttimeout in ms (foraandc). Default: 10000msrlikeretryoptionally followed by a number of retries: retry if something fails. Defaults to the number ofrs seen. -1 means foreverwlikewaitoptionally followed by numbersmaxbackoffmsincrementlimit. Waiting when socket setup fails. Defaults to 1000 10 0 20 2000slikesuccess: execute command whenpassfdterminates successfullyelikeerror: execute command on error (forocommand then is always executed)flikefork: fork command after socket established (before incoming connect or after successful connection)ulikeusefollowed by a list of FDs: use those FDs (compare:read -u) to pass the other FDs, default: 0 (this is forp)kkeep passed FDs open for forked command, too (this is fori)venable verbose mode (dumps status to stderr)nlikenonce: (security) use environment variable$PASSFD_NONCEfor socket communicationqlikequiet: do not set/modifyPASSFD_environment variables on forked program- missing: use the defaults for the given mode
mode:
dlikedirectsocket: create new socket, exec cmd with FD, if cmd ok pass socket touseilikeintosocket: create new socket, wait for connection to socket, remove socket, pass FDs, terminateolikeoutof socket: connect to socket, receive FDs, exec command with args and received FDs as givenplikepipe: connect to socket, receive FDs, sort FDs by number, pass FDs to FDs given byuse
mode (future, perhaps):
x/y/zstart/mid/end some bidirectional pipe- NOT YET IMPLEMENTED
- This creates some temporary abstract unix domain sockets for communication and sends their name and some NONCE over the pipe
xexpects some otherpassfd yorpassfd zon STDOUTyexpects somepassfd xorpassfd yon STDIN and somepassfd yorpassfd zon STDOUTzexpects somepassfd xorpassfd yon STDIN
socket:
$ENVto access the given environment variable- A number, which refers to an open FD. Note with
athis doesaccept(), withlthis doeslisten()+accept() -is the same as0@abstractfor abstrat Unix sockets (Linux only)- Path. Use
./for relative files which start with a digit or@. [host]:port[@bind](only valid for moded)@bindis half ignored currently
fds:
- Must be numeric
- You must give the same number (=count) of FDs on both sides!
- If no FD is given, it defaults to 0
$ENVworks here, too, the environment variable can be a space separated list
--:
- Is always ignored
- But must always be present if
commandfollows - Even if command would not be ambiguous here
command args..:
iwithaorldefaults tof(this makes sure the socket exists)iwithcdefaults tosodefaults tospdefaults tof.In future, cmd gets passed a socketpair which receives the FDs
Fun Facts:
retry(without number) increments the number of retries 2 times (as 'retry' includes 2r)- A
retryis, whenwhits the maximum or the limit (max total time). - Backoff is added to
mseach loop, then increment is added to backoff. - Retry numbers can go below 0.
- Waiting is capped at 128000ms if
msgoes higher.
Look into
Test.shfor another example for I/O
ssh -o ProxyUseFDPass=yes -o 'ProxyCommand=passfd d %h:1234 1 -- echo hello world' somehost
On somehost you can run (and diagnose) sshd like this:
socat -d -d tcp-listen:1234,reuseaddr,fork system:'set -x; read HW && [ "hello world" = "$HW" ] && exec sshd -d -D -i'
Only those who know the parole (hello world) can attach to this ssh ..
PASSFDSOCK="$(mktemp)" passfd l i \$PASSFDSOCK 4 -- ssh -o ProxyUseFDPass=yes -o 'ProxyCommand=passfd p $PASSFDSOCK' user@example.com
- FD 4 must be some open socket, connected to some
sshd, of course. - As
sshcloses all FDs from the outside, a temporary socket is used to pass the FD - The name of the socket can be passed using the environment
- Here a socket from the filesystem is used, but you can use abstract Unix Domain Sockets (starting with
@) if you like
You can test this locally with bash as follows:
4<>/dev/tcp/127.0.0.1/22 PASSFDSOCK="$(mktemp)" passfd l i \$PASSFDSOCK 4 -- ssh -o ProxyUseFDPass=yes -o 'ProxyCommand=passfd p $PASSFDSOCK' $LOGNAME
ProxyUseFDPass yes
ProxyCommand /usr/local/bin/passfd u 0 b s p - 0 -1 -- /usr/bin/ssh user@jumphost -W %h:%p
You might think, this can be done with ProxyCommand directly. Right. However think of a script which is able to pass direct sockets, too. Like this:
#!/bin/bash
case "$1" in
(127.0.0.1:*) exec /usr/local/bin/passfd v direct "$1:$2";;
(*) exec /usr/local/bin/passfd v u 0 s p - 0 -1 -- /usr/bin/ssh -f -N user@jumphost -W "$1:$2";;
esac
Why it is a bit cryptic and what this does:
venables verbose mode. (You do not need this. Everything else is needed.)u 0tellspto pass the "received" FDs (here: 1) to the parent via FD 0 (STDIN)btells to run the command (/usr/bin/ssh user@jumphost) in background- This is needed because the parent
sshwaits for the child process to terminate after passing the FD - Not running in background would hence make the parent
sshwait forever
- This is needed because the parent
sis the first magic to tellpto immediately run thessh user@jumphostas the first (and only) passed in FD- This magic creates a
socketpair()whensis active inpmode (which actually is a hack to do something meaningful)
- This magic creates a
pmeans proxy mode, that is "receive FDs and pass FDs" at the same time-is the second magic and tellspthat it shall read theFDsfrom FD 0.- you can write
0here as well, however with-(STDIN) it is better readable - Usually
pwould read the FDs from this socket (FD 0 here), however in thescase this is a dummy
- you can write
0 -1means to pass thesocketpair()to the command as FD 0 and 1- This only is a bit of magic, because this is a standard feature to duplicate some FD into another FD
--means end of options, command follows- Then the
sshis executed with STDIN and STDOUT connected to thesocketpair()at one side- The other side is passed (due to
u 0) to the parentsshvia the original FD 0.
- The other side is passed (due to
- Instead of
byou can use-f -Nwith the forkedsshto catch errors in the setup phase:- You need
-fbecause the parentsshwaits for the child process to terminate after passing the FD - You need the
-Nto keep the passed FD open aftersshgoes into the background - This way the forked
sshonly comes back with success when the connection is successful
- You need
Note that this is a degenerated case of the p command. Ususally you can have a socket definition where the - sits and read the FDs from this socket.
You also can use the command as a talk script to the socket to open it (perhaps hopping over some loops to reach the real destination).
Note: It is likely that I will change the p command in future. But this functionality will hopefully work in future, too.
-
It is far too unintuitive to use
- Can more and better examples help?
-
socketpair option not yet implemented (except for the
s phack)- If you have any better idea (except
|commandas a socket) please open Issue
- If you have any better idea (except
-
Inverse passing not yet implemented
- Means: pass from open socket to new socket
- I am not sure if this is needed at all
- I have no good idea how to call it
- Can be emulated with:
passfd out - 0 -- passfd in socket 0
-
directmode is limited- It would be very demanding to implement everything which might be needed
- If this is a problem use a wrapper command like
socat(or my toolsocklinger) to do the actual wrapping - However the tool should not
fork(). It should justconnect()and thenexec()passfd - Also it is a bit redundant as
bashsupports something like<>/dev/tcp/IP/port
-
This tool is barely tested
- However the examples work
-
x,y,zare missing -
@bind(from[host]:port@bindnot implemented (ignored)
This is mostly untested for now and proabably not very useful to others today.
Receive FD from some program like nc -F and execute some program with it
- something like
passfd x 1 0 nc -F 127.0.0.1 22 | passfd z -- script - something like
passfd o '|nc -F 127.0.0.1 22' 3 -- script
Pass FD (like STDIN) when forked from some program like busybox nc -e 'passfd ..' to some other open socket (like ssh)
- something like
TMPS="$(mktemp)"; passfd a p "$TMPS" -- busybox nc -e 'passfd i \$TMPS' - or (better):
PASSFD_NONCE= passfd n a p @ -- busybox nc -e 'passfd n i @ - This needs:
- Passing environment variables
PASSFD_xxx @creates temporary Abstract Unix Domain Sockets@connects to Abstract Unix Domain Socket from environment variable$PASSFD_SOCK- NONCEs (created if
PASSFD_NONCEis unset or empty)
- Passing environment variables
Make a bi-directional pipe like producer | passfd x 1 1 -- first program | passfd z -- second program | consumer
- Idea here is that
passfdpasses the socket into the 2nd passfd via the pipe - As pipes do not allow to pass FDs, we must transfer a name, not an FD!
firststill can read on STDIN from producer and bi-directionally communicates withsecondon STDOUTsecondstill can write on STDOUT to consumer and bi-directionally communicates withfirston STDIN- both
passfddo not show up in the pipe (as they were only needed to establish the communication) - hence this looks like
producer | ( first<>second ) | consumer - The latter should scale like for:
producer | ( first<>second<>third ) | consumer- Even with some additional connection of
first<>thirdas follows: p | passfd x 2 1 3 -- 1st | passfd y 2 0 -1 1 -- 2nd | passfd z 0 3 0 -- 3rd | cpassfd x 2 1 3 -- 1st: read in nothing, pass out 2 sockets (S1 S2), execute1st 1<>S1 3<>S2passfd y 2 0 -1 1 -- 2nd: read in sockets (S1 S2), pass out 2 sockets (S2 S3), execute2nd 0<>S1 1<>S3passfd z 0 3 0 -- 3rd: read in sockets (S2 S3), pass out nothing, execute3rd 3<>S2 0<>S3- Note that newly created sockets (S1 S2 on
passfd x, S3 onpassfd y) must be "consumed" locally
- Even with some additional connection of
Fill in some default environment variables for the forked program:
- Everything which might be interesing, like nonces etc. on
x - Suppressed by
q
WTF why?
- Because I need it
Contact? BUG?
- Open issue on GH, eventually I listen.
Contrib? Patch?
- Open PR on GH, eventually I listen.
- Stick to the license!
License?
- This Works is placed under the terms of the Copyright Less License,
see file COPYRIGHT.CLL. USE AT OWN RISK, ABSOLUTELY NO WARRANTY. - Read: Free as in free beer, free speech and free baby.