隧道顾名思义让不通的地方变通。SSH隧道技术也是同样的道理,借用SSH这个工具,通过SSHClient和SSHServer之间建立一条TCP通道,从而打通二台设备,于此同时分别监听各自的一个端口号,如果端口号复合就把该数据加密并且发到对端,对端解密后根据配置的命令再做出相对应的操作。SSH有正向代理与反向代理之说,下面将分别介绍。
SSH正向代理(也叫本地转发):
先来看看第一个例子,实验室里有台机子D,其上有一个IPv4的TCPServer进程,监听端口为5555。在我们的外网上有机子A、B、C,可以通过路由器互通,在A机子上有一个IPv4的TCPClient进程。由于机子D和外网是不通的,但是D所能到的机子C和外网却是可以进行通讯。因此我们会想,能不能把机子C当作一座连接D和外网的桥呢?
答案无疑是本地端口转发了,它的命令格式是:
ssh -gNfL ::
在机子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 <remote port>::
在机子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
#include
#include
#include
#include
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
#include
#include
#include
#include
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;
}