(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>filepara redirecionar fdnparafile(aberto somente para escrita,né1se omitido). -
n<filepara redirecionar fdnparafile(aberto apenas para leitura,né0se omitido). -
n<>filepara redirecionar fdnparafile(aberto para leitura e escrita). - quando especificado como
n>&moun<&moun<>&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 fd3do shell e escreve no fd1do shell.) -
cat >&3(que lê do fd0do shell e escreve no fd3do 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!