最近我遇到一個需求,我的一臺伺服器總是遭到埠掃描和惡意登入攻擊,對此可以怎麼辦呢?似乎除了內網隔離、增強密碼認證、證書登入、設定防火牆
這些方案實際上都無法解決我的問題。這是一臺公網伺服器,並沒有什麼複雜的網路結構,所以不能建立內網隔離。調整賬號的密碼策略,自然是一個方案,但是人工操作太麻煩,而且我一般經常換電腦使用,如果修改密碼,公司的和家裡的電腦都要更新,很麻煩。設定防火牆自然是運維的基本操作,但是
那怎麼辦呢,作為一個資深的
整個專案不到幾個小時就研發完了,起碼滿足了我自己的需求,並且實現了這樣幾個特性:
- 多程序
- 支援並行
- 守護行程
- 可以通過網頁面板管理
IP - 流量統計
- 攔截記錄
現在我們來一步一步的實現這個系統。
第一步,首先能夠簡簡單單的過濾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(); }
正如上面程式碼所示,只有簡單幾行,便實現了
第二步,與ThinkPHP 命令列整合在一起
為了專案開發方便,我都會使用
最終實現的命令列效果如下:
執行命令 php think door start php think door start --mode d // 守護行程重新啟動 重新啟動 php think door restart 停止 php think door stop
<?php declare(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 // 向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);
這裡使用的是
這裡我們將統計的資料儲存到快取裡,而不是直接連線資料庫更新,這是為了更好的連線效能。我們會另外開啟一個程序將這些改動更新到資料庫。後面會介紹到。
攔截統計
我們將第一步中的載入
<?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(); } };
在這裡我們不連線資料庫查詢,而是直接從本地快取讀取白名單,這樣會有更好的效能。我們會在另一個程序中更新這份白名單。
另外我們可以看到,攔截的
高效能處理快取資料
上面我們介紹,我們會另外開啟一個程序,維護
<?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(); } } }); };
他做的事情很簡單,讀取快取,更新資料到資料庫,並且更新
下一步,更好的效能設計
以上,只有幾行程式碼,幾個小時(如果不含設計系統的時間,程式碼量可能只有一兩個小時
更好的記憶體驅動
這裡使用的是
但是使用記憶體快取,
但實際上,
更好的使用者端
目前攔截
實際上,我們可以將使用者端的程式碼,另外開一個專案,使使用者端和麵板獨立開。在面板上實現通用得
但是這樣也帶來的更多的工作量,這種情況下,我們自然而然的認為使用者端的環境不安全,所以要做許可權認證,登入認證。介面開發也要寫更多的程式碼。
總結
這篇文章主要介紹了我實現
實際上還有更好的方式,那就是做一個
我把它開源了,如果有需要可以參考: https://gitee.com/augushong/ip-door 。
更多
這個系統,跟
比如將
目前我的系統還沒有實現多個埠的同時繫結轉發,但是核心的思路是一樣的,可以參考使用。
本文系轉載,原文標題:
IP 門禁:手把手教你用PHP 實現一個IP 防火牆原文地址:https://phpreturn.com/index/a62e1ddd672933.html