Files
2026-07-13 12:31:02 +08:00

254 lines
6.4 KiB
Go

package operations
import (
"fmt"
"net"
"strings"
"time"
"service-operation/types"
)
type DNSOperation struct {
timeout time.Duration
}
func NewDNSOperation(timeout time.Duration) *DNSOperation {
return &DNSOperation{timeout: timeout}
}
func (d *DNSOperation) Execute(host, query string) (*types.OperationResult, error) {
// Validate inputs
if host == "" {
return nil, fmt.Errorf("host cannot be empty")
}
if query == "" {
query = "A" // Default to A record
}
result := &types.OperationResult{
Type: types.OperationDNS,
Host: host,
DNSType: query,
StartTime: time.Now(),
}
start := time.Now()
// Resolve the host first to get detailed info
var resolvedIPs []string
var err error
switch strings.ToUpper(query) {
case "A":
resolvedIPs, err = d.performARecordLookup(host)
case "AAAA":
resolvedIPs, err = d.performAAAARecordLookup(host)
case "MX":
resolvedIPs, err = d.performMXRecordLookup(host)
case "TXT":
resolvedIPs, err = d.performTXTRecordLookup(host)
case "CNAME":
resolvedIPs, err = d.performCNAMERecordLookup(host)
case "NS":
resolvedIPs, err = d.performNSRecordLookup(host)
default:
// Default to A record lookup for unknown types
resolvedIPs, err = d.performARecordLookup(host)
}
result.ResponseTime = time.Since(start)
result.EndTime = time.Now()
if err != nil {
result.Error = err.Error()
result.Success = false
result.Details = d.createDetailedErrorMessage(err.Error(), host, query)
} else {
result.DNSRecords = resolvedIPs
result.Success = len(resolvedIPs) > 0
if result.Success {
result.Details = d.createDetailedSuccessMessage(result, host, query, resolvedIPs)
} else {
result.Error = "No DNS records found"
result.Details = d.createDetailedErrorMessage("no records found", host, query)
}
}
return result, nil
}
func (d *DNSOperation) performARecordLookup(host string) ([]string, error) {
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
var ipv4Records []string
for _, ip := range ips {
if ipv4 := ip.To4(); ipv4 != nil {
ipv4Records = append(ipv4Records, ipv4.String())
}
}
return ipv4Records, nil
}
func (d *DNSOperation) performAAAARecordLookup(host string) ([]string, error) {
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
var ipv6Records []string
for _, ip := range ips {
if ipv6 := ip.To16(); ipv6 != nil && ip.To4() == nil {
ipv6Records = append(ipv6Records, ipv6.String())
}
}
return ipv6Records, nil
}
func (d *DNSOperation) performMXRecordLookup(host string) ([]string, error) {
mxRecords, err := net.LookupMX(host)
if err != nil {
return nil, err
}
var records []string
for _, mx := range mxRecords {
records = append(records, fmt.Sprintf("%s (priority: %d)", mx.Host, mx.Pref))
}
return records, nil
}
func (d *DNSOperation) performTXTRecordLookup(host string) ([]string, error) {
txtRecords, err := net.LookupTXT(host)
if err != nil {
return nil, err
}
return txtRecords, nil
}
func (d *DNSOperation) performCNAMERecordLookup(host string) ([]string, error) {
cname, err := net.LookupCNAME(host)
if err != nil {
return nil, err
}
return []string{cname}, nil
}
func (d *DNSOperation) performNSRecordLookup(host string) ([]string, error) {
nsRecords, err := net.LookupNS(host)
if err != nil {
return nil, err
}
var records []string
for _, ns := range nsRecords {
records = append(records, ns.Host)
}
return records, nil
}
func (d *DNSOperation) createDetailedSuccessMessage(result *types.OperationResult, host, queryType string, records []string) string {
var details strings.Builder
// Success indicator with basic info
details.WriteString(fmt.Sprintf("🟢 DNS SUCCESS - %s query for %s",
strings.ToUpper(queryType), host))
// Response time
details.WriteString(fmt.Sprintf(" | Response time: %.2fms",
float64(result.ResponseTime.Nanoseconds())/1000000))
// Record count
details.WriteString(fmt.Sprintf(" | Records found: %d", len(records)))
// Show first few records for context
if len(records) > 0 {
details.WriteString(" | ")
if len(records) <= 3 {
details.WriteString(fmt.Sprintf("Results: %s", strings.Join(records, ", ")))
} else {
details.WriteString(fmt.Sprintf("Results: %s... (+%d more)",
strings.Join(records[:3], ", "), len(records)-3))
}
}
return details.String()
}
func (d *DNSOperation) createDetailedErrorMessage(errorMsg, host, queryType string) string {
var details strings.Builder
errorLower := strings.ToLower(errorMsg)
if strings.Contains(errorLower, "timeout") {
details.WriteString("⏱️ DNS TIMEOUT - Query timed out")
} else if strings.Contains(errorLower, "no such host") || strings.Contains(errorLower, "host not found") {
details.WriteString("🔍 HOST NOT FOUND - DNS resolution failed")
} else if strings.Contains(errorLower, "no answer") || strings.Contains(errorLower, "no records found") {
details.WriteString("📝 NO RECORDS - No DNS records of requested type")
} else if strings.Contains(errorLower, "server failure") {
details.WriteString("🔧 SERVER FAILURE - DNS server error")
} else if strings.Contains(errorLower, "refused") {
details.WriteString("🚫 QUERY REFUSED - DNS server refused query")
} else {
details.WriteString("❌ DNS FAILED - Query error")
}
// Add query details
details.WriteString(fmt.Sprintf(" | Query: %s %s",
strings.ToUpper(queryType), host))
// Add specific error details
if errorMsg != "" {
details.WriteString(fmt.Sprintf(" | Error: %s", d.getShortErrorMessage(errorMsg)))
}
return details.String()
}
func (d *DNSOperation) getShortErrorMessage(errorMessage string) string {
if errorMessage == "" {
return "Unknown error"
}
errorLower := strings.ToLower(errorMessage)
if strings.Contains(errorLower, "timeout") {
return "Query timeout"
} else if strings.Contains(errorLower, "no such host") {
return "Host not found"
} else if strings.Contains(errorLower, "no answer") {
return "No records found"
} else if strings.Contains(errorLower, "server failure") {
return "DNS server failure"
} else if strings.Contains(errorLower, "refused") {
return "Query refused"
} else if strings.Contains(errorLower, "network unreachable") {
return "Network unreachable"
}
// For other errors, take first 50 characters and clean it up
shortMsg := errorMessage
if len(shortMsg) > 50 {
shortMsg = shortMsg[:50] + "..."
}
return shortMsg
}