A Go-based acoustic communication system

14 minute read

Published:

A network communication system demonstrating UDP-based DNS resolution and TCP-based HTTP requests over an acoustic physical layer using Go.

This project implements essential transport layer protocols (UDP and TCP) over sound waves, enabling real-world internet services like domain name resolution and web page retrieval through audio signals.

Table of Contents

Overview

This project focuses on implementing transport layer protocols over acoustic communication. The main achievements are:

  1. UDP-based DNS Resolution: A complete DNS client that sends DNS queries using UDP and resolves domain names (e.g., www.example.com93.184.216.34)
  2. TCP-based HTTP Requests: A TCP client that establishes connections via 3-way handshake, sends HTTP GET requests, and receives web page content
  3. Acoustic Physical Layer: Uses sound waves (audio signals) as the transmission medium instead of electrical cables

The system enables a node behind a NAT gateway to access internet services through acoustic communication with another node that has internet connectivity.

Core Features

UDP DNS Resolution

The DNS implementation provides domain name resolution functionality:

Key Components:

  • DNS Query Generation: Creates standard DNS query packets with proper format (DNS header, questions section, A record type)
  • UDP Transport: Wraps DNS queries in UDP datagrams (port 53)
  • Query ID Tracking: Assigns unique IDs to each query and tracks pending responses
  • Response Parsing: Extracts IP addresses from DNS response packets
  • Upstream DNS: Forwards queries to external DNS servers (1.1.1.1, 8.8.8.8)
  • Timeout Handling: 5-second timeout for unresponsive queries
  • Caching: Stores resolved domains for 5 minutes to reduce redundant queries

Workflow:

  1. User executes ping www.example.com
  2. Node 1 creates DNS query packet with unique ID
  3. Query wrapped in UDP datagram and sent via acoustic link to Node 2
  4. Node 2 (NAT gateway) forwards query to internet DNS server (1.1.1.1)
  5. DNS response received from internet
  6. Node 2 sends response back through acoustic link
  7. Node 1 extracts IP address and proceeds with ping/curl

TCP HTTP Implementation

The TCP implementation enables reliable HTTP communication:

Key Components:

  • 3-Way Handshake: Complete SYN → SYN-ACK → ACK connection establishment
  • State Machine: Tracks connection states (CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT)
  • Sequence Numbers: Maintains proper sequence and acknowledgment numbers
  • Data Transmission: Sends HTTP GET requests with PSH+ACK flags
  • Reliable Delivery: Handles ACKs and retransmissions
  • Connection Teardown: FIN handshake for clean connection closure
  • HTTP Request Generation: Constructs valid HTTP/1.0 GET requests
  • Response Buffering: Accumulates TCP segments into complete HTTP response

Workflow:

  1. User executes curl www.example.com
  2. Node 1 resolves domain to IP (93.184.216.34) via DNS
  3. Node 1 initiates TCP connection:
    • Sends SYN packet through acoustic link
    • Node 2 forwards to internet server
    • Receives SYN-ACK from server via Node 2
    • Sends ACK to complete handshake
  4. Node 1 sends HTTP GET request in PSH+ACK segment
  5. Node 2 forwards request to web server
  6. Web server responds with HTTP headers and HTML content
  7. Node 2 forwards response segments back through acoustic link
  8. Node 1 reassembles segments and displays HTTP response
  9. Connection closed with FIN handshake

Lower Layers (Brief)

To support UDP and TCP over acoustic communication, several lower layers are implemented:

Physical Layer (src/phy/):

  • Chirp Preamble: 2-10kHz frequency sweep for frame synchronization
  • Manchester Encoding: Line coding for reliable bit transmission
  • CRC-16: Error detection for frame integrity
  • JACK Audio: 48kHz sample rate audio I/O

Data Link Layer (src/data_link/): [code]

  • CSMA/CA: Carrier sense multiple access with collision avoidance
  • ACK Frames: Acknowledgment frames for received data
  • Sliding Window: Selective repeat ARQ for reliable delivery
  • Frame Format: [PreambleHeaderPayloadCRC]

Network Layer (src/ip/): [code]

  • IP Routing: Basic IPv4 packet forwarding
  • ICMP: Ping functionality (echo request/reply)
  • NAT: Network Address Translation for internet access

These layers provide the foundation for reliable packet delivery, you may check our previous repo’s code.

Architecture

┌─────────────────────────────────────────────────────────┐
│         Application Layer                               │
│         [HTTP Requests, DNS Queries]                    │
│         ↑                                               │
│         curl www.example.com                            │
│         ping www.google.com                             │
├─────────────────────────────────────────────────────────┤
│         Transport Layer                                 │
│         [TCP: 3-way handshake, reliable delivery]       │
│         [UDP: DNS queries to port 53]                   │
├─────────────────────────────────────────────────────────┤
│         Network Layer                                   │
│         [IP Routing, NAT, ICMP]                         │
├─────────────────────────────────────────────────────────┤
│         Data Link Layer                                 │
│         [CSMA/CA, Framing, ACK]                         │
├─────────────────────────────────────────────────────────┤
│         Physical Layer                                  │
│         [Chirp, Manchester, CRC]                        │
│         JACK Audio Server (48kHz)                       │
└─────────────────────────────────────────────────────────┘

Prerequisites

  • Go 1.25+: Install from golang.org
  • JACK Audio Server: Required for audio I/O
    • Windows: Download from jackaudio.org
    • Install ASIO4ALL driver for low-latency audio
  • Network Requirements:
    • Microsoft KM-TEST Loopback Adapter
    • Audio cables or speakers/microphones for acoustic coupling

Installation

  1. Clone the repository:

  2. Install Go dependencies:

    go mod download
    

Environment Setup

1. Start JACK Server

cd "C:\Program Files\JACK2\"
.\jackd.exe -S -X winmme -dportaudio -d"ASIO::ASIO4ALL v2" -r48000 -p128

Parameters:

  • -r48000: Sample rate of 48kHz
  • -p128: Buffer size of 128 samples (adjust for latency/stability trade-off)

2. Install Microsoft Loopback Adapter

  1. Open Device Manager
  2. Click ActionAdd legacy hardware
  3. Select Install the hardware that I manually select from a list
  4. Select Network adapters
  5. Select Microsoft as manufacturer
  6. Select Microsoft KM-TEST Loopback Adapter
  7. Complete installation

3. Configure Network Interfaces

Configure static IP addresses for the Loopback Adapter:

  • Node 1 (Client): 172.18.0.1 / 255.255.255.0
  • Node 2 (Gateway): 172.18.0.2 / 255.255.255.0

Configure Gateway on Node 1:

  • Set default gateway: 172.18.0.2
  • Set DNS server: 8.8.8.8

4. Get Network Interface Name

# List all network interfaces
go run cmd\network_test\main.go list
# Interface name format: \Device\NPF_{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

Usage

DNS Query Example

Node 1 (Client):

go run cmd\node\main.go 1

Node 2 (NAT Gateway):

go run cmd\node\main.go 2

Execute DNS query on Node 1:

# In Node 1's console
ping www.google.com -n 4

Expected output:

[Node 1] Resolving domain: www.google.com
[DNS] Created query for www.google.com (ID: 1)
[Node 1] DNS query sent via acoustic link
[Node 2] Received DNS query, forwarding to 1.1.1.1
[Node 1] DNS response received: www.google.com -> 142.250.185.68
Pinging www.google.com [142.250.185.68] with 32 bytes of data:
Reply from 142.250.185.68: time=350ms
Reply from 142.250.185.68: time=345ms
...

HTTP Request Example

Execute HTTP request on Node 1:

# In Node 1's console
curl example.com

Expected output:

[Node 1] Resolving domain: www.example.com
[DNS] Created query for www.example.com (ID: 2)
[Node 1] Resolved www.example.com to 93.184.216.34
[TCP] Initiating connection to 93.184.216.34:80
[TCP] Sending SYN (Seq=0x12345678)
[TCP] Received SYN-ACK (Seq=XXXXXXX, Ack=0x12345679)
[TCP] Connection ESTABLISHED
[Curl] Connected! Sending HTTP Request...
[TCP] Sending HTTP GET request

--- HTTP Response ---
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 1256

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
...
</head>
<body>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples...</p>
</body>
</html>
---------------------
[TCP] Closing connection
[TCP] Sent FIN

Project Structure

acoustic-link/
├── cmd/
│   ├── network_test/          # Network interface testing
│   │   └── main.go
│   └── node/                  # Main application
│       ├── main.go            # Node logic, DNS/TCP/HTTP handlers
│       ├── reception.go       # Acoustic signal reception
│       └── transmission.go    # Acoustic signal transmission
├── src/
│   ├── transport/             # ★ Transport Layer (UDP, TCP, DNS)
│   │   ├── udp.go             # UDP packet creation and parsing
│   │   ├── dns.go             # DNS client: query generation, response parsing
│   │   └── tcp.go             # TCP client: 3-way handshake, state machine
│   ├── ip/                    # Network layer (IP routing, NAT, ICMP)
│   │   ├── router.go
│   │   └── icmp.go
│   ├── data_link/             # Data link layer (CSMA/CA MAC)
│   │   └── mac.go
│   ├── phy/                   # Physical layer (chirp, CRC, Manchester)
│   │   ├── chirp.go
│   │   ├── crc.go
│   │   ├── frame.go
│   │   └── linecoding.go
│   ├── encode/                # Signal encoding
│   │   └── encoder.go
│   ├── decode/                # Signal decoding
│   │   └── decoder.go
│   └── utils/                 # Utilities
│       └── utils.go
├── docs/                      # Documentation
│   └── report.tex
├── go.mod
└── README.md

Key Files for UDP/DNS and TCP/HTTP:

Implementation Details

UDP and DNS Implementation

UDP Packet Creation (src/transport/udp.go):

func CreateUDPPacket(srcIP, dstIP net.IP, srcPort, dstPort uint16, payload []byte) ([]byte, error) {
    // 1. Create IPv4 layer
    ipLayer := &layers.IPv4{
        Version:  4,
        TTL:      64,
        SrcIP:    srcIP,
        DstIP:    dstIP,
        Protocol: layers.IPProtocolUDP,
    }
    
    // 2. Create UDP layer
    udpLayer := &layers.UDP{
        SrcPort: layers.UDPPort(srcPort),
        DstPort: layers.UDPPort(dstPort),
    }
    
    // 3. Set network layer for checksum calculation
    udpLayer.SetNetworkLayerForChecksum(ipLayer)
    
    // 4. Serialize layers: [IP][UDP][Payload]
    gopacket.SerializeLayers(buffer, opts, ipLayer, udpLayer, gopacket.Payload(payload))
}

DNS Query Generation (src/transport/dns.go):

func (ds *DNSServer) CreateDNSQuery(domain string) ([]byte, uint16, error) {
    // Assign unique query ID
    queryID := ds.queryID
    ds.queryID++
    
    // Build DNS query packet
    dns := &layers.DNS{
        ID:     queryID,
        QR:     false,              // 0 = query
        OpCode: layers.DNSOpCodeQuery,
        RD:     true,               // Recursion desired
        Questions: []layers.DNSQuestion,
    }
    
    // Serialize DNS packet
    return buffer.Bytes(), queryID, nil
}

DNS Response Handling (src/transport/dns.go):

func (ds *DNSServer) HandleDNSResponse(payload []byte) {
    // 1. Parse DNS response
    ip, queryID, err := ds.ParseDNSResponse(payload)
    
    // 2. Find matching pending query
    ds.mu.Lock()
    if ch, ok := ds.pendingQuery[queryID]; ok {
        ch <- ip  // Send resolved IP to waiting goroutine
    }
    ds.mu.Unlock()
}

func (ds *DNSServer) WaitForResponse(queryID uint16) (net.IP, error) {
    ch := make(chan net.IP, 1)
    ds.pendingQuery[queryID] = ch
    
    // Wait for response with timeout
    select {
    case ip := <-ch:
        return ip, nil
    case <-time.After(5 * time.Second):
        return nil, fmt.Errorf("DNS timeout")
    }
}

Complete DNS Workflow:

// 1. Node 1 creates DNS query
dnsPayload, queryID := dnsServer.CreateDNSQuery("www.example.com")

// 2. Wrap in UDP packet
udpPacket := CreateUDPPacket(node1IP, node2IP, 55123, 53, dnsPayload)

// 3. Send through acoustic link to Node 2
node.QueueIPFrame(node2IP, udpPacket)

// 4. Node 2 receives and forwards to internet DNS (1.1.1.1:53)
router.ForwardToInternet(udpPacket)

// 5. Wait for response
ip := dnsServer.WaitForResponse(queryID)

// 6. Use resolved IP for subsequent operations
fmt.Printf("Resolved: %s -> %s\n", domain, ip)

TCP and HTTP Implementation

TCP State Machine (src/transport/tcp.go):

type TCPClient struct {
    SrcIP, DstIP     net.IP
    SrcPort, DstPort layers.TCPPort
    SeqNum, AckNum   uint32
    State            string  // "CLOSED", "SYN_SENT", "ESTABLISHED", "FIN_WAIT"
    DataBuffer       bytes.Buffer
    SendIPPacket     func(destIP net.IP, payload []byte)
}

// 3-Way Handshake
func (c *TCPClient) Connect() error {
    // 1. Send SYN
    c.State = "SYN_SENT"
    c.sendSegment(true, false, false, nil)  // SYN=true, ACK=false, FIN=false
    
    // 2. Wait for SYN-ACK (handled in HandlePacket)
    // 3. Send ACK (handled in HandlePacket when SYN-ACK received)
    
    // Block until ESTABLISHED
    for c.State != "ESTABLISHED" {
        c.cond.Wait()
    }
    return nil
}

TCP Packet Handling (src/transport/tcp.go):

func (c *TCPClient) HandlePacket(ip *layers.IPv4, tcp *layers.TCP) {
    switch c.State {
    case "SYN_SENT":
        // Expecting SYN-ACK
        if tcp.SYN && tcp.ACK {
            c.AckNum = tcp.Seq + 1        // ACK server's SYN
            c.SeqNum = tcp.Ack            // Update our sequence number
            c.State = "ESTABLISHED"
            
            // Send final ACK to complete handshake
            c.sendSegment(false, true, false, nil)  // SYN=false, ACK=true
            c.cond.Broadcast()  // Wake up Connect()
        }
        
    case "ESTABLISHED":
        // Handle incoming data
        if len(tcp.Payload) > 0 {
            // Check sequence number (in-order delivery)
            if tcp.Seq == c.AckNum {
                c.DataBuffer.Write(tcp.Payload)  // Accumulate HTTP response
                c.AckNum += uint32(len(tcp.Payload))
                c.sendSegment(false, true, false, nil)  // Send ACK
            }
        }
        
        // Handle FIN (server closing)
        if tcp.FIN {
            c.AckNum++
            c.sendSegment(false, true, false, nil)  // ACK the FIN
        }
    }
}

HTTP Request Generation (cmd/node/main.go):

func (n *Node) RunCurl(domain string) {
    // 1. Resolve domain name
    n.SendDNSQuery(domain, func(resolvedIP net.IP) {
        // 2. Create TCP connection
        srcPort := uint16(rand.Intn(10000) + 50000)
        tcpClient := transport.NewTCPClient(
            n.ipAddr,              // 172.18.0.1
            resolvedIP,            // 93.184.216.34 (example.com)
            srcPort,               // Random port (e.g., 55123)
            80,                    // HTTP port
            0x12345678,            // Initial sequence number
            n.QueueIPFrame,        // Send function
        )
        
        // 3. Establish TCP connection (3-way handshake)
        if err := tcpClient.Connect(); err != nil {
            fmt.Printf("Connection failed: %v\n", err)
            return
        }
        
        // 4. Send HTTP GET request
        httpRequest := fmt.Sprintf(
            "GET / HTTP/1.0\r\n"+
            "Host: %s\r\n"+
            "User-Agent: Aethernet\r\n"+
            "\r\n",
            domain,
        )
        tcpClient.Write([]byte(httpRequest))  // Sends as PSH+ACK segment
        
        // 5. Wait for HTTP response (15 second timeout)
        time.Sleep(15 * time.Second)
        
        // 6. Display accumulated response
        fmt.Println("\n--- HTTP Response ---")
        fmt.Println(tcpClient.Read())  // Read from DataBuffer
        fmt.Println("---------------------")
        
        // 7. Close TCP connection
        tcpClient.Close()  // Sends FIN
    })
}

TCP Segment Serialization:

func (c *TCPClient) sendSegment(syn, ack, fin bool, data []byte) {
    // Create TCP layer
    tcp := &layers.TCP{
        SrcPort: c.SrcPort,
        DstPort: c.DstPort,
        Seq:     c.SeqNum,
        Ack:     c.AckNum,
        SYN:     syn,
        ACK:     ack,
        FIN:     fin,
        PSH:     len(data) > 0,  // Push flag if sending data
        Window:  65535,
    }
    
    // Create IP layer
    ip := &layers.IPv4{
        SrcIP:    c.SrcIP,
        DstIP:    c.DstIP,
        Protocol: layers.IPProtocolTCP,
        TTL:      64,
    }
    
    // Set network layer for TCP checksum
    tcp.SetNetworkLayerForChecksum(ip)
    
    // Serialize: [IP][TCP][HTTP Data]
    gopacket.SerializeLayers(buffer, opts, ip, tcp, gopacket.Payload(data))
    
    // Send through lower layers
    c.SendIPPacket(c.DstIP, buffer.Bytes())
}

Configuration Parameters

Edit cmd/node/main.go to adjust:

const (
    // Transport layer
    TCPTimeOut    = 15 * time.Second   // TCP response timeout
    CloudflareDNS = "1.1.1.1"          // Upstream DNS server
    
    // Data link layer
    RTTTimeout    = 350 * time.Millisecond  // ACK timeout
    MaxRetries    = 2                        // Max retransmission attempts
    
    // Network layer
    GatewayMAC    = "00-00-5e-00-01-01"     // Gateway MAC address
)

Troubleshooting

DNS Resolution Fails

  • Verify Node 2 has internet connectivity
  • Check upstream DNS servers (1.1.1.1, 8.8.8.8) are accessible
  • Increase DNS timeout if network is slow
  • Check firewall allows outbound UDP port 53

TCP Connection Timeout

  • Verify DNS resolution succeeded first
  • Check Node 2’s NAT is properly forwarding TCP packets
  • Increase TCPTimeOut if remote server is slow
  • Verify destination server is reachable (try direct ping first)

HTTP Response Incomplete

  • Increase TCPTimeOut to allow more time for response
  • Check acoustic link quality (reduce noise, adjust volume)
  • Verify TCP sequence numbers are correct
  • Check for packet loss in lower layers

No Audio I/O

  • Ensure JACK server is running
  • Check audio device configuration
  • Verify audio cables are connected
  • Test with simple ping first before trying DNS/HTTP

High Packet Loss

  • Reduce ambient noise
  • Adjust microphone/speaker volume
  • Increase buffer size in JACK configuration
  • Use shorter cable connections when possible