ProxyCommand功能的使用 – 透过http代理

此部分基础请参考: OpenSSH系列(五)-跳板机与HTTP代理使用

完整的使用方法请参考: 使用代理-–-HTTPProxy

1
2
# 注意, 这里的nc需要使用netcat-openbsd(openbsd版本的nc, gnu的nc没有-X功能)
ssh -o "ProxyCommand=nc -X connect -x 127.0.0.1:8888 %h %p" [email protected]

源码相关

本来也没必要读源码的, 主要是我想知道ssh是怎样使用代理连接的. 下面是相关代码, 我把重要部分补上了注释. 主要使用了POSIX标准中的pipe功能, 新建一个子进程用户连接代理服务器, 而后父进程再通过stdin, stdout重定向的功能与子进程通信, 也就可以与远端服务器通信了, 此代码不难, 但是建议与博客中的NodeJS实现的代理服务器一起阅读.

代码来源: https://github.com/openssh/libopenssh

1
2
# 直接搜索相关的代码
find . -name "*.c" | xargs grep 'proxy_command' -n

可以看到, 相关的代码放在ssh/sshconnect.c

1
2
3
4
5
// ssh_connect 函数

/* If a proxy command is given, connect using it. */
if (proxy_command != NULL)
return ssh_proxy_connect(host, port, proxy_command);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
* Connect to the given ssh server using a proxy command.
*/
static struct ssh *
ssh_proxy_connect(const char *host, u_short port, const char *proxy_command)
{
struct ssh *ssh;
char *command_string, *tmp;
int pin[2], pout[2];
pid_t pid;
char *shell, strport[NI_MAXSERV];

if ((shell = getenv("SHELL")) == NULL || *shell == '\0')
shell = _PATH_BSHELL;

/* Convert the port number into a string. */
snprintf(strport, sizeof strport, "%hu", port);

/*
* Build the final command string in the buffer by making the
* appropriate substitutions to the given proxy command.
*
* Use "exec" to avoid "sh -c" processes on some platforms
* (e.g. Solaris)
*/
xasprintf(&tmp, "exec %s", proxy_command);
command_string = percent_expand(tmp, "h", host, "p", strport,
"r", options.user, (char *)NULL);
xfree(tmp);

// 初始化两个管道
/* Create pipes for communicating with the proxy. */
if (pipe(pin) < 0 || pipe(pout) < 0)
fatal("Could not create pipes to communicate with the proxy: %.100s",
strerror(errno));

debug("Executing proxy command: %.500s", command_string);

/* Fork and execute the proxy command. */
if ((pid = fork()) == 0) { // 子进程
char *argv[10];

/* Child. Permanently give up superuser privileges. */
permanently_drop_suid(original_real_uid);

/* Redirect stdin and stdout. */
close(pin[1]);
if (pin[0] != 0) {
// 关掉子进程的标准输入, 使用pin[0]作为标准输入
if (dup2(pin[0], 0) < 0)
perror("dup2 stdin");
close(pin[0]);
}
// 关掉子进程的标准输出, 使用pout[1]作为标准输出
close(pout[0]);
if (dup2(pout[1], 1) < 0)
perror("dup2 stdout");
/* Cannot be 1 because pin allocated two descriptors. */
close(pout[1]);

/* Stderr is left as it is so that error messages get
printed on the user's terminal. */
argv[0] = shell;
argv[1] = "-c";
argv[2] = command_string;
argv[3] = NULL;

/* Execute the proxy command. Note that we gave up any
extra privileges above. */
signal(SIGPIPE, SIG_DFL);
// 启动子进程, 子进程将会与远程的服务器相连
execv(argv[0], argv);
perror(argv[0]);
exit(1);
}
/* Parent. */
if (pid < 0)
fatal("fork failed: %.100s", strerror(errno));
else
proxy_command_pid = pid; /* save pid to clean up later */

/* Close child side of the descriptors. */
close(pin[0]);
close(pout[1]);

/* Free the command name. */
xfree(command_string);

/* Set the connection file descriptors. */
ssh = ssh_packet_set_connection(NULL, pout[0], pin[1]);
ssh_packet_set_timeout(ssh, options.server_alive_interval,
options.server_alive_count_max);

/* Indicate OK return */
return (ssh);
}