(Eu não tenho um dispositivo /dev/tcp
no meu sistema; entretanto, bash
parece ter algum tratamento interno para ele, alocando um soquete tcp conectado à seguinte /host/port
part.)
Assim, o comando ssh proxy executa um shell com segurança em gatewayserver
, o que faz:
exec 3<>/dev/tcp/targetserver/22
i.e. Anexe um soquete no filedescriptor 3 (conectado ao targetserver
/ port
). Então:
cat <&3 & cat >&3; kill $!
que é uma maneira de ter um redirecionamento bidirecional (usando dois processos separados) entre os dois grupos de descritores 0
(entrada) e 1
(saída) e filedescriptor 3
(entrada e saída). O kill $!
está lá para matar o processo de segundo plano cat <&3
depois que o outro processo cat >&3
retornou.
Tudo isso é apenas um equivalente de um padrão:
ProxyCommand ssh gatewayserver "tcpconnect targetserver port"
usando os recursos /dev/tcp
(ou bash
) em vez do comando tcpconnect
.
Mais alguns detalhes:
O comando proxy usado pelo ssh está lá para definir como se conectar ao host remoto targetserver
(a criptografia não é realmente necessária lá porque o protocolo ssh será usado sobre este canal). Em nosso caso, queremos estabelecer a conexão com esse host de destino através de gatewayserver
(provavelmente porque um firewall impede a conexão com targetserver
diretamente.
Então, um processo
ssh gatewayserver 'exec 3<>/dev/tcp/targetserver/22; cat <&3 & cat >&3;kill $!'
é iniciado e:
- filedescriptor (fd)
1
(a.k.a. saída padrão) será usado por um cliente ssh para enviar dados para o host de destino. - fd
0
(a.k.a. entrada padrão) será usado por um cliente ssh para ler dados do host remoto.
O ssh gatewayserver
é usado para se conectar primeiro ao gateway, que será o primeiro salto. Um novo shell é iniciado neste host e essa instância de ssh
retransmitirá fd 0
/ fd 1
do processo no host de origem para fd 0
/ fd 1
do shell em execução no host do gateway . O comando executado por este shell é:
exec 3<>/dev/tcp/targetserver/22; cat <&3 & cat >&3;kill $!
O exec
sem um comando não executará nada, apenas aplicará os seguintes redirecionamentos ao próprio shell. Os redirecionamentos habituais são:
-
n>file
para redirecionar fdn
parafile
(aberto somente para escrita,n
é1
se omitido). -
n<file
para redirecionar fdn
parafile
(aberto apenas para leitura,n
é0
se omitido). -
n<>file
para redirecionar fdn
parafile
(aberto para leitura e escrita). - quando especificado como
n>&m
oun<&m
oun<>&m
, o fdn
é redirecionado para o arquivo anteriormente apontado por fdm
.
Aqui, o seguinte é usado:
exec 3<>/dev/tcp/targetserver/22
Isso redirecionará um fd 3
recém-criado para algum arquivo muito especial /dev/tcp/targetserver/22
(que não é realmente um arquivo, mas algo que o bash entende nativamente). Aqui, o bash cria um socket (arquivo especial que usa o protocolo tcp) para falar com targetserver
na porta 22
(onde esperamos encontrar um sshd
server), e este arquivo está aberto (read & write) no fd 3
.
Agora precisamos "bombear" os dados em fd 0
(dados do cliente) e enviá-los para fd 3
(conectado ao servidor de destino).
Nós também precisamos assegurar a comunicação para trás "bombeando" os dados em fd 3
e enviando-os de volta para fd 1
(resultado para o cliente).
Essas duas "bombas" são configuradas usando dois processos cat
:
-
cat <&3
(que lê do fd3
do shell e escreve no fd1
do shell.) -
cat >&3
(que lê do fd0
do shell e escreve no fd3
do shell.)
Ambos cat
s devem ser executados em paralelo, portanto, é necessário ter um plano de fundo. Aqui, queremos que o que lê em fd 0
(que provavelmente será um tty) seja o que resta em primeiro plano. Isso dá:
cat <&3 & #run in the background
cat >&3; kill $!
O kill $!
está lá para eliminar o processo em segundo plano ( $!
expande para o pid do último processo em segundo plano). Desta forma, quando o cliente desliga, o processo em primeiro plano termina, o kill é executado e o último processo também é finalizado.
É isso! Nós fizemos a ponte:
origin host —(ssh)→ gateway —(pumps+socket)→ targetserver(port 22)
Um ssh user@targetserver
no host origin poderá se conectar ao host target por meio dessa ponte!