您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > php

IP门禁:手把手教你用PHP实现一个IP防火墙

时间:2022-07-28 14:43:09  来源:  作者:PHP武器库

最近我遇到一个需求,我的一台服务器总是遭到端口扫描和恶意登录攻击,对此可以怎么办呢?似乎除了内网隔离、增强密码认证、证书登录、设置防火墙iptables,网上找不到什么别的方案,对了,还用堡垒机的方案。

这些方案实际上都无法解决我的问题。这是一台公网服务器,并没有什么复杂的网络结构,所以不能建立内网隔离。调整账号的密码策略,自然是一个方案,但是人工操作太麻烦,而且我一般经常换电脑使用,如果修改密码,公司的和家里的电脑都要更新,很麻烦。设置防火墙自然是运维的基本操作,但是iptables的配置太麻烦,ufw工具还好些,firewall-cmd就麻烦些,而且有一个巨大的痛点,众所周知,大家的出网IP都会经常变,好不容易在命令行里一个字母一个字母的配置好了,睡了一觉,白费了。堡垒机更不是一个主流的方案,有点大材小用,用了堡垒机,反而不能随意使用系统,更何况还没听说过那个免费的堡垒机呢。

那怎么办呢,作为一个资深的php开发者,服务器这块的应用还不是手到擒来,当初连内网穿透都能轻松实现,一个IP过滤系统,小意思。所以我打算自己开发这样一个项目,首先能够实现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 appcommoncommand;use thinkconsoleCommand;use thinkconsoleInput;use thinkconsoleinputArgument;use thinkconsoleinputOption;use thinkconsoleOutput;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武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。



Tags:IP防火墙   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
IP门禁:手把手教你用PHP实现一个IP防火墙
最近我遇到一个需求,我的一台服务器总是遭到端口扫描和恶意登录攻击,对此可以怎么办呢?似乎除了内网隔离、增强密码认证、证书登录、设置防火墙iptables,网上找不到什么别的方案...【详细内容】
2022-07-28  Search: IP防火墙  点击:(382)  评论:(0)  加入收藏
▌简易百科推荐
PHP 8.3 新特性解读
作者 | Deepak Vohra译者 | 明知山策划 | 丁晓昀本文是 PHP 8.x 系列文章的一部分。你可以通过订阅 RSS 来接收有关本系列文章的更新通知。PHP 仍然是互联网上使用最广泛的...【详细内容】
2024-03-12    InfoQ  Tags:PHP 8.3   点击:(30)  评论:(0)  加入收藏
如何使用PHP SSH2模块执行远程Linux命令
PHP SSH2扩展是用于在PHP程序中使用SSH(安全壳协议)的一种扩展。它允许建立加密连接和执行远程命令、上传和下载文件等操作,十分方便实用。下面我将为大家详细介绍一下该扩展的...【详细内容】
2024-01-26  开源技术小栈  微信公众号  Tags:PHP   点击:(115)  评论:(0)  加入收藏
如何使用PHP抓取百度首页排名?方法与步骤详解
PHP是一种广泛应用于网站开发的脚本语言,具备简单、灵活、高效的特点,因此在SEO优化中也得到了广泛的应用。本文将深入解析如何使用PHP抓取百度首页排名,并介绍一些相关的内容...【详细内容】
2024-01-12  ·小钟无艳遇  今日头条  Tags:php   点击:(86)  评论:(0)  加入收藏
2023 年,PHP 停滞不前
热心开发者分析了一波 GitHub 的数据后发现,编程语言为 PHP 的 PR 数量逐年下降。GitHut 是通过 GitHub 数据专门分析编程语言的项目。它基于各种编程语言在 GitHub 中的使用...【详细内容】
2023-12-17  OSC开源社区    Tags:PHP   点击:(23)  评论:(0)  加入收藏
全方位深度剖析PHP7底层源码
PHP7是一门流行的Web编程语言,以其易学易用和广泛的应用场景而备受欢迎。本文将为您深入讲解PHP7的特性和优势,包括性能提升、语法改进和新特性等方面,帮助您了解和掌握这门灵...【详细内容】
2023-12-06  笔画春秋润    Tags:PHP7   点击:(188)  评论:(0)  加入收藏
PHP 8.3 正式发布!
作者 | Tim Anderson编译 | 如烟出品 | 51CTO技术栈(微信号:blog51cto)PHP 8.3 正式发布,最主要的变化是添加“类常量显式类型”、“只读属性深拷贝”,“以及对随机性功能的补充...【详细内容】
2023-11-27    51CTO  Tags:PHP   点击:(171)  评论:(0)  加入收藏
原来真的可以在 Next.js 中写 PHP 代码?
Next.js 14 近期发布,其中一个重大的功能点是 Server Actions 成为稳定版,因其超前和熟悉的开发方式,在社交网络上引起了一阵讨论,应该是下面这张图的内容了。图片有人说这是又...【详细内容】
2023-11-07  编程界  微信公众号  Tags:PHP   点击:(297)  评论:(0)  加入收藏
PHP编程语言,这个老古董,还有人用吗?
近年来,随着大数据、云计算和人工智能等新兴技术的崛起,许多传统的编程语言悄然退出了舞台。而PHP(PHP Hypertext Preprocessor)作为最古老的编程语言之一,是否也在逐渐沦为过去...【详细内容】
2023-11-02  程序员职场故事    Tags:PHP编程   点击:(236)  评论:(0)  加入收藏
PHP如何获取前几天日期时间
在PHP中,我们经常需要获取当前日期的前几天日期时间,以便进行一些特定的计算或数据处理。本文将介绍如何使用PHP获取前几天的日期时间,并提供具体的实现示例和代码。文章地址ht...【详细内容】
2023-08-10  学无止境    Tags:PHP   点击:(388)  评论:(0)  加入收藏
PHP+Python轻松抓取网络图片资源
你是否曾经遇到过需要从网络上获取大量图片的需求?你是否曾经为手动下载图片而感到疲惫?那么,本文将为你介绍一种高效、易用的方法&mdash;&mdash;利用PHP和Python编写爬虫程序,...【详细内容】
2023-05-09  俊俊的生活日记    Tags:PHP   点击:(337)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条