我有一个在Unix套接字上运行的ssh服务,我有一个本地TCP服务器,我想让它直接指向unix socket的通道。
基本上当我这样做时:
$ ssh root@localhost -p 2000
然后我的本地TCP服务器获取请求并将其传送到Unix套接字,TCP客户端(在这种情况下为ssh)从Unix套接字获取回复。 相关代码:
let running_tunnel debug (tcp_ic, tcp_oc) () =
Lwt_io.with_connection a_unix_addr begin fun (mux_ic, mux_oc) ->
let%lwt _ = some_call with_an_arg
and _ =
(* Some setup code *)
let rec forever () =
Lwt_io.read_line tcp_ic >>= fun opening_message ->
Lwt_io.write_from_string_exactly
mux_oc opening_message 0 (String.length opening_message) >>= fun () ->
Lwt_io.read_line mux_ic >>= fun reply ->
Lwt_io.printl reply >>= fun () ->
Lwt_io.write_line tcp_oc reply >>= fun () ->
forever ()
in
forever ()
in
Lwt.return_unit
end
这种作品。它会被卡住"当我在命令行上调用ssh但我知道我正在获取一些数据,因为另一方的ssh标头是正确的,SSH-2.0-OpenSSH_6.7
。我也得到了我的一面打印出初始ssh握手的更多部分,即我看到这个打印:
??^?W\zJ?~??curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1ssh-rsa,ssh-dss>aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com>aes128-ctr,aes192-ctr,aes256-ctr,chacha20-poly1305@openssh.com?umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1?umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1none,zlib@openssh.comnone,zlib@openssh.co
等,这似乎是正确的。我认为挂起的原因是因为我使用的是Lwt_io.read_line
,所以我尝试了这个:
let rec forever () =
Lwt_io.read tcp_ic >>= fun opening_message ->
Lwt_io.write_from_string_exactly
mux_oc opening_message 0 (String.length opening_message) >>= fun () ->
Lwt_io.read mux_ic >>= fun reply ->
Lwt_io.printl reply >>= fun () ->
Lwt_io.write tcp_oc reply >>= fun () ->
forever ()
in
forever ()
实际上哪个更糟糕,它甚至没有打印出最初的握手。我也尝试过专注的{write,read}_into
...功能,但成效有限。在strace / dtruce下运行我看到最终结果如:
read(0x6, "SSH-2.0-OpenSSH_6.9\r\n\0", 0x1000) = 21 0
write(0x1, "SSH-2.0-OpenSSH_6.9\n\0", 0x14) = 20 0
read(0x7, "\0", 0x1000) = -1 Err#35
write(0x7, "SSH-2.0-OpenSSH_6.9\0", 0x13) = 19 0
select(0x9, 0x7FFF5484F880, 0x7FFF5484F800, 0x7FFF5484F780, 0x0) = 1 0
read(0x7, "SSH-2.0-OpenSSH_6.7\r\n\0", 0x1000) = 21 0
write(0x1, "SSH-2.0-OpenSSH_6.7\n\0", 0x14) = 20 0
read(0x6, "\0", 0x1000) = -1 Err#35
write(0x6, "SSH-2.0-OpenSSH_6.7\n\0", 0x14) = 20 0
select(0x9, 0x7FFF5484F880, 0x7FFF5484F800, 0x7FFF5484F780, 0x0) = 1 0
read(0x6, "\0", 0x1000) = 1968 0
read(0x6, "\0", 0x1000) = -1 Err#35
^C
6.9是我本地机器的ssh,而6.7是Unix套接字后面的远程机器。对我来说似乎有点奇怪的是\r
如何被删除,这会将读/写计数改为1.我不确定这是否是关键区别。
理想情况下,我喜欢Lwt的某种抽象,只要在这个可读通道(TCP套接字)上有可用数据,就可以直接写入可写通道(Unix套接字),反之亦然。 / p>
答案 0 :(得分:2)
readline
的变体不起作用,因为数据流是二进制的,而readline
是基于文本行的输入。具有Lwt_io.read
函数的第二个变体不起作用,因为除非您指定了可选的count
参数,否则此函数将读取到最后的所有输入。这意味着,只有在阅读器端的EOF之后,该控件才会传递给write
。使用Lwt_io.read
进行一些计数,例如Lwt_io.read ~count:1024 mux_ic
将不是一个非常糟糕的主意。此外,如果您希望自己的流是有限的,那么您不应该忘记检查返回值。 read_into
应谨慎使用,与read
功能不同,它并不保证它会读取您所要求的确切数据量。换句话说,会有短读。 write_into
函数也是如此。此功能的_exactly
版本不会遇到此问题,因此最好使用它们。
还有一件事你应该考虑。 Lwt_io
为缓冲的输入和输出提供接口。这意味着,该模块中的所有函数都是从一些内部缓冲区写入和读取,而不是通过设备描述符直接与操作系统交互。这意味着,当您将数据从一个缓冲源传输到另一个缓冲源时,您将在两端出现意外延迟。所以你应该用冲洗来预测它们。否则,当你有双向互动时,可能会引入竞争条件。
此外,虽然缓冲的io简化了很多东西,但它还是有代价的。事实上,你有几个不必要的缓冲层,当你使用Lwt_io
时,你也会分配大量不必要的数据,垃圾堆坏你的内存。问题是Lwt_io
有自己的内部缓冲区,它不会为临时用户显示,所有返回数据或使用数据的函数都需要对内部函数执行额外的复制操作。例如,使用Lwt_io.{read,write}
,将执行以下操作:
write
部分)将数据从字符串复制到内部缓冲区看起来我们可以在2,3,4和6中删除副本。我们可以使用自己的缓冲区,并将数据从内核复制到其中,然后将数据从此内核复制回内核。我们甚至可以通过使用splice和tee系统调用来删除1和5中的副本,这些调用直接在内核缓冲区之间复制数据,而根本不涉及用户空间。但在这种情况下,我们将无法检查数据,通常这就是我们想要的。
因此,让我们尝试从内核空间中删除除副本之外的所有副本。
我们可以使用Lwt_io
中的内部缓冲区的低级接口,如direct_access
和新添加的block
函数,但这需要了解Lwt_io
的内部结构。而且不是很琐碎,但仍然是doable。相反,我们将使用一种使用Lwt_unix
库的简化方法。这个库直接与内核交互,没有任何中间缓冲区,只留下缓冲。
open Lwt.Infix
let bufsiz = 32768
let echo ic oc =
let buf = Lwt_bytes.create bufsiz in
let rec loop p =
let p = p mod bufsiz in
Lwt_bytes.read ic buf p (bufsiz - p) >>= function
| 0 -> Lwt.return ()
| n -> Lwt_bytes.write oc buf p n >>= loop in
loop 0
这将实现简单快速的数据复制,以与cat
程序相同的速度复制数据。尽管如此,仍有一些改进的余地。例如,应该添加错误处理,以获得稳健性(特别是对于EINTR
信号)。此外,此功能实现
同步复制,其中输入和输出被紧密锁定。有时它不是一种选择。考虑以下示例,输入是UDP套接字,可能很容易超过消费者,并且数据将被删除,即使平均而言生产者比消费者慢。要处理这个问题,您需要将读者和编写者分成两个独立的线程,这些线程通过一些弹性队列进行通信。
Lwt
是一个相当低级的库,它不会为你解决这个问题。它提供了可用于为每个案例特别构建解决方案的机制。那些确实为某些常见模式提供解决方案的库,0MQ和nanomessages就是很好的例子。
我可能是一个太低级的家伙,也许我会深入挖掘。如果您真的在寻找高级方法,那么您应该使用Lwt_stream
,在这种情况下,您可以将foo.pipe(bar).pipe(foo)
的节点等效代码编码为
let echo ic oc = Lwt_io.(write_chars oc (read_chars ic))
当然这会慢得多,但这取决于你的任务。
是的,要执行双向重定向,您应该运行两个线程,如下所示:echo ic oc <&> echo ic oc
用于具有文件描述符的版本,它们都是可写的。如果您正在使用Lwt_io
个渠道,这些渠道与管道一样无法定义,那么您将为每个部分获得两个端点。让我们相应地为前端输入和输出命名fi
和fo
,为后端部分命名bi
,bo
。然后你需要像这样连接它:echo fo bi <&> echo bo fi
,使用第二版echo
和流。
通常,高级抽象会带来性能成本。在我们的特定情况下,使用第一个版本的echo,每秒的吞吐量超过1Gb
。包含流的版本具有平均吞吐量5MB/s
。根据您的设置,它可能或可能不起作用。这对于常规ssh会话来说已经足够了,但可能会对本地网络中的scp
产生影响。