作为我的常用工具,对它的实现很感兴趣,而且(主要是)代码较少而且容易理解。简单解说,写在这里:
基于 1.4g 调用结构:4g。
Info |
---|
初次执行 ssh 加入 -o StrictHostKeyChecking=no 可以防止阻塞,并使用公钥认证 |
程序会建立一个端口 P1 监听数据,然后使用 ssh tunnel 代理该端口到远程主机。再代理远程端口到本地 P2,从本地连接到 P2,写数据到 P2 之后程序可以从 P1 读到这份数据。
如果数据收发正常,说明隧道正常。如果有问题就重启 ssh 子进程。
Fork 进程
主要的逻辑位于 ssh_run 函数中:在 while 循环中,使用 fork 函数生成一个子进程。
Code Block |
---|
|0cchild = fork(); // fork 函数的返回,子进程从此处执行,子进程返回 0,父进程返回子进程的 pid,错误返回 -1,errorno 说明错误原因 switch (cchild) { case 0: errlog(LOG_DEBUG, "child of %d execing %s", getppid(), av[0]); 1 // evecvp 执行的是 ssh 命令,此处阻塞执行 execvp(av[0], av); errlog(LOG_ERR, "%s: %s", av[0], strerror(errno)); /* else can loop restarting! */ 2 // 使用 SIGTERM kill 3 | |0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ main | get_env_args | ssh_run ------| | 父进程 kill(getppid(), SIGTERM); _exit(1); break; case -1: // 出错的情况下 cchild = 0; xerrlog(LOG_ERR, "fork: %s", strerror(errno)); break; default: // 父进程继续执行 errlog(LOG_INFO, "ssh child pid is %d", (int)cchild); set_sig_handlers(); retval = ssh_watch(sock); dolongjmp = 0; clear_alarm_timer(); unset_sig_handlers(); if (retval == P_EXITOK || retval == P_EXITERR) return retval; break; } |
监控子进程
在 conn_test 函数中实现了监听端口了传输数据的流程,用于 ssh 进程的隧道是否联通。
Code Block |
---|
conn_test
|-- conn_remote(char *host, char *port)
|-- conn_poll_for_accept(int sock, struct pollfd *pfd)
|-- conn_send_and_receive(char *rp, char *wp, size_t len, struct pollfd *pfd, int ntopoll) |
程序启动时已经调用了 conn_listen
监听端口。conn_test
调用的三个程序分别负责连接,accept
以及发送接收数据。
循环的时候,通过 SIGALRM 信号触发的连接测试,并定时准备下一次测试。