老戚的黑科技之SSH隧道技术

隧道顾名思义让不通的地方变通。SSH隧道技术也是同样的道理,借用SSH这个工具,通过SSHClient和SSHServer之间建立一条TCP通道,从而打通二台设备,于此同时分别监听各自的一个端口号,如果端口号复合就把该数据加密并且发到对端,对端解密后根据配置的命令再做出相对应的操作。SSH有正向代理与反向代理之说,下面将分别介绍。

SSH正向代理(也叫本地转发)

先来看看第一个例子,实验室里有台机子D,其上有一个IPv4的TCPServer进程,监听端口为5555。在我们的外网上有机子A、B、C,可以通过路由器互通,在A机子上有一个IPv4的TCPClient进程。由于机子D和外网是不通的,但是D所能到的机子C和外网却是可以进行通讯。因此我们会想,能不能把机子C当作一座连接D和外网的桥呢?

老戚的黑科技之SSH隧道技术_第1张图片

答案无疑是本地端口转发了,它的命令格式是:

ssh -gNfL :: 
-N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
-g 告诉SSH客户端,这个GatewayPorts=yes。即开启端口转发(别的主机发送过来的数据包只要端口匹配了,那就会送到SSHServer)
-f 告诉SSH客户端在后台运行
-L 做本地映射端口,
被冒号分割的三个部分含义分别是
需要使用的本地端口号
需要访问的目标机器IP地址(IP: 192.168.1.2)
需要访问的目标机器端口(端口: 5555)
最后一个参数是我们用来建立隧道的SSHServer的IP地址(IP: 172.16.163.213)

在机子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填的是一样的。比较常见的是下面这种情况:

老戚的黑科技之SSH隧道技术_第2张图片

在机子A上执行如下命令即可

#ssh -NfL 6666:localhost:5555 172.16.163.213
该命令告诉机子A转发本本机上发往6666端口的报文到SSHServer端,随后SSHServer在收到包以后把这个包发往localhost的5555端口。localhost也可以用172.16.163.213来代替。

SSH反向代理(也叫远程转发)

在理解了正向代理,咱们在来理解下反向代理,同理我们也是用一个例子来理解这么一过程。

在实验室有下面这么一套环境,D机子能直接连接外网,这个例子虽然跟其它的资料说的不一样,但是其原理是一样,只要你能理解它。

老戚的黑科技之SSH隧道技术_第3张图片

这里跟正向代理的区别在与SSHClient跟SSHServer发生了位置交换,也就是说现在的主动权在机子C上了,是机子C主动建立了一个隧道,让外网可以来访问机子D。

ssh -NfR <remote port>:: 
-N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
-f 告诉SSH客户端在后台运行
-R 做远程映射端口,
被冒号分割的三个部分含义分别是
需要使用的远程端口号
需要访问的本地机器IP地址(IP: 192.168.1.2)
需要访问的本地机器端口(端口: 5555)
最后一个参数是我们用来建立隧道的SSHServer的IP地址(IP: 172.16.25.210)

在机子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;
}


//ipv4tcpserver.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;//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;
}



你可能感兴趣的:(老戚的黑科技)