-
所有IP來源:
-
- {Object.entries(ipInfo.debug.allIpSources).map(([key, value]) => (
-
- {key}:
-
- {value || 'null'}
-
-
- ))}
+
+ {Object.entries(ipInfo.debug.allIpSources).map(([source, value]) => (
+
+ {source}:
+
+ {value || '未設置'}
+
-
-
-
-
-
-
請求信息:
-
-
- Host:
-
- {ipInfo.debug.host || 'null'}
-
-
-
- Referer:
-
- {ipInfo.debug.referer || 'null'}
-
-
-
- User Agent:
-
- {ipInfo.debug.userAgent || 'null'}
-
-
-
- 時間戳:
-
- {new Date(ipInfo.timestamp).toLocaleString('zh-TW')}
-
-
-
-
+ ))}
+ {/* 本地開發環境信息 */}
+ {ipInfo.development && (
+
+
+
+ {ipInfo.development.message}
+
+ {ipInfo.development.suggestions.map((suggestion, index) => (
+
• {suggestion}
+ ))}
+
+
+
+ )}
+
{/* 地理位置信息 */}
{ipInfo.location && (
-
-
- 地理位置信息
-
+ 地理位置信息
+
+ IP地址的地理位置信息
+
-
-
-
位置信息
-
-
國家: {ipInfo.location.country} ({ipInfo.location.countryCode})
-
地區: {ipInfo.location.regionName}
-
城市: {ipInfo.location.city}
-
郵遞區號: {ipInfo.location.zip}
-
時區: {ipInfo.location.timezone}
-
-
-
-
網路信息
-
-
ISP: {ipInfo.location.isp}
-
組織: {ipInfo.location.org}
-
AS: {ipInfo.location.as}
-
行動網路: {ipInfo.location.mobile ? '是' : '否'}
-
代理: {ipInfo.location.proxy ? '是' : '否'}
-
主機服務: {ipInfo.location.hosting ? '是' : '否'}
-
-
-
+
+ {JSON.stringify(ipInfo.location, null, 2)}
+
)}
+ {/* 其他調試信息 */}
+
+
+ 其他調試信息
+
+ 額外的調試和環境信息
+
+
+
+
+
+
+
主機
+
{ipInfo.debug.host || '未設置'}
+
+
+
引用來源
+
{ipInfo.debug.referer || '未設置'}
+
+
+
+
用戶代理
+
{ipInfo.debug.userAgent || '未設置'}
+
+
+
時間戳
+
{ipInfo.timestamp}
+
+
+
+
+
{/* 操作按鈕 */}
-
+
+
diff --git a/app/test/ip-format-test/page.tsx b/app/test/ip-format-test/page.tsx
new file mode 100644
index 0000000..0d37813
--- /dev/null
+++ b/app/test/ip-format-test/page.tsx
@@ -0,0 +1,246 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
+import { Alert, AlertDescription } from '@/components/ui/alert'
+import { Globe, RefreshCw, CheckCircle, AlertCircle, Info } from 'lucide-react'
+
+interface IpTestResult {
+ originalIp: string
+ cleanedIp: string
+ isIPv4: boolean
+ isIPv6: boolean
+ isLocalhost: boolean
+ description: string
+}
+
+export default function IpFormatTestPage() {
+ const [testResults, setTestResults] = useState
([])
+ const [loading, setLoading] = useState(false)
+
+ // 測試IP地址清理函數
+ function cleanIpForDisplay(ip: string): string {
+ if (!ip) return '127.0.0.1';
+
+ // 移除空白字符
+ ip = ip.trim();
+
+ // 處理IPv6格式的IPv4地址 (例如: ::ffff:192.168.1.1)
+ if (ip.startsWith('::ffff:')) {
+ return ip.substring(7);
+ }
+
+ // 處理純IPv6本地回環地址
+ if (ip === '::1') {
+ return '127.0.0.1';
+ }
+
+ // 驗證是否為有效的IPv4地址
+ const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ if (ipv4Regex.test(ip)) {
+ return ip;
+ }
+
+ // 如果不是有效的IPv4,返回默認值
+ return '127.0.0.1';
+ }
+
+ // 檢查IP類型
+ function analyzeIp(ip: string): { isIPv4: boolean; isIPv6: boolean; isLocalhost: boolean; description: string } {
+ const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
+
+ const isIPv4 = ipv4Regex.test(ip);
+ const isIPv6 = ipv6Regex.test(ip) || ip.startsWith('::ffff:') || ip === '::1';
+ const isLocalhost = ip === '127.0.0.1' || ip === '::1' || ip === 'localhost';
+
+ let description = '';
+ if (ip.startsWith('::ffff:')) {
+ description = 'IPv6格式的IPv4地址';
+ } else if (ip === '::1') {
+ description = 'IPv6本地回環地址';
+ } else if (isIPv4) {
+ description = '標準IPv4地址';
+ } else if (isIPv6) {
+ description = 'IPv6地址';
+ } else {
+ description = '無效的IP地址格式';
+ }
+
+ return { isIPv4, isIPv6, isLocalhost, description };
+ }
+
+ const runTests = () => {
+ setLoading(true);
+
+ const testIps = [
+ '::ffff:127.0.0.1',
+ '::1',
+ '127.0.0.1',
+ '192.168.1.1',
+ '::ffff:192.168.1.100',
+ '2001:db8::1',
+ 'invalid-ip',
+ 'localhost',
+ '::ffff:203.0.113.1',
+ '10.0.0.1'
+ ];
+
+ const results: IpTestResult[] = testIps.map(originalIp => {
+ const cleanedIp = cleanIpForDisplay(originalIp);
+ const analysis = analyzeIp(originalIp);
+
+ return {
+ originalIp,
+ cleanedIp,
+ ...analysis
+ };
+ });
+
+ setTestResults(results);
+ setLoading(false);
+ };
+
+ useEffect(() => {
+ runTests();
+ }, []);
+
+ return (
+
+
+
IPv4 格式測試
+
測試IP地址清理和IPv4格式轉換功能
+
+
+ {/* 說明 */}
+
+
+
+ 測試目的
+
+
• 驗證IPv6格式的IPv4地址能正確轉換為IPv4
+
• 確保所有IP地址都顯示為標準IPv4格式
+
• 測試各種IP地址格式的處理邏輯
+
• 驗證本地回環地址的正確處理
+
+
+
+
+ {/* 測試結果 */}
+
+
+
+
+ IP格式轉換測試結果
+
+
+ 各種IP地址格式的清理和轉換結果
+
+
+
+
+ {testResults.map((result, index) => (
+
+
+
+ 原始IP:
+
+ {result.originalIp}
+
+
+
+ 清理後:
+
+ {result.cleanedIp}
+
+
+
+
+
+
+ {result.isIPv4 ? (
+
+ ) : (
+
+ )}
+
IPv4: {result.isIPv4 ? '是' : '否'}
+
+
+
+ {result.isIPv6 ? (
+
+ ) : (
+ -
+ )}
+ IPv6: {result.isIPv6 ? '是' : '否'}
+
+
+
+ {result.isLocalhost ? (
+
+ ) : (
+ -
+ )}
+ 本地: {result.isLocalhost ? '是' : '否'}
+
+
+
+
+ {result.description}
+
+
+ ))}
+
+
+
+
+ {/* 操作按鈕 */}
+
+
+
+
+ {/* 總結 */}
+
+
+ 測試總結
+
+ IPv4格式轉換的關鍵點
+
+
+
+
+
+
✅ 正確處理的格式
+
+ - •
::ffff:127.0.0.1
→ 127.0.0.1
+ - •
::1
→ 127.0.0.1
+ - •
127.0.0.1
→ 127.0.0.1
(保持不變)
+ - •
192.168.1.1
→ 192.168.1.1
(保持不變)
+
+
+
+
+
⚠️ 無效格式處理
+
+ - • 無效IP地址 →
127.0.0.1
(默認值)
+ - • 空值或null →
127.0.0.1
(默認值)
+
+
+
+
+
🎯 目標
+
+ 確保所有顯示的IP地址都是標準的IPv4格式,提供一致且易於理解的用戶體驗。
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/test/ip-test/page.tsx b/app/test/ip-test/page.tsx
index 460a2b1..ae1ca99 100644
--- a/app/test/ip-test/page.tsx
+++ b/app/test/ip-test/page.tsx
@@ -186,6 +186,10 @@ ALLOWED_IPS=你的真實IP地址,其他允許的IP`}
詳細IP調試
+
- {ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
-
- ) : (
-
- )}
-
{ipInfo.ip}
- {ipInfo.enableIpWhitelist && (
-
- {ipInfo.isAllowed ? '允許' : '拒絕'}
-
+
+
+ {ipInfo.enableIpWhitelist && !ipInfo.isAllowed ? (
+
+ ) : (
+
+ )}
+
+
+
+ {isIPv6Mapped ? 'IPv6' : 'IPv4'}: {displayIp}
+
+ {isIPv6Mapped && (
+
+ {ipv6Format}
+
+ )}
+
+
+ {ipInfo.enableIpWhitelist && (
+
+ {ipInfo.isAllowed ? '允許' : '拒絕'}
+
+ )}
+
+ {/* IPv6格式切換按鈕 */}
+
+
+
+ {/* 詳細信息彈出框 */}
+ {showIPv6Format && (
+
+
+
+ IPv4格式:
+ {displayIp}
+
+
+ IPv6格式:
+ {ipv6Format}
+
+ {originalFormat && originalFormat !== displayIp && (
+
+ 原始格式:
+ {originalFormat}
+
+ )}
+ {ipInfo.debug?.ipDetectionMethod && (
+
+ 檢測方法:
+ {ipInfo.debug.ipDetectionMethod}
+
+ )}
+ {ipInfo.debug?.allFoundIps && ipInfo.debug.allFoundIps.length > 0 && (
+
+
所有檢測到的IP:
+
+ {ipInfo.debug.allFoundIps.map((ip, index) => (
+
+ {ip}
+
+ ))}
+
+
+ )}
+ {ipInfo.ipv6Info?.hasIPv6Support && (
+
+ ✓ IPv6支援已啟用
+
+ )}
+
+
)}
)
diff --git a/lib/ip-utils.ts b/lib/ip-utils.ts
index d15b55e..78fb480 100644
--- a/lib/ip-utils.ts
+++ b/lib/ip-utils.ts
@@ -66,6 +66,11 @@ function cleanIpAddress(ip: string): string | null {
ip = ip.substring(7);
}
+ // 處理純IPv6本地回環地址
+ if (ip === '::1') {
+ return '127.0.0.1';
+ }
+
// 驗證IP格式
if (!isValidIp(ip)) {
return null;
@@ -246,6 +251,24 @@ export function getDetailedIpInfo(req: any): {
}
});
+ // 如果沒有找到任何IP,檢查是否有IPv6格式的地址
+ if (allFoundIps.length === 0) {
+ Object.values(ipSources).forEach(ipSource => {
+ if (ipSource) {
+ const ipList = ipSource.toString().split(',').map(ip => ip.trim());
+ ipList.forEach(ip => {
+ // 直接處理IPv6格式的IPv4地址
+ if (ip.startsWith('::ffff:')) {
+ const cleanIp = ip.substring(7);
+ if (isValidIp(cleanIp) && !allFoundIps.includes(cleanIp)) {
+ allFoundIps.push(cleanIp);
+ }
+ }
+ });
+ }
+ });
+ }
+
// 選擇最佳IP
for (const ip of allFoundIps) {
if (isPublicIp(ip)) {
diff --git a/scripts/test-ip-detection.js b/scripts/test-ip-detection.js
index 1195248..62bd7d3 100644
--- a/scripts/test-ip-detection.js
+++ b/scripts/test-ip-detection.js
@@ -3,158 +3,43 @@
* 用於測試和驗證IP白名單功能
*/
-const { getClientIp, isIpAllowed, isValidIp, isValidCidr } = require('../lib/ip-utils.ts');
+const { getClientIp, getDetailedIpInfo, cleanIpAddress } = require('../lib/ip-utils.ts');
// 模擬請求對象
-function createMockRequest(headers = {}) {
- return {
- headers,
- connection: { remoteAddress: '192.168.1.100' },
- socket: { remoteAddress: '192.168.1.100' },
- ip: '192.168.1.100'
- };
-}
+const mockRequest = {
+ headers: {
+ 'x-forwarded-for': '::ffff:127.0.0.1, 192.168.1.100',
+ 'x-real-ip': '::ffff:127.0.0.1',
+ 'x-client-ip': '::1',
+ 'connection': {
+ 'remoteAddress': '::ffff:127.0.0.1'
+ },
+ 'socket': {
+ 'remoteAddress': '::1'
+ }
+ },
+ ip: '::ffff:127.0.0.1'
+};
-// 測試IP檢測功能
-function testIpDetection() {
- console.log('🧪 開始測試IP檢測功能...\n');
+console.log('=== IP 檢測測試 ===');
- // 測試1: 基本IP檢測
- console.log('📋 測試1: 基本IP檢測');
- const basicRequest = createMockRequest({
- 'x-forwarded-for': '203.0.113.1, 192.168.1.100'
- });
- const detectedIp = getClientIp(basicRequest);
- console.log(`檢測到的IP: ${detectedIp}`);
- console.log(`預期結果: 203.0.113.1 (公網IP)`);
- console.log(`實際結果: ${detectedIp === '203.0.113.1' ? '✅ 通過' : '❌ 失敗'}\n`);
+// 測試 cleanIpAddress 函數
+console.log('\n1. 測試 cleanIpAddress 函數:');
+console.log('::ffff:127.0.0.1 ->', cleanIpAddress('::ffff:127.0.0.1'));
+console.log('::1 ->', cleanIpAddress('::1'));
+console.log('127.0.0.1 ->', cleanIpAddress('127.0.0.1'));
+console.log('192.168.1.1 ->', cleanIpAddress('192.168.1.1'));
- // 測試2: Cloudflare代理
- console.log('📋 測試2: Cloudflare代理');
- const cloudflareRequest = createMockRequest({
- 'cf-connecting-ip': '203.0.113.2'
- });
- const cfIp = getClientIp(cloudflareRequest);
- console.log(`檢測到的IP: ${cfIp}`);
- console.log(`預期結果: 203.0.113.2`);
- console.log(`實際結果: ${cfIp === '203.0.113.2' ? '✅ 通過' : '❌ 失敗'}\n`);
+// 測試詳細IP信息
+console.log('\n2. 測試詳細IP信息:');
+const detailedInfo = getDetailedIpInfo(mockRequest);
+console.log('檢測到的IP:', detailedInfo.detectedIp);
+console.log('所有找到的IP:', detailedInfo.allFoundIps);
+console.log('IP來源:', detailedInfo.ipSources);
- // 測試3: 本地開發環境
- console.log('📋 測試3: 本地開發環境');
- const localRequest = createMockRequest({
- 'x-forwarded-for': '127.0.0.1'
- });
- const localIp = getClientIp(localRequest);
- console.log(`檢測到的IP: ${localIp}`);
- console.log(`預期結果: 127.0.0.1 (本地回環)`);
- console.log(`實際結果: ${localIp === '127.0.0.1' ? '✅ 通過' : '❌ 失敗'}\n`);
+// 測試客戶端IP獲取
+console.log('\n3. 測試客戶端IP獲取:');
+const clientIp = getClientIp(mockRequest);
+console.log('最終檢測到的IP:', clientIp);
- // 測試4: 多個代理IP
- console.log('📋 測試4: 多個代理IP');
- const multiProxyRequest = createMockRequest({
- 'x-forwarded-for': '203.0.113.3, 10.0.0.1, 192.168.1.1'
- });
- const multiIp = getClientIp(multiProxyRequest);
- console.log(`檢測到的IP: ${multiIp}`);
- console.log(`預期結果: 203.0.113.3 (第一個公網IP)`);
- console.log(`實際結果: ${multiIp === '203.0.113.3' ? '✅ 通過' : '❌ 失敗'}\n`);
-}
-
-// 測試IP白名單功能
-function testIpWhitelist() {
- console.log('🔒 開始測試IP白名單功能...\n');
-
- const allowedIps = '114.33.18.13,125.229.65.83,192.168.1.0/24';
-
- // 測試1: 單一IP匹配
- console.log('📋 測試1: 單一IP匹配');
- const testIp1 = '114.33.18.13';
- const result1 = isIpAllowed(testIp1, allowedIps);
- console.log(`測試IP: ${testIp1}`);
- console.log(`預期結果: true (在白名單中)`);
- console.log(`實際結果: ${result1 ? '✅ 通過' : '❌ 失敗'}\n`);
-
- // 測試2: CIDR範圍匹配
- console.log('📋 測試2: CIDR範圍匹配');
- const testIp2 = '192.168.1.100';
- const result2 = isIpAllowed(testIp2, allowedIps);
- console.log(`測試IP: ${testIp2}`);
- console.log(`預期結果: true (在192.168.1.0/24範圍內)`);
- console.log(`實際結果: ${result2 ? '✅ 通過' : '❌ 失敗'}\n`);
-
- // 測試3: 不在白名單中的IP
- console.log('📋 測試3: 不在白名單中的IP');
- const testIp3 = '203.0.113.1';
- const result3 = isIpAllowed(testIp3, allowedIps);
- console.log(`測試IP: ${testIp3}`);
- console.log(`預期結果: false (不在白名單中)`);
- console.log(`實際結果: ${!result3 ? '✅ 通過' : '❌ 失敗'}\n`);
-
- // 測試4: 空白名單
- console.log('📋 測試4: 空白名單');
- const testIp4 = '203.0.113.1';
- const result4 = isIpAllowed(testIp4, '');
- console.log(`測試IP: ${testIp4}`);
- console.log(`預期結果: true (空白名單允許所有IP)`);
- console.log(`實際結果: ${result4 ? '✅ 通過' : '❌ 失敗'}\n`);
-}
-
-// 測試IP格式驗證
-function testIpValidation() {
- console.log('🔍 開始測試IP格式驗證...\n');
-
- // 測試有效IP
- console.log('📋 測試有效IP格式');
- const validIps = ['192.168.1.1', '10.0.0.1', '203.0.113.1'];
- validIps.forEach(ip => {
- const isValid = isValidIp(ip);
- console.log(`${ip}: ${isValid ? '✅ 有效' : '❌ 無效'}`);
- });
- console.log('');
-
- // 測試無效IP
- console.log('📋 測試無效IP格式');
- const invalidIps = ['192.168.1.256', '10.0.0', 'invalid', '192.168.1.1.1'];
- invalidIps.forEach(ip => {
- const isValid = isValidIp(ip);
- console.log(`${ip}: ${!isValid ? '✅ 正確拒絕' : '❌ 錯誤接受'}`);
- });
- console.log('');
-
- // 測試CIDR格式
- console.log('📋 測試CIDR格式');
- const validCidrs = ['192.168.1.0/24', '10.0.0.0/8', '172.16.0.0/12'];
- validCidrs.forEach(cidr => {
- const isValid = isValidCidr(cidr);
- console.log(`${cidr}: ${isValid ? '✅ 有效' : '❌ 無效'}`);
- });
- console.log('');
-}
-
-// 主測試函數
-function runAllTests() {
- console.log('🚀 IP白名單功能測試套件\n');
- console.log('=' * 50);
-
- try {
- testIpDetection();
- testIpWhitelist();
- testIpValidation();
-
- console.log('🎉 所有測試完成!');
- } catch (error) {
- console.error('❌ 測試過程中發生錯誤:', error);
- }
-}
-
-// 如果直接運行此腳本
-if (require.main === module) {
- runAllTests();
-}
-
-module.exports = {
- testIpDetection,
- testIpWhitelist,
- testIpValidation,
- runAllTests
-};
\ No newline at end of file
+console.log('\n=== 測試完成 ===');
\ No newline at end of file