这些方案实际上都无法办理我的问题。这是一台公网做事器,并没有什么繁芜的网络构造,以是不能建立内网隔离。调度账号的密码策略,自然是一个方案,但是人工操作太麻烦,而且我一样平常常常换电脑利用,如果修正密码,公司的和家里的电脑都要更新,很麻烦。设置防火墙自然是运维的基本操作,但是iptables的配置太麻烦,ufw工具还好些,firewall-cmd就麻烦些,而且有一个巨大的痛点,众所周知,大家的出网IP都会常常变,好不容易在命令行里一个字母一个字母的配置好了,睡了一觉,空费了。堡垒机更不是一个主流的方案,有点大材小用,用了堡垒机,反而不能随意利用系统,更何况还没听说过那个免费的堡垒机呢。
那怎么办呢,作为一个资深的PHP开拓者,做事器这块的运用还不是手到擒来,当初连内网穿透都能轻松实现,一个IP过滤系统,小意思。以是我打算自己开拓这样一个项目,首先能够实现IP过滤,其余,可以轻松地将IP加入到白名单里,比如访问一个网页,就自动加入到白名单。
全体项目不到几个小时就研发完了,最少知足了我自己的需求,并且实现了这样几个特性:

现在我们来一步一步地实现这个别系。
第一步,首先能够简大略单的过滤IP利用PHP监听端口并且转发数据的框架很多,对此我选择workerman,缘故原由有3:
运行大略稳定方法接口大略内置进程守护至于详细的安装方法,可以参考他的官方文档。
workerman的利用方法非常大略,只要10行代码,就实现了IP转发+白名单过滤:
$worker = new Worker('tcp:0.0.0.0:' . Config::get('door.port_in'));// 监听一个端口$worker->count = 2;// 设置多进程$worker->onConnect = function (TcpConnection $connection) { // 获取IP白名单 $list_ip = AppIp::where('status', 0)->cache(3)->column('ip'); $remote_ip = $connection->getRemoteIp(); // 拦截IP if (!in_array($remote_ip, $list_ip)) { $connection->close(); } // 放行连接,连接内部目标端口 $to_connection = new AsyncTcpConnection('tcp:127.0.0.1:' . Config::get('door.port_to')); // 相互转发流量 $connection->pipe($to_connection); $to_connection->pipe($connection); $to_connection->connect();}
正如上面代码所示,只有大略几行,便实现了IP监听和转发,个中IP白名单通过数据库查询,并且缓存。
第二步,与ThinkPHP命令行整合在一起为了项目开拓方便,我都会利用ThinkPHP框架进行开拓,它够大略,功能也比较完好。
终极实现的命令行效果如下:
运行命令php think door start php think door start --mode d // 守护进程重启重启php think door restart停滞php think door stop
workerman的命令参数与thinkphp并不兼容,但是实现这样的效果并不难,实际上很大略,代码如下:
<?phpdeclare(strict_types=1);namespace app\common\command;use think\console\Command;use think\console\Input;use think\console\input\Argument;use think\console\input\Option;use think\console\Output;class Door extends Command{ protected function configure() { // 指令配置 $this->setName('door') // 设置think的命令参数 ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start') ->addOption('mode', 'm', Option::VALUE_OPTIONAL, 'Run the workerman server in daemon mode.') ->setDescription('the door command'); } protected function execute(Input $input, Output $output) { // 指令输出 $output->writeln('door'); $action = $input->getArgument('action'); $mode = $input->getOption('mode'); // 重新布局命令行参数,以便兼容workerman的命令 global $argv; $argv = []; array_unshift($argv, 'think', $action); if ($mode == 'd') { $argv[] = '-d'; } else if ($mode == 'g') { $argv[] = '-g'; } // ...workerman的代码 }}
在上面的代码中,紧张做了两件事:
实现ThinkPHP的命令设置将命令参数重新布局为workerman兼容的办法第三步,实现管理面板利用PHP实现一个管理面板太大略了,PHP到处都是这样的后台框架,这里我选择ulthon_admin,这是我自己开拓掩护的,它基于ThinkPHP6,很大略,为定制而生,不搞所谓的“插件”和“市场”生态,能够自动天生CURD代码,并且内置几了几个有趣的皮肤。
终极效果如下:
以上是ulthon_admin内置的两款皮肤效果,分别是:科幻、像素。
对付面板的管理,这里多做先容,这算是PHP开拓者的基本功,谁还不会个CURD啊。
第四步,进阶,更好的性能和流量统计我们的IP拦截客户端须要运行在做事器上,并且直接连接数据库,如果每次收到要求都要查询数据库,那么很有可能导致连接不通畅,尤其是客户端和数据库本身位置较远的时候。在第一步的代码中,我们只是大略地利用了查询缓存,但是还不足,还可以优化。并且我们可以在管理面板的截图中看到,我们是可以统计流量和拦截次数的,现在我们要实现这些功能:
流量统计首先我们将第一个步骤,流量转发部分的代码改造成如下的样子:
<?php// 向TO发起连接$to_connection = new AsyncTcpConnection('tcp://127.0.0.1:' . Config::get('door.port_to'));$to_connection->onMessage = function ($source, $data) use ($connection, $remote_ip) { // 吸收到来自TO的数据,返回的数据 $connection->send($data); // 将流量统计存储到内存里 Cache::inc(md5($remote_ip) . '-to', strlen($data));};// 流程和流量掌握$to_connection->onClose = function ($source) use ($connection) { $connection->close();};$connection->onBufferFull = function ($dest) use ($to_connection) { $to_connection->pauseRecv();};$connection->onBufferDrain = function ($dest) use ($to_connection) { $to_connection->resumeRecv();};$connection->onMessage = function ($source, $data) use ($to_connection, $remote_ip) { // 吸收来自IN的数据,要求的数据 $to_connection->send($data); // 将流量统计存储到内存里 Cache::inc(md5($remote_ip) . '-in', strlen($data));};// 流程和流量掌握$connection->onClose = function ($source) use ($to_connection) { $to_connection->close();};$to_connection->onBufferFull = function ($dest) use ($connection) { $connection->pauseRecv();};$to_connection->onBufferDrain = function ($dest) use ($connection) { $connection->resumeRecv();};
在第一部的代码中,只用两行便实现了这些代码:
// 放行连接,连接内部目标端口$to_connection = new AsyncTcpConnection('tcp:127.0.0.1:' . Config::get('door.port_to'));// 相互转发流量$connection->pipe($to_connection);$to_connection->pipe($connection);
这里利用的是workerman内置的流量转发,它很好用,但是这里我们要统计流量,以是我们手动转发流量。
这里我们将统计的数据存储到缓存里,而不是直接连接数据库更新,这是为了更好的连接性能。我们会其余开启一个进程将这些改动更新到数据库。后面会先容到。
拦截统计我们将第一步中的加载IP白名单的逻辑改成下面这样:
<?php$worker->onConnect = function (TcpConnection $connection) { $disable_cache_key = 'disable_ip_list'; $list_ip = Cache::get($disable_cache_key); if (empty($list_ip)) { $connection->close(); } $remote_ip = $connection->getRemoteIp(); if (!in_array($remote_ip, $list_ip)) { AppIpReject::initRecord($remote_ip); $connection->close(); }};
在这里我们不连接数据库查询,而是直接从本地缓存读取白名单,这样会有更好的性能。我们会在另一个进程中更新这份白名单。
其余我们可以看到,拦截的IP调用了一个静态方法,这里的功能很大略,判断数据库中该IP是否存在,如果不存在则新增,如果存在,则更新拦截次数+·1。这里就不多先容了。这里也没有必要做什么性能优化,反正本来便是拦截的IP,优化个毛。
高性能处理缓存数据上面我们先容,我们会其余开启一个进程,掩护IP白名单,并且将流量统计提交到数据库。这便是这个进程:
<?php$worker_ip = new Worker();$worker_ip->name = 'report';$worker_ip->onWorkerStart = function () { Timer::add(5, function () { $disable_cache_key = 'disable_ip_list'; $list_ip = AppIp::where('status', 1)->column('ip'); Cache::set($disable_cache_key, $list_ip); foreach ($list_ip as $ip) { $ip_md5 = md5($ip); $in_length = Cache::pull("$ip_md5-in"); // 要求的数据 $to_length = Cache::pull("$ip_md5-to"); // 返回的数据 if (!empty($in_length) || !empty($to_length)) { $model_ip = AppIp::where('ip', $ip)->find(); $model_ip->in_buffer += $in_length; $model_ip->to_buffer += $to_length; $model_ip->save(); } } });};
他做的事情很大略,读取缓存,更新数据到数据库,并且更新IP白名单。这里不须要考虑它和数据库之间的性能问题,这是额外的进程,不影响端口的连接和转发。
下一步,更好的性能设计以上,只有几行代码,几个小时(如果不含设计系统的韶光,代码量可能只有一两个小时)。还能再怎么优化呢?实际上还是可以优化的。
更好的内存驱动这里利用的是ThinkPHP内置的文件缓存,存储到磁盘上,以上方法,在大量连接并发时,肯定受制于磁盘的性能。以是自然而然,我们可以利用内存缓存。
但是利用内存缓存,redis可以吗?并不好。这里是客户端,它只是想简大略单实现一个拦截转发,还要再支配redis,不可取。
但实际上,workerman本身内置了数据共享组件,这是一个很好的方案。相称于一个极简的redis。完美符合我们的需求。但是我并没有实现这个功能,目前的系统已经符合我的场景。
更好的客户端目前拦截IP客户端和管理面板集成在一起,利用相同的配置,面板基于ThinkPHP,客户端只是ThinkPHP的一个命令。我之以是这样做,是希望直接在Workerman中利用ThinkPHP的浩瀚特性(数据库、缓存)。
实际上,我们可以将客户真个代码,其余开一个项目,使客户端和面板独立开。在面板上实现通用得API。客户端通过API操作数据。这样客户端就不须要连接数据库。好处多多。
但是这样也带来的更多的事情量,这种情形下,我们自然而然的认为客户真个环境不屈安,以是要做权限认证,登录认证。接口开拓也要写更多的代码。
总结这篇文章紧张先容了我实现IP防火墙的思路。这些技能,须要开拓者有丰富的网站开拓履历,这个哀求不高,但是也要有基本的网络开拓履历,这就有一定的门槛。Workerman非常大略,但是Workerman不是HTTP,这不是一样平常的网站开拓,须要一定的学习和思路转变。但是对付我来说,轻车驾熟。如果我去找其他的方案,学习、支配、测试,可能还不如我自己开拓来更快。
IP白名单是怎么管理的呢,既可以通过面板添加,也可以访问面板的一个页面,自动获取出网IP添加到白名单中,利用体验和很好。
实际上还有更好的办法,那便是做一个rss做事器,自动获取订阅rss的客户单的出网IP加入到白名单。但是我本身没有利用rss的习气,并且手机上也没有好的rss阅读器,也不想每次更新IP白名单都要特意打开它,也就没利用这个方案。
我把它开源了,如果有须要可以参考: https://gitee.com/augushong/ip-door
更多这个别系,跟iptables比较,只是有一个更方便的IP白名单管理体验而已,相称于一个大略堡垒机。他可以实现,将一些端口隐蔽起来,只有“我”能连接。
比如将ssh的端口隐蔽起来,通过ip门禁转发过去。再比如将80端口隐蔽起来,通过ip门禁转发过去。
目前我的系统还没有实现多个端口的同时绑定转发,但是核心的思路是一样的,可以参考利用。
原文标题:IP门禁:手把手教你用PHP实现一个IP防火墙
原文地址:https://phpreturn.com/index/a62e1ddd672933.html
原文平台:PHP武器库
版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站许可任何形式的转载/引用文章,但必须同时注明出处。
标签:connection大略