Skip to content

Latest commit

 

History

History
669 lines (522 loc) · 16 KB

File metadata and controls

669 lines (522 loc) · 16 KB

Defenra Agent Architecture

This document describes the architecture and internal design of Defenra Agent.

Table of Contents


Overview

Defenra Agent is a high-performance, multi-protocol proxy and DNS server written in Go. It acts as an edge node in a distributed network, providing:

  • GeoDNS - Geographic DNS routing
  • Reverse Proxy - HTTP/HTTPS traffic forwarding
  • WAF - Web Application Firewall with Lua
  • Port Forwarding - TCP/UDP proxy
┌─────────────────────────────────────────────────────────────┐
│                     Defenra Agent (GoLang)                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐    │
│  │  DNS Server  │  │ HTTP Proxy   │  │ TCP/UDP Proxy│    │
│  │   (Port 53)  │  │ (80/443)     │  │  (custom)    │    │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘    │
│         │                  │                  │            │
│         └──────────────────┴──────────────────┘            │
│                            │                               │
│                   ┌────────▼─────────┐                     │
│                   │  Config Manager  │                     │
│                   │  (Poll Core API) │                     │
│                   └────────┬─────────┘                     │
│                            │                               │
│                   ┌────────▼─────────┐                     │
│                   │  GeoIP Service   │                     │
│                   │   Lua WAF VM     │                     │
│                   └──────────────────┘                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                            ▲
                            │ HTTPS Poll (every 60s)
                            │
                  ┌─────────▼──────────┐
                  │   Defenra Core     │
                  │   (Node.js API)    │
                  └────────────────────┘

Component Architecture

1. Main Application (main.go)

Responsibilities:

  • Initialize all components
  • Load environment variables
  • Start all services in goroutines
  • Handle graceful shutdown

Key Functions:

func main()
func getEnvInt(key string, defaultVal int) int

2. Config Manager (config/)

Responsibilities:

  • Poll configuration from Core API
  • Store and manage configuration
  • Provide thread-safe access to config
  • Track statistics

Key Components:

  • ConfigManager - Main manager
  • Config - Configuration structure
  • Domain - Domain configuration
  • Proxy - Proxy configuration

Thread Safety:

  • Uses sync.RWMutex for concurrent access
  • Atomic updates to prevent inconsistency

3. DNS Server (dns/)

Responsibilities:

  • Handle DNS queries (UDP/TCP)
  • Resolve A, AAAA, CNAME, MX, TXT records
  • Perform geographic routing (GeoDNS)
  • Cache DNS responses
  • Track query statistics

Key Components:

  • DNSServer - Main DNS server
  • DNSCache - In-memory cache
  • GeoIPService - Geographic IP lookup

Performance:

  • Uses github.com/miekg/dns library
  • In-memory caching with TTL
  • Concurrent query handling
  • Atomic statistics counters

4. HTTP/HTTPS Proxy (proxy/)

Responsibilities:

  • Forward HTTP/HTTPS requests
  • Terminate SSL connections
  • Execute WAF rules
  • Add proxy headers
  • Track request statistics

Key Components:

  • HTTPProxyServer - HTTP proxy
  • HTTPSProxyServer - HTTPS proxy with SSL
  • ProxyManager - TCP/UDP proxy manager
  • TCPProxy - TCP proxy
  • UDPProxy - UDP proxy

Features:

  • SNI support for multiple domains
  • Dynamic certificate loading
  • Connection pooling
  • Header manipulation

5. Lua WAF (waf/)

Responsibilities:

  • Execute Lua scripts for request filtering
  • Provide nginx-like API
  • Shared memory for rate limiting
  • Thread-safe script execution

Key Components:

  • LuaWAF - WAF engine
  • WAFResponse - Response structure

Performance:

  • Uses github.com/yuin/gopher-lua
  • Lua state pooling
  • Sandbox environment
  • Timeout protection

6. Health Check (health/)

Responsibilities:

  • Provide health status endpoint
  • Expose runtime statistics
  • Monitor agent status

Endpoints:

  • /health - Basic health check
  • /stats - Detailed statistics

Data Flow

Configuration Update Flow

1. Timer Tick (every 60s)
   │
   ▼
2. ConfigManager.poll()
   │
   ▼
3. HTTP POST to Core API
   │
   ├─ Success ─────────────────┐
   │                           │
   ▼                           ▼
4. Parse JSON Response    Log Error
   │
   ▼
5. ConfigManager.updateConfig()
   │
   ├─ Lock mutex
   ├─ Update domains
   ├─ Update proxies
   └─ Unlock mutex
   │
   ▼
6. Components read new config

DNS Query Flow

1. Client sends DNS query
   │
   ▼
2. DNSServer.handleDNSRequest()
   │
   ├─ Extract query details
   ├─ Extract client IP
   └─ Get domain config
   │
   ├─ Domain not found ───> NXDOMAIN
   │
   ▼
3. Check if GeoDNS enabled
   │
   ├─ Yes ─────────────────────┐
   │                            │
   ▼                            ▼
4. Regular DNS Query     5. GeoDNS Query
   │                        │
   ├─ Find record          ├─ Get client location (GeoIP)
   ├─ Build answer         ├─ Find best agent IP
   └─ Return response      └─ Return agent IP

HTTP Request Flow

1. Client sends HTTP request
   │
   ▼
2. HTTPProxyServer.handleRequest()
   │
   ├─ Extract host
   ├─ Get domain config
   └─ Check proxy enabled
   │
   ├─ Not enabled ───> 403 Forbidden
   │
   ▼
3. Execute Lua WAF (if configured)
   │
   ├─ Blocked ───> Return error
   │
   ▼
4. Find backend target
   │
   ├─ Not found ───> 502 Bad Gateway
   │
   ▼
5. Proxy request to backend
   │
   ├─ Add X-Forwarded-* headers
   ├─ Forward request
   ├─ Receive response
   └─ Return to client

HTTPS Request Flow

1. Client initiates TLS handshake
   │
   ▼
2. GetCertificate callback
   │
   ├─ Extract SNI hostname
   ├─ Get domain config
   └─ Load certificate
   │
   ├─ No cert ───> TLS error
   │
   ▼
3. TLS handshake complete
   │
   ▼
4. Same as HTTP flow (steps 2-5)

Configuration Management

Polling Mechanism

The agent polls configuration from Core API at regular intervals:

type PollRequest struct {
    AgentID  string `json:"agentId"`
    AgentKey string `json:"agentKey"`
}

type PollResponse struct {
    Success bool     `json:"success"`
    Domains []Domain `json:"domains"`
    Proxies []Proxy  `json:"proxies"`
}

Polling Interval: 60 seconds (configurable)

Retry Logic:

  • On failure, log error and continue
  • Next poll in 60 seconds
  • No exponential backoff (keeps trying)

Configuration Structure

type Config struct {
    Domains    []Domain
    Proxies    []Proxy
    LastUpdate time.Time
    mu         sync.RWMutex
}

type Domain struct {
    Domain     string
    DNSRecords []DNSRecord
    GeoDNSMap  map[string]string  // location -> IP
    HTTPProxy  HTTPProxy
    SSL        SSL
    LuaCode    string
}

type Proxy struct {
    Name       string
    Protocol   string  // "tcp" or "udp"
    ListenPort int
    TargetHost string
    TargetPort int
}

Thread Safety

All configuration access is protected by sync.RWMutex:

  • Read operations use RLock() - Multiple readers allowed
  • Write operations use Lock() - Exclusive access
func (cm *ConfigManager) GetDomain(domain string) *Domain {
    cm.mu.RLock()
    defer cm.mu.RUnlock()
    // ... search domain ...
}

func (cm *ConfigManager) updateConfig(resp PollResponse) {
    cm.mu.Lock()
    defer cm.mu.Unlock()
    // ... update config ...
}

DNS Server

Record Types

Supported DNS record types:

  • A - IPv4 address
  • AAAA - IPv6 address
  • CNAME - Canonical name
  • MX - Mail exchange
  • TXT - Text record

GeoDNS Implementation

GeoDNS provides different responses based on client location:

func (s *DNSServer) handleGeoDNSQuery(w dns.ResponseWriter, r *dns.Msg, 
    domainConfig *config.Domain, clientIP string) {
    
    // 1. Determine client location
    clientLocation := s.geoIP.GetLocation(clientIP)
    
    // 2. Find best agent IP for this location
    agentIP := findBestAgentIP(domainConfig.GeoDNSMap, clientLocation)
    
    // 3. Return agent IP
    msg.Answer = append(msg.Answer, &dns.A{
        Hdr: dns.RR_Header{
            Name:   r.Question[0].Name,
            Rrtype: dns.TypeA,
            Class:  dns.ClassINET,
            Ttl:    60,  // Low TTL for dynamic routing
        },
        A: net.ParseIP(agentIP),
    })
}

Location Mapping:

Client Location → Agent Location → Agent IP

cn (China) → asia → 103.xxx.xxx.xxx
us (USA) → us → 45.xxx.xxx.xxx
de (Germany) → europe → 51.91.242.9
(unknown) → default → 51.91.242.9

GeoIP Database

Uses MaxMind GeoLite2-City database:

db, err := geoip2.Open("GeoLite2-City.mmdb")
record, err := db.City(net.ParseIP(clientIP))

countryCode := record.Country.IsoCode  // "US", "CN", etc.
continent := record.Continent.Code     // "NA", "AS", etc.

Caching:

  • IP → Location mapping is cached in memory
  • Reduces database lookups
  • Improves performance

HTTP/HTTPS Proxy

SSL Termination

Dynamic certificate loading using SNI:

tlsConfig := &tls.Config{
    GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
        domainConfig := configMgr.GetDomain(hello.ServerName)
        
        cert, err := tls.X509KeyPair(
            []byte(domainConfig.SSL.Certificate),
            []byte(domainConfig.SSL.PrivateKey),
        )
        
        return &cert, nil
    },
}

Features:

  • Multiple domains on single IP
  • Dynamic cert reload (no restart needed)
  • TLS 1.2+ only
  • HTTP/2 support

Request Forwarding

Forwards requests to backend servers:

proxyReq, _ := http.NewRequest(r.Method, targetURL, r.Body)

// Copy headers
for key, values := range r.Header {
    proxyReq.Header.Add(key, values[0])
}

// Add proxy headers
proxyReq.Header.Set("X-Forwarded-For", clientIP)
proxyReq.Header.Set("X-Forwarded-Proto", "https")
proxyReq.Header.Set("X-Real-IP", clientIP)

// Execute request
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(proxyReq)

// Forward response
for key, values := range resp.Header {
    w.Header().Add(key, values[0])
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)

Lua WAF

Nginx-like API

Provides familiar API for Nginx users:

-- Request variables
ngx.var.remote_addr  -- Client IP
ngx.var.uri          -- Request URI
ngx.var.host         -- Host header

-- Block request
ngx.exit(403)

-- Shared memory (rate limiting)
local count = ngx.shared.cache:get(key)
ngx.shared.cache:set(key, value, ttl)
ngx.shared.cache:incr(key, delta, init, ttl)

-- Response headers
ngx.header["X-Custom"] = "value"

Execution Flow

func (w *LuaWAF) Execute(luaCode string, r *http.Request) (bool, WAFResponse) {
    // 1. Get Lua state from pool
    L := w.pool.Get().(*lua.LState)
    defer w.pool.Put(L)
    
    // 2. Inject request context
    requestTable := L.NewTable()
    L.SetField(requestTable, "method", lua.LString(r.Method))
    L.SetGlobal("request", requestTable)
    
    // 3. Setup nginx API
    w.setupNginxAPI(L)
    
    // 4. Execute Lua code
    err := L.DoString(luaCode)
    
    // 5. Check if blocked
    blocked := L.GetGlobal("_blocked")
    if blocked != lua.LNil {
        return true, WAFResponse{Blocked: true, StatusCode: 403}
    }
    
    return false, WAFResponse{}
}

Performance Optimization

  • State Pooling - Reuse Lua states
  • Sandbox - Limited functionality for security
  • Timeout - Prevent long-running scripts
  • Shared Memory - In-memory cache for rate limiting

TCP/UDP Proxy

TCP Proxy

Bidirectional forwarding:

func (p *TCPProxy) handleConnection(clientConn net.Conn) {
    targetConn, _ := net.Dial("tcp", targetAddr)
    
    // Client → Backend
    go io.Copy(targetConn, clientConn)
    
    // Backend → Client
    io.Copy(clientConn, targetConn)
}

UDP Proxy

Stateless packet forwarding:

func handleUDPPacket(conn *net.UDPConn, clientAddr *net.UDPAddr, 
    data []byte, config ProxyConfig) {
    
    // Forward to backend
    targetConn, _ := net.DialUDP("udp", nil, targetAddr)
    targetConn.Write(data)
    
    // Wait for response
    targetConn.SetReadDeadline(time.Now().Add(5 * time.Second))
    n, _ := targetConn.Read(responseBuffer)
    
    // Send back to client
    conn.WriteToUDP(responseBuffer[:n], clientAddr)
}

Performance

Benchmarks

DNS Server:

  • 10,000+ QPS per agent
  • < 10ms average latency
  • < 100MB memory usage

HTTP Proxy:

  • 5,000+ RPS per agent
  • < 20ms average latency
  • < 200MB memory usage

Lua WAF:

  • < 5ms overhead per request
  • State pooling for efficiency

Optimization Techniques

  1. Goroutines - Concurrent request handling
  2. Connection Pooling - Reuse HTTP connections
  3. Caching - DNS and GeoIP caching
  4. Atomic Operations - Lock-free statistics
  5. Read-Write Locks - Multiple concurrent readers

Resource Limits

  • Max Goroutines: Unlimited (OS limited)
  • Max Memory: 2GB (configurable)
  • Max Connections: 65535 (OS limit)
  • Max File Descriptors: 65536

Security

Input Validation

  • DNS queries validated
  • HTTP headers sanitized
  • Lua scripts sandboxed

Privilege Separation

  • Runs as non-root user (with capabilities)
  • Limited file system access
  • No shell access

Network Security

  • TLS 1.2+ only
  • Strong cipher suites
  • Certificate validation

Lua Sandbox

  • No file system access
  • No network access
  • No OS commands
  • CPU/memory limits

Future Improvements

  1. Prometheus Metrics - Better observability
  2. Hot Reload - Config reload without restart
  3. Redis Backend - Distributed cache
  4. HTTP/3 Support - QUIC protocol
  5. Auto SSL - Let's Encrypt integration
  6. Rate Limiting - Built-in rate limiter
  7. DDoS Protection - Layer 7 protection

References