140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/binary"
|
|
"encoding/csv"
|
|
"encoding/pem"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
quic "github.com/AeonDave/mp-quic-go"
|
|
)
|
|
|
|
func generateTLSConfig() *tls.Config {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
|
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
|
|
|
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &tls.Config{
|
|
Certificates: []tls.Certificate{tlsCert},
|
|
NextProtos: []string{"mpquic-exp"},
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
addr := flag.String("addr", "0.0.0.0:4242", "Address to listen on")
|
|
maxPaths := flag.Int("maxpaths", 4, "Maximum number of paths")
|
|
outCSV := flag.String("output", "app_metrics.csv", "Output CSV for application-level metrics")
|
|
flag.Parse()
|
|
|
|
listener, err := quic.ListenAddr(*addr, generateTLSConfig(), &quic.Config{
|
|
MaxPaths: *maxPaths,
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Printf("Server listening on %s with max %d paths\n", *addr, *maxPaths)
|
|
fmt.Printf("Logging app-level metrics to %s\n", *outCSV)
|
|
|
|
// Open CSV
|
|
f, err := os.OpenFile(*outCSV, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
log.Fatal("Failed to open CSV:", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
writer := csv.NewWriter(f)
|
|
writer.Write([]string{"sequence_number", "drone_send_time", "ground_recv_time", "latency_ns", "bytes_received"})
|
|
writer.Flush()
|
|
|
|
// Mutex to protect CSV writer if multiple streams are used
|
|
var mu sync.Mutex
|
|
|
|
for {
|
|
conn, err := listener.Accept(context.Background())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Println("Client connected:", conn.RemoteAddr())
|
|
|
|
go func(conn *quic.Conn) {
|
|
for {
|
|
stream, err := conn.AcceptStream(context.Background())
|
|
if err != nil {
|
|
fmt.Println("Session error:", err)
|
|
return
|
|
}
|
|
go func(stream *quic.Stream) {
|
|
header := make([]byte, 4)
|
|
for {
|
|
// Read 4-byte length prefix
|
|
_, err := io.ReadFull(stream, header)
|
|
if err != nil {
|
|
fmt.Printf("Stream closed\n")
|
|
return
|
|
}
|
|
|
|
length := binary.BigEndian.Uint32(header)
|
|
if length < 20 {
|
|
fmt.Printf("Warning: Invalid frame length %d\n", length)
|
|
continue
|
|
}
|
|
|
|
// Read rest of the payload
|
|
payload := make([]byte, length-4)
|
|
_, err = io.ReadFull(stream, payload)
|
|
if err != nil {
|
|
fmt.Printf("Stream read error: %v\n", err)
|
|
return
|
|
}
|
|
|
|
recvTime := time.Now().UnixNano()
|
|
|
|
seqNum := binary.BigEndian.Uint64(payload[0:8])
|
|
sendTime := binary.BigEndian.Uint64(payload[8:16])
|
|
latency := recvTime - int64(sendTime)
|
|
|
|
mu.Lock()
|
|
writer.Write([]string{
|
|
strconv.FormatUint(seqNum, 10),
|
|
strconv.FormatUint(sendTime, 10),
|
|
strconv.FormatInt(recvTime, 10),
|
|
strconv.FormatInt(latency, 10),
|
|
strconv.FormatUint(uint64(length), 10),
|
|
})
|
|
// Periodically flush?
|
|
if seqNum % 1000 == 0 {
|
|
writer.Flush()
|
|
}
|
|
mu.Unlock()
|
|
}
|
|
}(stream)
|
|
}
|
|
}(conn)
|
|
}
|
|
}
|