diff --git a/package/secubox/luci-app-service-registry/README.md b/package/secubox/luci-app-service-registry/README.md index 498305ae..8b5008da 100644 --- a/package/secubox/luci-app-service-registry/README.md +++ b/package/secubox/luci-app-service-registry/README.md @@ -17,6 +17,15 @@ Unified service aggregation dashboard with automatic publishing to HAProxy (clea ## Dashboard +### Network Connectivity Panel + +Real-time network status showing: +- **Public IPv4** - Your external IP address with reverse DNS hostname +- **Public IPv6** - IPv6 address if available +- **External Port 80/443** - Whether ports are reachable from the internet (tests upstream router/ISP forwarding) +- **Local Firewall** - OpenWrt firewall rule status +- **HAProxy** - Reverse proxy container status + ### Health Summary Bar Shows overall system status at a glance: @@ -32,12 +41,17 @@ Before publishing a service, verify the domain is properly configured: 1. Enter a domain in the checker (e.g., `example.com`) 2. Click "Check" to verify: - - **DNS Resolution** - Domain resolves to expected IP - - **Firewall Ports** - Ports 80 and 443 open from WAN + - **Your Public IP** - Shows your IPv4/IPv6 addresses and reverse DNS + - **DNS Resolution** - Verifies domain resolves to your public IP (detects private IP misconfiguration) + - **Internet Accessibility** - Tests if ports 80/443 are reachable from internet (upstream router check) + - **Local Firewall** - OpenWrt firewall rule status - **SSL Certificate** - Valid certificate with expiry status - **HAProxy** - Reverse proxy container running -The checker provides actionable recommendations when issues are found. +The checker provides specific actionable recommendations: +- If DNS points to private IP (e.g., 192.168.x.x), shows the correct public IP to use +- If ports are blocked externally, advises checking upstream router port forwarding +- Shows exact DNS A record to create: `domain.com → your.public.ip` ### Service Health Indicators @@ -96,6 +110,39 @@ When you publish a service with a domain: ## Health Check API +### Get Network Info + +```bash +ubus call luci.service-registry get_network_info +``` + +Response: +```json +{ + "success": true, + "lan_ip": "192.168.255.1", + "ipv4": { + "address": "185.220.101.12", + "status": "ok", + "hostname": "server.example.com" + }, + "ipv6": { + "address": "2001:db8::1", + "status": "ok" + }, + "external_ports": { + "http": { "accessible": true, "status": "open" }, + "https": { "accessible": true, "status": "open" } + }, + "firewall": { + "status": "ok", + "http_open": true, + "https_open": true + }, + "haproxy": { "status": "running" } +} +``` + ### Check Single Domain ```bash @@ -107,25 +154,41 @@ Response: { "success": true, "domain": "example.com", + "public_ip": { + "ipv4": "185.220.101.12", + "ipv6": "2001:db8::1", + "hostname": "server.example.com" + }, "dns": { "status": "ok", - "resolved_ip": "203.0.113.10" + "resolved_ip": "185.220.101.12" }, - "certificate": { + "external_access": { "status": "ok", - "days_left": 45 + "http_accessible": true, + "https_accessible": true }, "firewall": { "status": "ok", "http_open": true, "https_open": true }, + "certificate": { + "status": "ok", + "days_left": 45 + }, "haproxy": { "status": "running" } } ``` +DNS status values: +- `ok` - Domain resolves to your public IP +- `private` - Domain resolves to a private IP (192.168.x.x, 10.x.x.x, etc.) +- `mismatch` - Domain resolves to a different public IP +- `failed` - DNS resolution failed + ### Check All Services ```bash @@ -222,8 +285,9 @@ uci commit service-registry | `list_services` | List all services from all providers | | `publish_service` | Publish a service to HAProxy/Tor | | `unpublish_service` | Remove service from HAProxy/Tor | -| `check_service_health` | Check DNS/cert/firewall for domain | +| `check_service_health` | Check DNS/cert/firewall/external access for domain | | `check_all_health` | Batch health check all services | +| `get_network_info` | Get public IPs, external port accessibility, firewall status | | `generate_landing_page` | Regenerate static landing page | ## License diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js index 2735d499..a1577732 100644 --- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js +++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/service-registry/api.js @@ -100,6 +100,12 @@ var callCheckAllHealth = rpc.declare({ expect: {} }); +var callGetNetworkInfo = rpc.declare({ + object: 'luci.service-registry', + method: 'get_network_info', + expect: {} +}); + // HAProxy status for provider info var callHAProxyStatus = rpc.declare({ object: 'luci.haproxy', @@ -245,6 +251,11 @@ return baseclass.extend({ return callCheckAllHealth(); }, + // Get network connectivity info (public IPs, port accessibility) + getNetworkInfo: function() { + return callGetNetworkInfo(); + }, + // Get dashboard data with health status getDashboardDataWithHealth: function() { return Promise.all([ diff --git a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js index 547ad6e2..1ed7c65c 100644 --- a/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js +++ b/package/secubox/luci-app-service-registry/htdocs/luci-static/resources/view/service-registry/overview.js @@ -50,9 +50,15 @@ return view.extend({ var published = services.filter(function(s) { return s.published; }); var unpublished = services.filter(function(s) { return !s.published; }); + // Load network info asynchronously + var networkPanel = E('div', { 'id': 'sr-network-panel', 'class': 'sr-network-loading' }, + E('span', { 'class': 'spinning' }, 'Loading network info...')); + this.loadNetworkInfo(networkPanel); + return E('div', { 'class': 'sr-compact' }, [ this.renderHeader(services, providers, data.haproxy, data.tor), this.renderHealthSummary(data.health), + networkPanel, this.renderUrlChecker(), this.renderSection('📡 Published Services', published, true), this.renderSection('🔍 Discovered Services', unpublished, false), @@ -60,6 +66,108 @@ return view.extend({ ]); }, + loadNetworkInfo: function(container) { + api.getNetworkInfo().then(function(data) { + if (!data.success) { + container.innerHTML = '
Failed to load network info
'; + return; + } + + var ipv4 = data.ipv4 || {}; + var ipv6 = data.ipv6 || {}; + var extPorts = data.external_ports || {}; + var firewall = data.firewall || {}; + + var html = '
'; + html += '
🌍 Network Connectivity
'; + html += '
'; + + // IPv4 + html += '
'; + html += 'IPv4'; + if (ipv4.address) { + html += '' + ipv4.address + ''; + if (ipv4.hostname) { + html += '' + ipv4.hostname + ''; + } + } else { + html += 'Not available'; + } + html += '
'; + + // IPv6 + html += '
'; + html += 'IPv6'; + if (ipv6.address) { + html += '' + ipv6.address + ''; + if (ipv6.hostname) { + html += '' + ipv6.hostname + ''; + } + } else { + html += 'Not available'; + } + html += '
'; + + // External Port 80 + html += '
'; + html += 'Port 80 (HTTP)'; + var http = extPorts.http || {}; + if (http.status === 'open') { + html += '✅ Open from Internet'; + } else if (http.status === 'blocked') { + html += '🚫 Blocked'; + html += '' + (http.hint || 'Check router') + ''; + } else { + html += 'Unknown'; + } + html += '
'; + + // External Port 443 + html += '
'; + html += 'Port 443 (HTTPS)'; + var https = extPorts.https || {}; + if (https.status === 'open') { + html += '✅ Open from Internet'; + } else if (https.status === 'blocked') { + html += '🚫 Blocked'; + html += '' + (https.hint || 'Check router') + ''; + } else { + html += 'Unknown'; + } + html += '
'; + + // Local Firewall + html += '
'; + html += 'Local Firewall'; + if (firewall.status === 'ok') { + html += '✅ Ports 80/443 open'; + } else if (firewall.status === 'partial') { + html += '⚠️ Partial'; + } else { + html += '🚫 Closed'; + } + html += '
'; + + // HAProxy + html += '
'; + html += 'HAProxy'; + var haproxy = data.haproxy || {}; + if (haproxy.status === 'running') { + html += '🟢 Running'; + } else { + html += '🔴 Stopped'; + } + html += '
'; + + html += '
'; + + container.className = 'sr-network-loaded'; + container.innerHTML = html; + }).catch(function(err) { + container.innerHTML = '
Error: ' + err.message + '
'; + }); + }, + renderHealthSummary: function(health) { if (!health || !health.firewall) return E('div'); @@ -136,46 +244,90 @@ return view.extend({ var html = '
'; - // DNS Status + // Public IP Info + var publicIp = result.public_ip || {}; + html += '
'; + html += '🌍'; + html += 'Your Public IP'; + html += ''; + if (publicIp.ipv4) { + html += 'IPv4: ' + publicIp.ipv4 + ''; + if (publicIp.hostname) html += ' (' + publicIp.hostname + ')'; + } + if (publicIp.ipv6) { + html += '
IPv6: ' + publicIp.ipv6 + ''; + } + html += '
'; + + // DNS Status with IP comparison var dnsStatus = result.dns || {}; - var dnsIcon = healthIcons.dns[dnsStatus.status] || '❓'; - var dnsClass = dnsStatus.status === 'ok' ? 'sr-check-ok' : 'sr-check-fail'; + var dnsClass = 'sr-check-fail'; + if (dnsStatus.status === 'ok') dnsClass = 'sr-check-ok'; + else if (dnsStatus.status === 'private' || dnsStatus.status === 'mismatch') dnsClass = 'sr-check-warn'; + html += '
'; - html += '' + dnsIcon + ''; + html += '🌐'; html += 'DNS Resolution'; if (dnsStatus.status === 'ok') { - html += '✅ Resolves to ' + dnsStatus.resolved_ip + ''; + html += '✅ Resolves to ' + dnsStatus.resolved_ip + ' (matches public IP)'; + } else if (dnsStatus.status === 'private') { + html += '⚠️ Resolves to ' + dnsStatus.resolved_ip + ' (private IP!)'; + html += 'Should be: ' + dnsStatus.expected + ''; + } else if (dnsStatus.status === 'mismatch') { + html += '⚠️ Resolves to ' + dnsStatus.resolved_ip + ''; + html += 'Your public IP: ' + dnsStatus.expected + ''; } else { html += '❌ DNS not configured or not resolving'; } html += '
'; - // Firewall Status + // External Port Accessibility + var extAccess = result.external_access || {}; + var extClass = extAccess.status === 'ok' ? 'sr-check-ok' : (extAccess.status === 'partial' ? 'sr-check-warn' : 'sr-check-fail'); + html += '
'; + html += '🔌'; + html += 'Internet Accessibility'; + if (extAccess.status === 'ok') { + html += '✅ Ports 80 & 443 reachable from internet'; + } else if (extAccess.status === 'partial') { + var open = []; + var closed = []; + if (extAccess.http_accessible) open.push('80'); else closed.push('80'); + if (extAccess.https_accessible) open.push('443'); else closed.push('443'); + html += '⚠️ Open: ' + open.join(',') + ' | Blocked: ' + closed.join(',') + ''; + html += '' + (extAccess.hint || '') + ''; + } else if (extAccess.status === 'blocked') { + html += '🚫 Ports NOT reachable from internet'; + html += '' + (extAccess.hint || 'Check upstream router/ISP port forwarding') + ''; + } else { + html += '❓ Could not test external accessibility'; + } + html += '
'; + + // Local Firewall Status var fwStatus = result.firewall || {}; - var fwIcon = healthIcons.firewall[fwStatus.status] || '❓'; var fwClass = fwStatus.status === 'ok' ? 'sr-check-ok' : (fwStatus.status === 'partial' ? 'sr-check-warn' : 'sr-check-fail'); html += '
'; - html += '' + fwIcon + ''; - html += 'Firewall Ports'; + html += '🛡️'; + html += 'Local Firewall'; var ports = []; if (fwStatus.http_open) ports.push('80'); if (fwStatus.https_open) ports.push('443'); - html += '' + (ports.length ? 'Open: ' + ports.join(', ') : '❌ Ports 80/443 not open') + ''; + html += '' + (ports.length === 2 ? '✅ Ports 80/443 open' : (ports.length ? '⚠️ Only port ' + ports.join(',') + ' open' : '❌ Ports closed')) + ''; html += '
'; // Certificate Status var certStatus = result.certificate || {}; - var certIcon = healthIcons.cert[certStatus.status] || '❓'; var certClass = certStatus.status === 'ok' ? 'sr-check-ok' : (certStatus.status === 'warning' ? 'sr-check-warn' : 'sr-check-fail'); html += '
'; - html += '' + certIcon + ''; + html += '🔒'; html += 'SSL Certificate'; if (certStatus.status === 'ok' || certStatus.status === 'warning') { - html += '' + certStatus.days_left + ' days remaining'; + html += '' + (certStatus.status === 'ok' ? '✅' : '⚠️') + ' ' + certStatus.days_left + ' days remaining'; } else if (certStatus.status === 'expired') { html += '❌ Certificate expired'; } else if (certStatus.status === 'missing') { - html += '⚪ No certificate (request via HAProxy)'; + html += '⚪ No certificate yet'; } else { html += '⚪ Not applicable'; } @@ -183,10 +335,9 @@ return view.extend({ // HAProxy Status var haStatus = result.haproxy || {}; - var haIcon = haStatus.status === 'running' ? '🟢' : '🔴'; var haClass = haStatus.status === 'running' ? 'sr-check-ok' : 'sr-check-fail'; html += '
'; - html += '' + haIcon + ''; + html += '' + (haStatus.status === 'running' ? '🟢' : '🔴') + ''; html += 'HAProxy'; html += '' + (haStatus.status === 'running' ? '✅ Running' : '❌ Not running') + ''; html += '
'; @@ -194,24 +345,36 @@ return view.extend({ html += '
'; // Summary and recommendation - var allOk = dnsStatus.status === 'ok' && fwStatus.status === 'ok' && haStatus.status === 'running'; + var dnsOk = dnsStatus.status === 'ok'; + var extOk = extAccess.status === 'ok'; + var fwOk = fwStatus.status === 'ok'; + var haOk = haStatus.status === 'running'; + var certOk = certStatus.status === 'ok' || certStatus.status === 'warning'; var needsCert = certStatus.status === 'missing'; + var allOk = dnsOk && extOk && fwOk && haOk; html += '
'; - if (allOk && !needsCert) { - html += '
✅ ' + domain + ' is ready and serving!
'; + if (allOk && certOk) { + html += '
✅ ' + domain + ' is fully operational!
'; } else if (allOk && needsCert) { html += '
⚠️ ' + domain + ' is ready - just need SSL certificate
'; html += '📜 Request Certificate'; } else { html += '
❌ ' + domain + ' needs configuration
'; - if (dnsStatus.status !== 'ok') { - html += '
💡 Point DNS A record to your public IP
'; + if (dnsStatus.status === 'private') { + html += '
💡 DNS points to private IP! Update A record to: ' + publicIp.ipv4 + '
'; + } else if (dnsStatus.status === 'mismatch') { + html += '
💡 DNS points to different IP. Update A record to: ' + publicIp.ipv4 + '
'; + } else if (dnsStatus.status !== 'ok') { + html += '
💡 Create DNS A record: ' + domain + ' → ' + publicIp.ipv4 + '
'; } - if (fwStatus.status !== 'ok') { - html += '
💡 Open ports 80 and 443 in firewall
'; + if (!extOk && extAccess.status !== 'unknown') { + html += '
💡 Port forwarding needed! Forward ports 80/443 on your router to this device
'; } - if (haStatus.status !== 'running') { + if (!fwOk) { + html += '
💡 Open ports 80 and 443 in local firewall
'; + } + if (!haOk) { html += '
💡 Start HAProxy container
'; } } @@ -550,6 +713,24 @@ return view.extend({ @media (prefers-color-scheme: dark) { .sr-health-bar { background: #1a2a3e; } } .sr-health-item { font-size: 0.9em; } + /* Network Info Panel */ + .sr-network-loading { padding: 20px; text-align: center; background: #f8f8f8; border-radius: 8px; margin-bottom: 15px; } + @media (prefers-color-scheme: dark) { .sr-network-loading { background: #1a1a2e; } } + .sr-network-loaded { margin-bottom: 15px; } + .sr-network-error { padding: 15px; background: #fef2f2; color: #dc2626; border-radius: 8px; margin-bottom: 15px; } + @media (prefers-color-scheme: dark) { .sr-network-error { background: #450a0a; color: #fca5a5; } } + .sr-network-card { background: linear-gradient(135deg, #1e3a5f 0%, #0d2137 100%); border-radius: 12px; padding: 20px; color: #fff; } + .sr-network-header { font-size: 1.1em; font-weight: 600; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); } + .sr-network-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; } + .sr-network-item { background: rgba(0,0,0,0.2); padding: 12px; border-radius: 8px; } + .sr-network-label { display: block; font-size: 0.8em; color: #94a3b8; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.5px; } + .sr-network-value { display: block; font-size: 0.95em; font-weight: 500; word-break: break-all; } + .sr-network-value.sr-network-ok { color: #22c55e; } + .sr-network-value.sr-network-fail { color: #ef4444; } + .sr-network-value.sr-network-warn { color: #eab308; } + .sr-network-value.sr-network-na { color: #64748b; font-style: italic; } + .sr-network-sub { display: block; font-size: 0.75em; color: #64748b; margin-top: 4px; } + /* URL Checker Wizard Card */ .sr-wizard-card { background: linear-gradient(135deg, #0a192f 0%, #172a45 100%); border-radius: 12px; padding: 20px; margin-bottom: 25px; color: #fff; } .sr-wizard-header { display: flex; align-items: center; gap: 12px; margin-bottom: 15px; } @@ -569,14 +750,20 @@ return view.extend({ .sr-check-item.sr-check-ok { border-left-color: #22c55e; } .sr-check-item.sr-check-warn { border-left-color: #eab308; } .sr-check-item.sr-check-fail { border-left-color: #ef4444; } - .sr-check-icon { font-size: 1.3em; } - .sr-check-label { font-weight: 600; font-size: 0.9em; min-width: 100px; } - .sr-check-value { font-size: 0.85em; opacity: 0.8; } + .sr-check-item.sr-check-info { border-left-color: #0099cc; } + .sr-check-icon { font-size: 1.3em; flex-shrink: 0; } + .sr-check-label { font-weight: 600; font-size: 0.9em; min-width: 100px; flex-shrink: 0; } + .sr-check-value { font-size: 0.85em; opacity: 0.9; flex: 1; } + .sr-check-value code { background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 3px; font-family: monospace; } + .sr-check-value strong { color: #fff; } + .sr-check-sub { display: block; font-size: 0.8em; color: #94a3b8; margin-top: 4px; } .sr-check-summary { margin-top: 15px; padding: 15px; background: #0f172a; border-radius: 8px; text-align: center; } .sr-check-ready { font-size: 1.1em; color: #22c55e; font-weight: 600; } .sr-check-almost { font-size: 1.1em; color: #eab308; font-weight: 600; } .sr-check-notready { font-size: 1.1em; color: #ef4444; font-weight: 600; margin-bottom: 10px; } - .sr-check-tip { font-size: 0.85em; opacity: 0.8; margin-top: 5px; } + .sr-check-tip { font-size: 0.85em; opacity: 0.9; margin-top: 8px; text-align: left; padding: 0 20px; } + .sr-check-tip code { background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 3px; font-family: monospace; color: #0ff; } + .sr-check-tip strong { color: #fbbf24; } .sr-check-action { display: inline-block; margin-top: 10px; padding: 8px 16px; background: #0099cc; color: #fff; text-decoration: none; border-radius: 6px; font-size: 0.9em; } .sr-check-action:hover { background: #00b3e6; } diff --git a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry index 979e4714..58b166b9 100644 --- a/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry +++ b/package/secubox/luci-app-service-registry/root/usr/libexec/rpcd/luci.service-registry @@ -901,7 +901,7 @@ check_dns_resolution() { return 1 } -# Helper: Get WAN IP address +# Helper: Get WAN IP address (local interface) get_wan_ip() { local wan_ip="" # Try to get WAN IP from interface @@ -917,6 +917,62 @@ get_wan_ip() { echo "$wan_ip" } +# Helper: Get public IPv4 address (from external service) +get_public_ipv4() { + local ip="" + # Try multiple services for reliability + ip=$(wget -qO- -T 5 "http://ipv4.icanhazip.com" 2>/dev/null | tr -d '\n') + [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://api.ipify.org" 2>/dev/null | tr -d '\n') + [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://v4.ident.me" 2>/dev/null | tr -d '\n') + echo "$ip" +} + +# Helper: Get public IPv6 address (from external service) +get_public_ipv6() { + local ip="" + # Try multiple services for reliability + ip=$(wget -qO- -T 5 "http://ipv6.icanhazip.com" 2>/dev/null | tr -d '\n') + [ -z "$ip" ] && ip=$(wget -qO- -T 5 "http://v6.ident.me" 2>/dev/null | tr -d '\n') + echo "$ip" +} + +# Helper: Check external port accessibility using portchecker service +check_external_port() { + local ip="$1" + local port="$2" + local result="" + + # Use canyouseeme.org API or similar + # Try portquiz.net which echoes back on any port + result=$(wget -qO- -T 5 "http://portquiz.net:${port}/" 2>/dev/null) + if echo "$result" | grep -q "Port ${port}"; then + return 0 + fi + + # Alternative: try to connect to our own IP from outside perspective + # Use online port checker API + result=$(wget -qO- -T 8 "https://ports.yougetsignal.com/short-url-check-port.php?remoteAddress=${ip}&portNumber=${port}" 2>/dev/null) + if echo "$result" | grep -qi "open"; then + return 0 + fi + + return 1 +} + +# Helper: Reverse DNS lookup +get_reverse_dns() { + local ip="$1" + local hostname="" + + if command -v nslookup >/dev/null 2>&1; then + hostname=$(nslookup "$ip" 2>/dev/null | grep "name =" | head -1 | awk '{print $NF}' | sed 's/\.$//') + elif command -v host >/dev/null 2>&1; then + hostname=$(host "$ip" 2>/dev/null | grep "pointer" | head -1 | awk '{print $NF}' | sed 's/\.$//') + fi + + echo "$hostname" +} + # Helper: Check certificate expiry check_cert_expiry() { local domain="$1" @@ -975,6 +1031,116 @@ check_port_firewall_open() { return 1 } +# Get network connectivity info (public IPs, port accessibility) +method_get_network_info() { + json_init + json_add_boolean "success" 1 + + local lan_ip + lan_ip=$(get_lan_ip) + json_add_string "lan_ip" "$lan_ip" + + # Get public IPv4 + json_add_object "ipv4" + local public_ipv4 + public_ipv4=$(get_public_ipv4) + if [ -n "$public_ipv4" ]; then + json_add_string "address" "$public_ipv4" + json_add_string "status" "ok" + + # Reverse DNS + local rdns + rdns=$(get_reverse_dns "$public_ipv4") + [ -n "$rdns" ] && json_add_string "hostname" "$rdns" + else + json_add_string "status" "unavailable" + fi + json_close_object + + # Get public IPv6 + json_add_object "ipv6" + local public_ipv6 + public_ipv6=$(get_public_ipv6) + if [ -n "$public_ipv6" ]; then + json_add_string "address" "$public_ipv6" + json_add_string "status" "ok" + + # Reverse DNS + local rdns6 + rdns6=$(get_reverse_dns "$public_ipv6") + [ -n "$rdns6" ] && json_add_string "hostname" "$rdns6" + else + json_add_string "status" "unavailable" + fi + json_close_object + + # External port accessibility (from internet perspective) + json_add_object "external_ports" + if [ -n "$public_ipv4" ]; then + # Check port 80 + json_add_object "http" + if check_external_port "$public_ipv4" 80; then + json_add_boolean "accessible" 1 + json_add_string "status" "open" + else + json_add_boolean "accessible" 0 + json_add_string "status" "blocked" + json_add_string "hint" "Check upstream router port forwarding" + fi + json_close_object + + # Check port 443 + json_add_object "https" + if check_external_port "$public_ipv4" 443; then + json_add_boolean "accessible" 1 + json_add_string "status" "open" + else + json_add_boolean "accessible" 0 + json_add_string "status" "blocked" + json_add_string "hint" "Check upstream router port forwarding" + fi + json_close_object + else + json_add_object "http" + json_add_string "status" "unknown" + json_add_string "error" "No public IP" + json_close_object + json_add_object "https" + json_add_string "status" "unknown" + json_add_string "error" "No public IP" + json_close_object + fi + json_close_object + + # Local firewall status + json_add_object "firewall" + local http_open=0 + local https_open=0 + check_port_firewall_open 80 && http_open=1 + check_port_firewall_open 443 && https_open=1 + json_add_boolean "http_open" "$http_open" + json_add_boolean "https_open" "$https_open" + if [ "$http_open" = "1" ] && [ "$https_open" = "1" ]; then + json_add_string "status" "ok" + elif [ "$http_open" = "1" ] || [ "$https_open" = "1" ]; then + json_add_string "status" "partial" + else + json_add_string "status" "closed" + fi + json_close_object + + # HAProxy status + json_add_object "haproxy" + if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then + json_add_string "status" "running" + else + json_add_string "status" "stopped" + fi + json_close_object + + json_dump +} + # Check health status for a service method_check_service_health() { local service_id domain @@ -1003,14 +1169,46 @@ method_check_service_health() { json_add_string "service_id" "$service_id" json_add_string "domain" "$domain" - # DNS check + # Get public IPs for comparison + local public_ipv4 public_ipv6 + public_ipv4=$(get_public_ipv4) + public_ipv6=$(get_public_ipv6) + + # Public IP info + json_add_object "public_ip" + json_add_string "ipv4" "$public_ipv4" + [ -n "$public_ipv6" ] && json_add_string "ipv6" "$public_ipv6" + if [ -n "$public_ipv4" ]; then + local rdns + rdns=$(get_reverse_dns "$public_ipv4") + [ -n "$rdns" ] && json_add_string "hostname" "$rdns" + fi + json_close_object + + # DNS check with public IP comparison json_add_object "dns" if [ -n "$domain" ]; then local resolved_ip resolved_ip=$(check_dns_resolution "$domain") if [ -n "$resolved_ip" ]; then - json_add_string "status" "ok" json_add_string "resolved_ip" "$resolved_ip" + # Check if DNS points to public IP or private IP + case "$resolved_ip" in + 10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*) + json_add_string "status" "private" + json_add_string "error" "DNS points to private IP (not reachable from internet)" + json_add_string "expected" "$public_ipv4" + ;; + *) + if [ "$resolved_ip" = "$public_ipv4" ]; then + json_add_string "status" "ok" + else + json_add_string "status" "mismatch" + json_add_string "expected" "$public_ipv4" + json_add_string "hint" "DNS points to different IP than your public IP" + fi + ;; + esac else json_add_string "status" "failed" json_add_string "error" "DNS resolution failed" @@ -1020,6 +1218,30 @@ method_check_service_health() { fi json_close_object + # External port accessibility check + json_add_object "external_access" + if [ -n "$public_ipv4" ]; then + local http_ext=0 + local https_ext=0 + check_external_port "$public_ipv4" 80 && http_ext=1 + check_external_port "$public_ipv4" 443 && https_ext=1 + json_add_boolean "http_accessible" "$http_ext" + json_add_boolean "https_accessible" "$https_ext" + if [ "$http_ext" = "1" ] && [ "$https_ext" = "1" ]; then + json_add_string "status" "ok" + elif [ "$http_ext" = "1" ] || [ "$https_ext" = "1" ]; then + json_add_string "status" "partial" + json_add_string "hint" "Check upstream router/ISP port forwarding" + else + json_add_string "status" "blocked" + json_add_string "hint" "Ports not accessible from internet - check router/ISP" + fi + else + json_add_string "status" "unknown" + json_add_string "error" "Could not determine public IP" + fi + json_close_object + # Certificate check json_add_object "certificate" if [ -n "$domain" ]; then @@ -1293,6 +1515,7 @@ case "$1" in "get_certificate_status": { "service_id": "string" }, "check_service_health": { "service_id": "string", "domain": "string" }, "check_all_health": {}, + "get_network_info": {}, "get_landing_config": {}, "save_landing_config": { "auto_regen": "boolean" } } @@ -1313,6 +1536,7 @@ EOF get_certificate_status) method_get_certificate_status ;; check_service_health) method_check_service_health ;; check_all_health) method_check_all_health ;; + get_network_info) method_get_network_info ;; get_landing_config) method_get_landing_config ;; save_landing_config) method_save_landing_config ;; *) diff --git a/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json b/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json index f91234d3..68fd189d 100644 --- a/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json +++ b/package/secubox/luci-app-service-registry/root/usr/share/rpcd/acl.d/luci-app-service-registry.json @@ -11,6 +11,7 @@ "get_certificate_status", "check_service_health", "check_all_health", + "get_network_info", "get_landing_config" ], "luci.haproxy": [