标签归档:socket

Sockets代理 之 SSH Sockets

使用SSH来开启Socks代理:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22
root@x.x.x.x's password:

这样就可以启动一个Socks,C表示压缩数据传输,N表示不进入Bash。(比如浏览器)设置Socket代理IP为127.0.0.1端口为7070,然后就可以通过x.x.x.x(x.x.x.x可以是本机地址)代理上网了。

SSH可以通过-i指定一个私钥来进行认证(这样就不需要输入密码),这种方式意味在需要在服务器端对应的用户里放置一个公钥:

root@xxx:~/.ssh ls
authorized_keys  known_hosts
root@xxx:~/.ssh# pwd
/root/.ssh
root@xxx:~/.ssh:~/.ssh# ls
authorized_keys  known_hosts
root@xxx:~/.ssh:~/.ssh# cat authorized_keys 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCiaMoJRe..........

公钥放进去后,在客户端就需要指定其对应的私钥:

ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 -i /roo/is_rsa

这样就不需要输入密码了。Linux中SSH不支持直接输入密码,但是可以使用一个包装工具来完成(sshpass):

yum install sshpass

#运行sshpass
sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 

#查看进程可见:sshpass就是一个包装器
sshpass -p zzzzzzzzzz ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22
ssh -CN -D 127.0.0.1:7070 root@127.0.0.1 -p 22

注:这里是监听本机的127.0.0.1的7070端口,本机通过SSH把数据传输到远程服务器,把远程服务器设置在本机也是可以的,这样本机和远程都一样的IP。

为了防止进程异常退出,可以定时运行一个SHELL脚本:

#1 使用sshpass包装器

#!/bin/bash

live=`ps -efH | grep 'sshpass' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    sshpass -p "passwd" ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi


#2 不使用sshpass包装器时,需要在远程服务器上设置放置好公钥
#!/bin/bash

live=`ps -efH | grep 'ssh -CN -D' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
    ssh -CN -D 127.0.0.1:7070 root@x.x.x.x -p 22 2>&1 >> /dev/null
fi

本地和远程之间传输数据实际是经过加密的(走SSH),就算本机和远程是同一条机器时也是如此。本地和客户端建立的TCP是一条持久链接:

netstat -ntp

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name   
tcp        0      0 127.0.0.1:22            127.0.0.1:39426         ESTABLISHED 20575/sshd: root                    
tcp        0      0 127.0.0.1:39426         127.0.0.1:22            ESTABLISHED 20574/ssh  

客户端ssh随机打开了一个端口(39436)和远程22端口进行链接。所有从7070过来的数据,都会转发到39436端口,由它把数据发送到服务端。不过这个本地转发是透明的。到此,我们有两个结论:
一、所有的数据都是通过一条持久链接发送到远程的
二、由SSH开放的端口(这里是7070),需要HOLD住所有的链接,等到服务端返回后,再转发回去。
所以,SSH开启的代理,不适用于高并发场景。对于高并发的场景,代理需要HOLD住大量链接,然后在和服务端链接时应该一一对应,或者开一个池。但是对于一般的代理上网,是够用的。

在Windows下也有对应的工具,下载地址:http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html。这里有一个叫Plink的工具,它类似sshpass的功能:

plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v

参数C表示启用压缩,N表示不进入终端,-pw来指定密码,-P指定端口(注意这里是大写的P), -v表示实时的输出日志。使用Plink这个工具(或者Putty)也可以通过-i来指定私钥。

监控脚本,在Windows下:

:1
bin\plink.exe -C -N -D 127.0.0.1:7070 root@x.x.x.x -pw password -P 22 -v
goto 1

保存为一个bat文件,需要代理时,双击它,然后弹出一个命令行窗口,由于加了-v参数,可以实时参考输出日志(证明它是工作的),不需要时直接关闭即可。

最后就是浏览器的代理配置了(以火狐为例子):
工具 – 选项
firefox-setting

firefox-agent

看起来,虽然实现了使用SSH Socks来上网,但是还是稍有麻烦:
1 不使用代理时,需要重新进入 工具 – 选项,找到网络,然后点击不使用代理
2 每次要上网代理时,需要运行一个命令行窗口

为了解决第一个麻烦问题,我们可以在firefox中安装一个叫autoproxy的插件(也有其它类似的),安装之后浏览器上会有一个按钮,点击切换是否使用代理:
autoproxy
可以配置多个代理,方便切换:
autoproxy-switch
Autoproxy这个插件看起来从2013年开始就再没有更新了。

至于第二个问题,我们需要找到一个客户端管理工具。目前在使用的工具:
1 http://nemesis2.qx.net/pages/MyEnTunnel (需要翻墙)
下载的是一个exe安装文件,一路安装下来:
myentunnel
其中lng是语言文件,ini是配置文件,可以发现plink.exe,所以这个工具是plink.exe包装器,让使用plink.exe使用更加方便一点而已。双击myentunnel.exe:
myentunnel-setting
这里的配置跟plink.exe的命令使用是对应的。可以设置多个配置,默认的是叫default的配置,可以点击右下角的红锁(或绿锁),添加配置,这个配置实际是对应命令目录下的ini文件。看来,配置无法删除,只能到文件夹里面删除对应的文件。

可以把这个文件夹拷贝下来。

2 另一个可用的工具是Bitvise Tunnelier,它不是plink.exe的包装器,可以互联网搜索看看。

PHP函数参考 – 其它服务 – Sockets(Socket编程)

PHP的Socket支持放入了“其它服务 – Sockets”一节(http://cn2.php.net/manual/zh/book.sockets.php)。实际上这部分内容和PHP的“其它基本扩展 — Streams”一节有重复,不过Streams比Socket更加抽象和具体。

PHP中的socket编程跟C socket编程非常类似。要让PHP支持socket编程,在编译PHP时必须在配置中添加–enable-sockets 配置项来启用。
PHP编译参考:

./configure --prefix=/usr/local/php-5.5.15 --with-config-file-path=/usr/local/php-5.5.15/etc \
--with-mysql=mysqlnd --with-mysqli=mysqlnd --with-iconv-dir --with-freetype-dir=/usr \ 
--with-jpeg-dir=/usr --with-png-dir=/usr --with-zlib --with-libxml-dir=/usr --enable-xml \
--disable-rpath --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization \
--with-curl --enable-mbregex --enable-mbstring --with-mcrypt=/usr --with-gd --enable-gd-native-ttf \
--with-openssl --with-mhash --enable-pcntl --enable-sockets --with-xmlrpc --enable-zip --enable-soap 
--enable-opcache --with-pdo-mysql --enable-maintainer-zts --enable-fpm

PHP中的socket编程例子:

//php_socket_server.php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '0.0.0.0', 8080);
socket_listen($server,128);

if($server){
        $i=1;
        while($client = socket_accept($server)){
                //socket_set_option($client, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 5, "usec" => 0));

                //read
                $buf = socket_read($client,8192);
                echo "Read data from client success is-->\n".$buf."\n\n";

                //write
                $writeto = "$i --> Server Server Server";
                if(socket_write($client,$writeto,strlen($writeto))){
                        echo "Write ($writeto) to client success.\n\n";
                }

                socket_close($client);
                $i++;
        }
}

//php_socket_client.php
$client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($client, "192.168.1.168", 8080);

//write
$to = "Client Client Client";
if(socket_write($client,$to,strlen($to))){
        echo "Write ($to) to server success.\n\n";
}


//read
while($out = socket_read($client,8192)){
        echo "\n\nRead data from server sussess-->\n";
        echo $out."\n\n";
}

socket_close($client);

运行:

###客户端
/usr/local/php-5.5.15/bin/php php_socket_client.php 
Write (Client Client Client) to server success.

Read data from server sussess-->
1 --> Server Server Server

###服务端(不退出,继续等待链接)
/usr/local/php-5.5.15/bin/php php_socket_server.php 
Read data from client success is-->
Client Client Client

Write (1 --> Server Server Server) to client success.

服务端循环等待客户端的链接,获取到链接后把来自客户端的内容读出来,然后再把内容写到客户端。客户端是先往服务端写数据,然后把来自服务端的数据读回来。

函数说明:
1) resource socket_create ( int $domain , int $type , int $protocol )
[pre]
$domain 指定应用的协议,跟C Socket一致,只有AF_INET、AF_INET6和AF_UNIX
$type 指定套接字使用的类型,一般如果是TCP就是SOCK_STREAM,UDP就是SOCK_DGRAM
$protocol 设置指定 domain 套接字下的具体协议。TCP 或 UDP,可以直接使用常量 SOL_TCP 和 SOL_UDP
[/pre]
socket_create() 正确时返回一个套接字,失败时返回 FALSE。要读取错误代码,可以调用 socket_last_error()。这个错误代码可以通过 socket_strerror() 读取文字的错误说明。

2)bool socket_bind ( resource $socket , string $address [, int $port = 0 ] )
绑定地址和端口到$socket。在使用 socket_listen()之前,这个操作必须先完成。 如果如果创建的套接字是AF_INET,$address就是IP地址,$port就是端口号;如果套接字是AF_UNIX,$address本地套接字路径,$port不需要指定。

函数socket_create() 创建的socket默认是一个主动类型的,socket_listen函数将socket变为被动类型的,等待客户的连接请求。

函数socket_connect()的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数端口号。客户端通过调用socket_connect()函数来建立与TCP服务器的连接,所以它不需要调用socket_bind()和socket_listen()。

3) bool socket_listen ( resource $socket [, int $backlog = 0 ] )
创建了套接字和绑定了地址,接下来需要对其进行监听,意味着可以接受客户端的链接请求了。不过这个函数只能用于在创建socket时type是SOCK_STREAM和SOCK_SEQPACKET的套接字。$backlog参数表示链接队列的长度,默认为0,意味着一个个来,当前没有处理完成,那么下面的链接将被拒绝。

4) resource socket_create_listen ( int $port [, int $backlog = 128 ] )
这个函数是socket_create()、socket_bind()和socket_listen()这个三个函数的type为AF_INET和绑定所有本地地址的简化具体版本。$backlog和socket_listen()一样,表示接受链接的队列长度。

5) resource socket_accept ( resource $socket )
从$socket的队列中获取第一个链接,返回一个套接字。如果队列中没有链接,那么socket_accept()将会等待。如果使用socket_set_nonblock()设置了$socket处于非堵塞模式,socket_accept()在无法获取到链接是直接返回FALSE。

socket_accept ()函数返回的是一个具体的套接字。以后的通信交互都是基于这个新的套接字的。

6)string socket_read ( resource $socket , int $length [, int $type = PHP_BINARY_READ ] )
从套接字读取数据,注意$type默认是PHP_BINARY_READ,表示二进制数据,字节流。如果设置成PHP_NORMAL_READ 就是遇到“\r”或“\n”就返回。

PHP还提供了int socket_recv ( resource $socket , string &$buf , int $len , int $flags )函数,把返回的的数据写入到引用变量$buf中,函数返回接收到的字节数量。

PHP还提供了int socket_recvfrom ( resource $socket , string &$buf , int $len , int $flags , string&$name [, int &$port ] )函数,它从套接字中读取数据,不管这个套接字是不是面向链接的。

7) int socket_write ( resource $socket , string $buffer [, int $length = 0 ] )
向$socket写数据,$length一般需要指定。

PHP还提供了int socket_send ( resource $socket , string $buf , int $len , int $flags ),它向套接字中写指定长度字节的数据。

PHP还提供了int socket_sendto ( resource $socket , string $buf , int $len , int $flags , string $addr [,int $port = 0 ] )函数它向套接字写指定长度的数据而不管这个套接字是否是面向链接的。

8 int socket_last_error ([ resource $socket ] )
获取套接字最后产生的错误。可以通过socket_strerror()获取具体的错误信息。用socket_clear_error()清楚在套接字上产生的错误。

9 void socket_close ( resource $socket )
关闭套接字。

原创文章,转载务必保留出处。
永久链接: http://blog.ifeeline.com/1100.html