隧道顾名思义让不通的地方变通。SSH隧道技术也是同样的道理,借用SSH这个工具,通过SSHClient和SSHServer之间建立一条TCP通道,从而打通二台设备,于此同时分别监听各自的一个端口号,如果端口号复合就把该数据加密并且发到对端,对端解密后根据配置的命令再做出相对应的操作。SSH有正向代理与反向代理之说,下面将分别介绍。
SSH正向代理(也叫本地转发):
先来看看第一个例子,实验室里有台机子D,其上有一个IPv4的TCPServer进程,监听端口为5555。在我们的外网上有机子A、B、C,可以通过路由器互通,在A机子上有一个IPv4的TCPClient进程。由于机子D和外网是不通的,但是D所能到的机子C和外网却是可以进行通讯。因此我们会想,能不能把机子C当作一座连接D和外网的桥呢?
答案无疑是本地端口转发了,它的命令格式是:
ssh -gNfL <local port>:<remote host>:<remote port> <SSH hostname>
在机子B上执行如下命令即可建立一个SSH的本地端口转发,例如:
#ssh -gNfL 6666:192.168.1.100:5555 172.16.163.213命令说明:
该命令建立了一个SSHClient和SSHServer,当SSHClient收到一个目的端口是6666的报文,就会通过建立的这条隧道把数据转到SSHServer这边,SSHServer在收到这个报文,会进行解包,并把之前配置的目的IP和目的端口写到报文里,然后转发给192.168.1.100:5555这个地址。这样双方就能进行通讯了。
这是SSHServer和目标机器不是同一台机器的情况,如果是同一台,那SSH hostname跟remote host填的是一样的。比较常见的是下面这种情况:
在机子A上执行如下命令即可
#ssh -NfL 6666:localhost:5555 172.16.163.213该命令告诉机子A转发本本机上发往6666端口的报文到SSHServer端,随后SSHServer在收到包以后把这个包发往localhost的5555端口。localhost也可以用172.16.163.213来代替。
在理解了正向代理,咱们在来理解下反向代理,同理我们也是用一个例子来理解这么一过程。
在实验室有下面这么一套环境,D机子能直接连接外网,这个例子虽然跟其它的资料说的不一样,但是其原理是一样,只要你能理解它。
这里跟正向代理的区别在与SSHClient跟SSHServer发生了位置交换,也就是说现在的主动权在机子C上了,是机子C主动建立了一个隧道,让外网可以来访问机子D。
ssh -NfR <<span style="font-family: Arial, Helvetica, sans-serif;">remote </span><span style="font-family: Arial, Helvetica, sans-serif;">port>:<local host>:<local port> <SSH hostname></span>
在机子C上执行如下命令即可建立一个SSH的远程端口转发,例如:
#ssh -NfR 6666:192.168.1.100:5555 172.16.25.210该命令告诉SSHServer在收到6666这个端口的报文时,转发到SSHClient这边,SSHClient根据配置的地址,把该报文转发到192.168.1.100:5555。
下面是IPv4的TCP 服务端和客户端的测试代码,其中对于正向代理,TCP的客户端运行在机子A上,对于反向代理,TCP的客户端运行在机子B上。TCP的服务端都是运行在机子D上。
//ipv4tcpclient.c
#include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> int main (int argc, const char * argv[]) { struct sockaddr_in server_addr; //server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(5555); server_addr.sin_addr.s_addr = inet_addr("172.16.25.210"); bzero(&(server_addr.sin_zero),8); int server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror("socket error"); return 1; } char recv_msg[1024]; char reply_msg[1024]; if (connect(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))==0) { //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。 while (1) { bzero(recv_msg, 1024); bzero(reply_msg, 1024); long byte_num = recv(server_socket,recv_msg,1024,0); recv_msg[byte_num] = '\0'; printf("server said:%s\n",recv_msg); printf("reply:"); scanf("%s",reply_msg); if (send(server_socket, reply_msg, 1024, 0) == -1) { perror("send error"); } } } // insert code here... printf("Hello, World!\n"); return 0; }
#include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> int main (int argc, const char * argv[]) { struct sockaddr_in server_addr; //server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET;//Address families AF_INET互联网地址簇 server_addr.sin_port = htons(5555); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); bzero(&(server_addr.sin_zero),8); //创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有连接 if (server_socket == -1) { perror("socket error"); return 1; } //绑定socket:将创建的socket绑定到本地的IP地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信 int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (bind_result == -1) { perror("bind error"); return 1; } //listen侦听 第一个参数是套接字,第二个参数为等待接受的连接的队列的大小,在connect请求过来的时候,完成三次握手后先将连接放到这个队列中,直到被accept处理。如果这个队列满了,且有新的连接的时候,对方可能会收到出错信息。 if (listen(server_socket, 5) == -1) { perror("listen error"); return 1; } struct sockaddr_in client_address; socklen_t address_len; int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len); //返回的client_socket为一个全相关的socket,其中包含client的地址和端口信息,通过client_socket可以和客户端进行通信。 if (client_socket == -1) { perror("accept error"); return -1; } char recv_msg[1024]; char reply_msg[1024]; while (1) { bzero(recv_msg, 1024); bzero(reply_msg, 1024); printf("reply:"); scanf("%s",reply_msg); send(client_socket, reply_msg, 1024, 0); long byte_num = recv(client_socket,recv_msg,1024,0); recv_msg[byte_num] = '\0'; printf("client said:%s\n",recv_msg); } return 0; }