<?php
/**
* 路由器远程日志查询
* 4类结构化日志:DHCP / NAT / URL审计 / AUTH_USER 用户认证
* 后台服务程序rsyslog,将接收到的日志全部写入日志目录
* 日志目录:/var/log/remote/172.16.8.1/
* PHP7.4
附/etc/rsyslog.conf配置文件:
# /etc/rsyslog.conf configuration file for rsyslog
#
# For more information install rsyslog-doc and see
# /usr/share/doc/rsyslog-doc/html/configuration/index.html
#
# Default logging rules can be found in /etc/rsyslog.d/50-default.conf
#################
#### MODULES ####
#################
module(load=”imuxsock”) # provides support for local system logging
#module(load=”immark”) # provides –MARK– message capability
# provides UDP syslog reception
module(load=”imudp”)
input(type=”imudp” port=”514″)
# provides TCP syslog reception
module(load=”imtcp”)
input(type=”imtcp” port=”514″)
# provides kernel logging support and enable non-kernel klog messages
module(load=”imklog” permitnonkernelfacility=”on”)
###########################
#### GLOBAL DIRECTIVES ####
###########################
#
# Use traditional timestamp format.
# To enable high precision timestamps, comment out the following line.
#
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
# Filter duplicated messages
$RepeatedMsgReduction on
#
# Set the default permissions for all log files.
#
$FileOwner syslog
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022
$PrivDropToUser syslog
$PrivDropToGroup syslog
#
# Where to place spool and state files
#
$WorkDirectory /var/spool/rsyslog
#
# Include all config files in /etc/rsyslog.d/
#
$IncludeConfig /etc/rsyslog.d/*.conf
$FileOwner root
$FileGroup www-data
$FileCreateMode 0644
$DirCreateMode 0755
# 创建模板,按来源IP命名日志文件
template(name=”RemoteLog” type=”string” string=”/var/log/remote/%fromhost-ip%/%$YEAR%%$MONTH%%$DAY%-%$HOUR%.log”)
# 将所有远程日志写入上述模板定义的目录
*.* ?RemoteLog
# 日志文件结束
*/
set_time_limit(180);
ini_set(‘memory_limit’, ‘256M’);
$logBaseDir = ‘/var/log/remote/172.16.8.1/’;
$search = trim($_GET[‘search’] ?? ”);
$logType = trim($_GET[‘logtype’] ?? ‘all’);
$page = max(1, (int)($_GET[‘page’] ?? 1));
$perPage = 100; // 每页100条
$fileFilter = trim($_GET[‘file’] ?? ”);
// ========== 正则规则(4类日志全部匹配完成) ==========
$urlPattern = ‘/%CONTENT_AUDIT-6-URL_LOG:DATA-TIME=(.+?),USER-NAME=(.+?),MAC-ADDRESS=(.+?),SOURCE-IP=(.+?),SOURCE-PORT=(.+?),DESTINATION-IP=(.+?),DESTINATION-PORT=(.+?),PROTOCOL=(.+?),URL=(.+?),SN=(.+)/’;
$dhcpPattern = ‘/%DHCPD-6-IP_ASSIGN:DATA-TIME=(.+?),IP-ADDRESS=(.+?),DATE-OF-LEASE=(.+?),FINISH-OF-LEASE=(.+?),MAC-ADDRESS=(.+?),DEVICE-NAME=(.+?),SN=(.+)/’;
$natPattern = ‘/%NAT-6-NAT_LOG:DATA-TIME=(.+?),USER-NAME=(.+?),MAC-ADDRESS=(.+?),ORI-SRC-IP=(.+?),ORI-SRC-PORT=(.+?),ORI-DES-IP=(.+?),ORI-DES-PORT=(.+?),REP-SRC-IP=(.+?),REP-SRC-PROT=(.+?),REP-DST-IP=(.+?),REP-DES-PORT=(.+?),IP-PROTOCOL=(.+?),ORI-INPUT-BYTES=(.+?),REP-INPUT-BYTES=(.+?),FLOW-DIE-TIME=(.+?),FLOW-CREATE-TIME=(.+?),SN=(.+)/’;
$authPattern = ‘/%AUTH_USER-6-AUTH_USER_LOG:DATA-TIME=(.+?),USER-NAME=(.+?),MAC-ADDRESS=(.+?),IP=(.+?),STATUS=(.+?),REASON=(.+?),SN=(.+)/’;
// 协议号翻译函数
function protoName($num)
{
$map = [
‘1’ => ‘ICMP’,
‘6’ => ‘TCP’,
’17’ => ‘UDP’,
];
return $map[$num] ?? $num;
}
// MAC高亮
function highlightMac($str)
{
if ($str === ’00:00:00:00:00:00′) {
return ‘<span style=”color:#ff5555;font-weight:bold;”>’ . htmlspecialchars($str) . ‘【跨三层无二层MAC】</span>’;
}
return htmlspecialchars($str);
}
// 扫描日志文件
$files = [];
if (is_dir($logBaseDir)) {
$dir = new DirectoryIterator($logBaseDir);
foreach ($dir as $entry) {
if ($entry->isFile() && !$entry->isDot()) {
$fname = $entry->getFilename();
if (!empty($fileFilter) && strpos($fname, $fileFilter) === false) {
continue;
}
$files[] = [
‘name’ => $fname,
‘path’ => $entry->getPathname(),
‘size’ => $entry->getSize(),
‘mtime’ => $entry->getMTime()
];
}
}
// 文件按修改时间倒序
usort($files, function ($a, $b) {
return $b[‘mtime’] – $a[‘mtime’];
});
}
$allRecords = [];
foreach ($files as $f) {
$fp = @fopen($f[‘path’], ‘r’);
if (!$fp) continue;
$lines = [];
while (($line = fgets($fp)) !== false) {
$lines[] = $line;
}
fclose($fp);
// 单文件内日志行反转:文件内最新行放前面
$lines = array_reverse($lines);
foreach ($lines as $line) {
$rawLine = trim($line);
if ($search !== ” && stripos($rawLine, $search) === false) {
continue;
}
$record = [
‘type’ => ‘raw’,
‘file’ => $f[‘name’],
‘raw’ => $rawLine,
‘data’ => []
];
// URL审计日志
if (preg_match($urlPattern, $rawLine, $m)) {
$record[‘type’] = ‘url’;
$record[‘data’] = [
‘time’ => $m[1],
‘username’ => $m[2],
‘mac’ => $m[3],
‘src_ip’ => $m[4],
‘src_port’ => $m[5],
‘dst_ip’ => $m[6],
‘dst_port’ => $m[7],
‘proto’ => $m[8],
‘url’ => $m[9],
‘sn’ => $m[10]
];
}
// DHCP分配日志
elseif (preg_match($dhcpPattern, $rawLine, $m)) {
$record[‘type’] = ‘dhcp’;
$record[‘data’] = [
‘time’ => $m[1],
‘ip’ => $m[2],
‘lease_start’ => $m[3],
‘lease_end’ => $m[4],
‘mac’ => $m[5],
‘dev_name’ => $m[6],
‘sn’ => $m[7]
];
}
// NAT会话日志
elseif (preg_match($natPattern, $rawLine, $m)) {
$record[‘type’] = ‘nat’;
$record[‘data’] = [
‘time’ => $m[1],
‘user’ => $m[2],
‘mac’ => $m[3],
‘ori_src_ip’ => $m[4],
‘ori_src_port’ => $m[5],
‘ori_des_ip’ => $m[6],
‘ori_des_port’ => $m[7],
‘rep_src_ip’ => $m[8],
‘rep_src_prot’ => $m[9],
‘rep_dst_ip’ => $m[10],
‘rep_des_port’ => $m[11],
‘ip_proto’ => $m[12],
‘ori_input_bytes’ => $m[13],
‘rep_input_bytes’ => $m[14],
‘flow_die_time’ => $m[15],
‘flow_create_time’ => $m[16],
‘sn’ => $m[17]
];
}
// 用户认证日志
elseif (preg_match($authPattern, $rawLine, $m)) {
$record[‘type’] = ‘auth’;
$record[‘data’] = [
‘time’ => $m[1],
‘username’ => $m[2],
‘mac’ => $m[3],
‘ip’ => $m[4],
‘status’ => $m[5],
‘reason’ => $m[6],
‘sn’ => $m[7]
];
}
$allRecords[] = $record;
}
}
// 日志类型筛选
if ($logType !== ‘all’) {
$typeMap = [
‘dhcp’ => ‘dhcp’,
‘nat’ => ‘nat’,
‘url’ => ‘url’,
‘auth’ => ‘auth’
];
if (isset($typeMap[$logType])) {
$filterType = $typeMap[$logType];
$allRecords = array_filter($allRecords, function ($r) use ($filterType) {
return $r[‘type’] === $filterType;
});
}
}
// 分页
$total = count($allRecords);
$totalPage = ceil($total / $perPage);
$offset = ($page – 1) * $perPage;
$pageRecords = array_slice($allRecords, $offset, $perPage);
?>
<!DOCTYPE html>
<html lang=”zh-CN”>
<head>
<meta charset=”utf-8″>
<title>路由器远程日志查询|4类日志结构化解析</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:monospace,system-ui;}
body{padding:20px;background:#1a1a1a;color:#eee;}
.box{background:#2b2b2b;padding:16px;border-radius:6px;margin-bottom:16px;}
input,select,button{padding:8px;font-size:14px;}
input[type=”text”],select{width:260px;background:#333;color:#fff;border:1px solid #555;}
button{background:#0066cc;color:#fff;border:none;cursor:pointer;padding:8px 14px;}
table{width:100%;border-collapse:collapse;font-size:13px;margin:8px 0;}
td,th{border:1px solid #444;padding:6px;vertical-align:top;}
th{background:#383838;}
.pager{margin:16px 0;}
.pager a{color:#5cf;text-decoration:none;margin:0 6px;}
.pager a.current{color:#ff6;}
.raw-text{word-break:break-all;color:#aaa;}
h3{margin:8px 0;color:#8cf;}
</style>
</head>
<body>
<h2>路由器日志查询 | 目录:<?=htmlspecialchars($logBaseDir)?></h2>
<div class=”box”>
<form method=”get”>
关键词:<input type=”text” name=”search” value=”<?=htmlspecialchars($search)?>” placeholder=”IP/MAC/URL/用户名/设备名”>
文件筛选:<input type=”text” name=”file” value=”<?=htmlspecialchars($fileFilter)?>” placeholder=”文件名,例:2026-10″>
日志类型:
<select name=”logtype”>
<option value=”all” <?= $logType===’all’ ? ‘selected’ : ” ?>>全部日志</option>
<option value=”dhcp” <?= $logType===’dhcp’ ? ‘selected’ : ” ?>>DHCP</option>
<option value=”nat” <?= $logType===’nat’ ? ‘selected’ : ” ?>>NAT</option>
<option value=”url” <?= $logType===’url’ ? ‘selected’ : ” ?>>URL审计</option>
<option value=”auth” <?= $logType===’auth’ ? ‘selected’ : ” ?>>用户认证</option>
</select>
<button type=”submit”>查询</button>
<?php if(!empty($search) || !empty($fileFilter) || $logType!==’all’):?>
<a href=”?”><button type=”button”>重置</button></a>
<?php endif;?>
</form>
<div style=”margin-top:10px;”>
总匹配记录:<b><?=$total?></b> 条,当前第<?=$page?>/<?=$totalPage?>页,每页<?=$perPage?>条
</div>
</div>
<?php
foreach ($pageRecords as $r) {
if ($r[‘type’] === ‘url’) {
$d = $r[‘data’];
?>
<div class=”box”>
<h3>📄 URL访问审计 | 文件:<?=htmlspecialchars($r[‘file’])?></h3>
<table>
<tr>
<th>审计时间</th>
<th>内网源IP</th>
<th>MAC地址</th>
<th>源端口</th>
<th>目的IP</th>
<th>目的端口</th>
<th>协议</th>
<th>URL</th>
<th>设备SN</th>
</tr>
<tr>
<td><?=htmlspecialchars($d[‘time’])?></td>
<td><?=htmlspecialchars($d[‘src_ip’])?></td>
<td><?=highlightMac($d[‘mac’])?></td>
<td><?=htmlspecialchars($d[‘src_port’])?></td>
<td><?=htmlspecialchars($d[‘dst_ip’])?></td>
<td><?=htmlspecialchars($d[‘dst_port’])?></td>
<td><?=protoName($d[‘proto’])?></td>
<td class=”raw-text”><?=htmlspecialchars($d[‘url’])?></td>
<td><?=htmlspecialchars($d[‘sn’])?></td>
</tr>
</table>
</div>
<?php
} elseif ($r[‘type’] === ‘dhcp’) {
$d = $r[‘data’];
?>
<div class=”box”>
<h3>🟢 DHCP地址分配 | 文件:<?=htmlspecialchars($r[‘file’])?></h3>
<table>
<tr>
<th>事件时间</th>
<th>分配IP</th>
<th>租约开始</th>
<th>租约到期</th>
<th>MAC地址</th>
<th>设备名称</th>
<th>设备SN</th>
</tr>
<tr>
<td><?=htmlspecialchars($d[‘time’])?></td>
<td><?=htmlspecialchars($d[‘ip’])?></td>
<td><?=htmlspecialchars($d[‘lease_start’])?></td>
<td><?=htmlspecialchars($d[‘lease_end’])?></td>
<td><?=highlightMac($d[‘mac’])?></td>
<td><?=htmlspecialchars($d[‘dev_name’])?></td>
<td><?=htmlspecialchars($d[‘sn’])?></td>
</tr>
</table>
</div>
<?php
} elseif ($r[‘type’] === ‘nat’) {
$d = $r[‘data’];
?>
<div class=”box”>
<h3>🔵 NAT会话日志 | 文件:<?=htmlspecialchars($r[‘file’])?></h3>
<table>
<tr>
<th>日志时间</th>
<th>内网IP</th>
<th>MAC</th>
<th>原始源IP</th>
<th>原始源端口</th>
<th>原始目的IP</th>
<th>原始目的端口</th>
<th>NAT后公网IP</th>
<th>会话创建时间</th>
<th>会话销毁时间</th>
<th>协议</th>
<th>SN</th>
</tr>
<tr>
<td><?=htmlspecialchars($d[‘time’])?></td>
<td><?=htmlspecialchars($d[‘user’])?></td>
<td><?=highlightMac($d[‘mac’])?></td>
<td><?=htmlspecialchars($d[‘ori_src_ip’])?></td>
<td><?=htmlspecialchars($d[‘ori_src_port’])?></td>
<td><?=htmlspecialchars($d[‘ori_des_ip’])?></td>
<td><?=htmlspecialchars($d[‘ori_des_port’])?></td>
<td><?=htmlspecialchars($d[‘rep_dst_ip’])?></td>
<td><?=htmlspecialchars($d[‘flow_create_time’])?></td>
<td><?=htmlspecialchars($d[‘flow_die_time’])?></td>
<td><?=protoName($d[‘ip_proto’])?></td>
<td><?=htmlspecialchars($d[‘sn’])?></td>
</tr>
</table>
</div>
<?php
} elseif ($r[‘type’] === ‘auth’) {
$d = $r[‘data’];
?>
<div class=”box”>
<h3>🟡 用户认证日志 | 文件:<?=htmlspecialchars($r[‘file’])?></h3>
<table>
<tr>
<th>认证时间</th>
<th>账号</th>
<th>MAC地址</th>
<th>接入IP</th>
<th>登录状态</th>
<th>原因</th>
<th>设备SN</th>
</tr>
<tr>
<td><?=htmlspecialchars($d[‘time’])?></td>
<td><?=htmlspecialchars($d[‘username’])?></td>
<td><?=highlightMac($d[‘mac’])?></td>
<td><?=htmlspecialchars($d[‘ip’])?></td>
<td><?=htmlspecialchars($d[‘status’])?></td>
<td><?=htmlspecialchars($d[‘reason’])?></td>
<td><?=htmlspecialchars($d[‘sn’])?></td>
</tr>
</table>
</div>
<?php
} else {
?>
<div class=”box”>
<h3>📜 原始日志 | 文件:<?=htmlspecialchars($r[‘file’])?></h3>
<div class=”raw-text”><?=htmlspecialchars($r[‘raw’])?></div>
</div>
<?php
}
}
?>
<div class=”pager box”>
<?php
for($i=1;$i<=$totalPage;$i++){
$query = http_build_query([
‘search’ => $search,
‘file’ => $fileFilter,
‘logtype’ => $logType,
‘page’ => $i
]);
if($i == $page){
echo “<span class=’current’>$i</span> “;
}else{
echo “<a href=’?$query’>$i</a> “;
}
}
?>
</div>
</body>
</html>