之前想弄一个shadowsocks的插件,发现网上要不是frankwei98 开发的这种需要架设API的,就是soft-wiki 这种功能极其简陋,UI不友好的。然后就又在网上找了一些传说中shadowsocks 3.x版本的安装,发现里面的代码被改得乱七八糟的,明显是不想给人用的,然后自己能看懂一点代码,就这几天把这些代码改好,然后放出来共享一下。
效果图 shadowsocks插件其实关键的文件就是shadowsocks.php
和templates/details.tpl
两个文件,其实php文件是负责处理后台逻辑,tpl文件是负责前端显示。而其他css、js等文件就是UI显示锦上添花而已。
shadowsocks.php解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function shadowsocks_TestConnection(array $params) { try { $dbhost = $params['serverip']; $dbuser = $params['serverusername']; $dbpass = $params['serverpassword']; $db = new PDO('mysql:host=' . $dbhost, $dbuser, $dbpass); $success = true; $errorMsg = ''; } catch (Exception $e) { logModuleCall('shadowsocks', 'shadowsocks_TestConnection', $params, $e->getMessage(), $e->getTraceAsString()); $success = false; $errorMsg = $e->getMessage(); } return array('success' => $success, 'error' => $errorMsg); }
上面这是一个测试数据库服务器通断的函数,这里需要在whmcs后台配置Setup->Products/Services->Servers->Add New Server
页面,其中页面中的IP Address其实就是通过$params['serverip']
来传递数据库IP的参数,Server Details中选中Shadowsocks for whmcs,再填写下面的Username
和Password
,分别设置和数据库服务器的登录名和密码,在代码中可以看出是通过$params['serverusername']
和$params['serverpassword']
传递。然后通过PDO来跟数据库服务器建立连接,如果连接成功没有触发异常就显示成功,触发异常就显示false。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function initialize(array $params) { $query['RECYCLE'] = 'SELECT `port` FROM `recycle_bin` ORDER BY `created_at` DESC LIMIT 1'; $query['DELETE_RECYCLE'] = 'DELETE FROM `recycle_bin` WHERE `port` = :port'; $query['ADD_RECYCLE'] = 'INSERT INTO `recycle_bin`(`port`,`created_at`) VALUES (:port,UNIX_TIMESTAMP())'; $query['LATEST_USER'] = 'SELECT `port` FROM `user` ORDER BY `port` DESC LIMIT 1'; $query['CREATE_ACCOUNT'] = 'INSERT INTO `user`(`passwd`,`transfer_enable`,`port`,`created_at`,`need_reset`,`sid`) VALUES (:passwd,:transfer_enable,:port,UNIX_TIMESTAMP(),:need_reset,:sid)'; $query['ALREADY_EXISTS'] = 'SELECT `port` FROM `user` WHERE `sid` = :sid'; $query['ENABLE'] = 'UPDATE `user` SET `enable` = :enable WHERE `sid` = :sid'; $query['DELETE_ACCOUNT'] = 'DELETE FROM `user` WHERE `sid` = :sid'; $query['CHANGE_PASSWORD'] = 'UPDATE `user` SET passwd = :passwd WHERE `sid` = :sid'; $query['USERINFO'] = 'SELECT `id`,`passwd`,`port`,`t`,`u`,`d`,`transfer_enable`,`enable`,`created_at`,`updated_at`,`need_reset`,`sid` FROM `user` WHERE `sid` = :sid'; $query['RESET'] = 'UPDATE `user` SET `u`=0,`d`=0 WHERE `sid` = :sid'; $query['CHANGE_PACKAGE'] = 'UPDATE `user` SET `transfer_enable` = :transfer_enable WHERE `sid` = :sid'; return $query; }
上面这个初始化函数就是生成一大串数据库检索要用到的函数,就是各种插入,更新的函数。还有,这个shadowsocks数据库中有两个表,一个recycle_bin
,一个是user
,其中recycle_bin
就是为了回收端口,user
表中删除的端口先放到recycle_bin
中,然后新增用户优先从recycle_bin
中检索,recycle_bin
空了才用新端口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function shadowsocks_ConfigOptions() { return array( '数据库名' => array('Type' => 'text', 'Size' => '25'), '重置流量' => array( 'Type' => 'dropdown', 'Options' => array('1' => '需要重置', '0' => '不需要重置'), 'Description' => '是否需要重置流量' ), '流量限制' => array('Type' => 'text', 'Size' => '25', 'Description' => '单位MB'), '授权密钥' => array('Type' => 'text', 'Size' => '32', 'Description' => '请输入您从购买者手中获取的密钥'), '起始端口' => array('Type' => 'text', 'Size' => '25', 'Description' => '如果数据库有记录此项无效'), '线路列表' => array('Type' => 'textarea', 'Rows' => '3', 'Cols' => '50', 'Description' => '格式 xxx|服务器地址|加密方式|协议|混淆| 一行一个') ); }
上面的代码是shadowsocks插件的配置页面代码,就是Setup->Products/Services->Products/Services->Create a New Product->Module Settings
配置的信息,这里自定义了6个参数,到时候就是按顺序$params['configoption1']
到$params['configoption6']
这样来定位,其中第一个参数就是shadowsocks数据库名,第二个参数是重置流量,就是在数据库中多了一个reset参数需要配置,其实到时候就看你的重置逻辑怎么写而已,第三个是最大流量,第四个是授权密钥,感觉这个插件之前是收费的,在initialize函数中有一个认证密钥的过程,那认证过程已经删掉,留空不填就可以了,第五个是起始端口,就是shadowsocks的端口从哪个开始分配,第六个是线路列表,格式就是xxx|服务器地址|加密方式|协议|混淆
,这个参数的读取代码写得有点问题,添加多个服务器时,混淆后面要加|,最后一行才不用加|,因为我是用|来分割字符串,然后5个参数读一下这样写的。
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 function shadowsocks_CreateAccount(array $params) { $query = initialize($params); try { $dbhost = $params['serverip']; $dbname = $params['configoption1']; $dbuser = $params['serverusername']; $dbpass = $params['serverpassword']; $db = new PDO('mysql:host=' . $dbhost . ';dbname=' . $dbname, $dbuser, $dbpass); $already = $db->prepare($query['ALREADY_EXISTS']); $already->bindValue(':sid', $params['serviceid']); $already->execute(); if ($already->fetchColumn()) { return 'User already exists.'; } $bandwidth = (!empty($params['configoption3']) ? convert($params['configoption3'], 'mb', 'bytes') : (!empty($params['configoptions']['traffic']) ? convert($params['configoptions']['traffic'], 'gb', 'bytes') : '1099511627776')); $recycle = $db->prepare($query['RECYCLE']); $recycle->execute(); $recycle = $recycle->fetch(); if ($recycle) { $port = $recycle['port']; define('RECYCLE', true); } else { $port = $db->prepare($query['LATEST_USER']); $port->execute(); $port = $port->fetch(); if ($port) { $port = $port['port'] + 1; } else { $port = (!empty($params['configoption5']) ? $params['configoption5'] : '10000'); } } $create = $db->prepare($query['CREATE_ACCOUNT']); $create->bindValue(':passwd', $params['customfields']['password']); $create->bindValue(':transfer_enable', $bandwidth); $create->bindValue(':port', $port); $create->bindValue(':need_reset', $params['configoption2']); $create->bindValue(':sid', $params['serviceid']); $create = $create->execute(); if ($create) { if (defined('RECYCLE') && RECYCLE) { $recycle = $db->prepare($query['DELETE_RECYCLE']); $recycle->bindParam(':port', $port); $recycle->execute(); } return 'success'; } else { $error = $db->errorInfo(); return $error; } } catch (Exception $e) { logModuleCall('shadowsocks', 'shadowsocks_CreateAccount', $params, $e->getMessage(), $e->getTraceAsString()); return $e->getMessage(); } }
上面就是开通账号的函数,逻辑如下:拿到serverip、数据库名、serverusername
和serverpassword
后,就开始连接数据库,由于whmcs在开通每个服务时会分配一个serviceid
,就先用数据库查找是否存在相同的serviceid
,这一部分使用ALREADY_EXISTS
来检索。如果没有,就继续执行。然后再读取params['configoption3']
为设定的最大带宽。然后就开始在回收站表中看是否有回收的port
,优先使用,没有就检索user
表,在现有port
的值上再加1,如果user
表为空,就设定params['configoption5']
的端口为初始端口。然后就使用CREATE_ACCOUNT
来插入一个user
表。成功创建后,还要回头看看是否用了回收站表的端口,用了的话就要从回收站中删除相应的port。
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 function shadowsocks_ClientArea(array $params) { $query = initialize($params); try { $dbhost = $params['serverip']; $dbname = $params['configoption1']; $dbuser = $params['serverusername']; $dbpass = $params['serverpassword']; $db = new PDO('mysql:host=' . $dbhost . ';dbname=' . $dbname, $dbuser, $dbpass); $usage = $db->prepare($query['USERINFO']); $usage->bindValue(':sid', $params['serviceid']); $usage->execute(); $usage = $usage->fetch(); $nodes = $params['configoption6']; $results = array(); $node = explode('|', $nodes); $x=0;$count=count($node)-1; while($x <= $count){ $results[$x/5][$x%5] = $node[$x]; $x++; } $user = array('passwd' => $usage['passwd'], 'port' => $usage['port'], 'u' => $usage['u'], 'd' => $usage['d'], 't' => $usage['t'], 'sum' => $usage['u'] + $usage['d'], 'transfer_enable' => $usage['transfer_enable'], 'created_at' => $usage['created_at'], 'updated_at' => $usage['updated_at']); if ($usage && $usage['enable']) { return array( 'tabOverviewReplacementTemplate' => 'details.tpl', 'templateVariables' => array('usage' => $user, 'params' => $params, 'nodes' => $results) ); } return array( 'tabOverviewReplacementTemplate' => 'error.tpl', 'templateVariables' => array('usefulErrorHelper' => '出现了一些问题,可能您的服务还未开通,请稍后再来试试。') ); } catch (Exception $e) { logModuleCall('shadowsocks', 'shadowsocks_ClientArea', $params, $e->getMessage(), $e->getTraceAsString()); return array( 'tabOverviewReplacementTemplate' => 'error.tpl', 'templateVariables' => array('usefulErrorHelper' => $e->getMessage()) ); } }
shadowsocks_ClientArea
函数就是用户登录后,点击shadowsocks服务会触发的一个请求,这个函数会从数据库中检索出数据,然后传递给details.tpl
来做前端显示,这里可以看出传递了两个关键的结构体,usage
和nodes
结构体。 然后还有其他shadowsocks_ResetBandwidth
重置流量、shadowsocks_ChangePassword
修改密码、shadowsocks_ChangePackage
修改套餐、shadowsocks_TerminateAccount
删除账号、shadowsocks_SuspendAccount
停用账号等,都是一些对数据库表的操作,大同小异。
details.tpl解析 这个前端显示的文件就更简单了,就是大部分是html
、css
、js
代码,中间夹杂着几个数据,就是shadowsocks_ClientArea
函数传递过来的usage
和nodes
结构体。
shadowsocks安装 默认是使用breakwa11的shadowsocks-rss,详细配置说明在此 ,其实由于rss的混淆和协议均能选择兼容版本,所以能一次提供shadowsocks原版和shadowsocks-rss两种服务,如果实在只想用原版,就是线路配置的地方“xxx|服务器地址|加密方式|协议|混淆”变成“xxx|服务器地址|加密方式||”即可。 这里就简单说一下自己遇到的坑:
服务器的防火墙一定要看是否关闭,要不死活连不上;
协议插件可以设为:auth_sha1_v4_compatible
,混淆插件可以设为tls1.2_ticket_auth_compatible
,有compatible字样就能兼容原版。
shadowsocks插件的使用说明 插件放到Modules/servers/shadowsocks
目录下
服务器配置 新建服务器 IP Address此处填写服务器的IP地址,Username和Password处填写服务器的MySQL用户密码(要求有远程连接权限,并且至少拥有Shadowsocks表认证表的增删该查权限)
配置产品 新建产品 选择Module Settings,填写配置信息 在Custom Fields添加自定义密码,如图一字不差的填写。 大功告成,收工,shadowsocks插件代码https://github.com/kesuki/whmcs-shadowsocks-plugin/ 以下分割线为更新内容,2017年4月18日更新
重置流量 已更新github代码,新增cron.php重置流量脚本,重置流量的代码使用php语言编写,可以直接放在搭建whmcs服务的vps上,建议不要放在web站点目录上,建议修改cron.php
文件权限为644。 在vps上新建计划任务crontab -e
每月1日使用php运行该脚本0 0 1 * * /usr/bin/php -f 目录路径/cron.php &> /dev/null
以下分割线为更新内容,2017年12月20日更新
本博客已不再维护Github上的项目,而且该项目也进行了多次的版本迭代,如有疑问,请在Github上提交Issues。