Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ All notable changes to this project will be documented in this file.
### Changes
- Client
- Demote passive-mode liveness session-down log messages from Info to Debug to reduce log noise when no dataplane action is taken
- Telemetry
- Add `Version` (uint8) and `TargetIP` ([4]byte) fields to LocationOffset wire format (v1, 174 bytes), with version validation on unmarshal to enable safe future format evolution
- Tools
- Update TWAMP signed packet parser byte offsets and `OffsetInfo` struct for LocationOffset v1 layout
- E2E Tests
- Add geoprobe E2E test (`TestE2E_GeoprobeDiscovery`) that exercises the full geolocation flow: deploy geolocation program, create probe onchain, start geoprobe-agent container, and verify the telemetry-agent discovers and measures the probe via TWAMP
- Add geoprobe Docker image, geolocation program build/deploy support, and manager geolocation CLI configuration to the E2E devnet infrastructure
Expand Down
2 changes: 2 additions & 0 deletions controlplane/telemetry/cmd/geoprobe-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,11 +749,13 @@ func runMeasurementCycle(
sentCount := 0
for addr, measuredRttNs := range rttData {
compositeOffset := geoprobe.LocationOffset{
Version: geoprobe.LocationOffsetVersion,
MeasurementSlot: slot,
MeasuredRttNs: measuredRttNs,
Lat: dzdOffset.Lat,
Lng: dzdOffset.Lng,
RttNs: dzdOffset.RttNs + measuredRttNs,
TargetIP: geoprobe.IPToTargetIP(addr.Host),
NumReferences: 1,
References: []geoprobe.LocationOffset{*dzdOffset},
}
Expand Down
5 changes: 5 additions & 0 deletions controlplane/telemetry/cmd/geoprobe-target/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ type OffsetOutput struct {
SourceAddr string `json:"source_addr"`
AuthorityPubkey string `json:"authority_pubkey"`
SenderPubkey string `json:"sender_pubkey"`
TargetIP string `json:"target_ip"`
ReferencePoint CoordinateOutput `json:"reference_point"`
RttMs float64 `json:"rtt_ms"`
MeasuredRttMs float64 `json:"measured_rtt_ms"`
Expand All @@ -343,6 +344,7 @@ type CoordinateOutput struct {
type ReferenceOutput struct {
AuthorityPubkey string `json:"authority_pubkey"`
SenderPubkey string `json:"sender_pubkey"`
TargetIP string `json:"target_ip"`
Location CoordinateOutput `json:"location"`
RttMs float64 `json:"rtt_ms"`
MeasuredRttMs float64 `json:"measured_rtt_ms"`
Expand All @@ -359,6 +361,7 @@ func formatLocationOffset(offset *geoprobe.LocationOffset, addr *net.UDPAddr, si
SourceAddr: addr.String(),
AuthorityPubkey: formatPubkey(offset.AuthorityPubkey[:]),
SenderPubkey: formatPubkey(offset.SenderPubkey[:]),
TargetIP: geoprobe.FormatTargetIP(offset.TargetIP),
ReferencePoint: formatCoordinate(offset.Lat, offset.Lng),
RttMs: rttMs,
MeasuredRttMs: measuredRttMs,
Expand All @@ -378,6 +381,7 @@ func formatLocationOffset(offset *geoprobe.LocationOffset, addr *net.UDPAddr, si
output.DZDReferenceChain = append(output.DZDReferenceChain, ReferenceOutput{
AuthorityPubkey: formatPubkey(ref.AuthorityPubkey[:]),
SenderPubkey: formatPubkey(ref.SenderPubkey[:]),
TargetIP: geoprobe.FormatTargetIP(ref.TargetIP),
Location: formatCoordinate(ref.Lat, ref.Lng),
RttMs: refRttMs,
MeasuredRttMs: refMeasuredRttMs,
Expand All @@ -394,6 +398,7 @@ func formatTextOutput(output OffsetOutput) string {
sb.WriteString(fmt.Sprintf("[%s] Received LocationOffset from Probe\n", output.Timestamp))
sb.WriteString(fmt.Sprintf(" Authority: %s\n", output.AuthorityPubkey))
sb.WriteString(fmt.Sprintf(" Sender: %s\n", output.SenderPubkey))
sb.WriteString(fmt.Sprintf(" Target IP: %s\n", output.TargetIP))
sb.WriteString(fmt.Sprintf(" Reference Point: %s\n", output.ReferencePoint.Formatted))
sb.WriteString(fmt.Sprintf(" RTT to Target: %.2fms\n", output.RttMs))
sb.WriteString(fmt.Sprintf(" Measured RTT: %.2fms\n", output.MeasuredRttMs))
Expand Down
44 changes: 44 additions & 0 deletions controlplane/telemetry/internal/geoprobe/offset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package geoprobe
import (
"fmt"
"io"
"net"

bin "github.com/gagliardetto/binary"
)

const (
LocationOffsetVersion = 1

MaxReferenceDepth = 2
MaxTotalReferences = 5
)
Expand All @@ -22,17 +25,37 @@ const (
// Based on RFC16: Geolocation Verification
type LocationOffset struct {
Signature [64]byte // Ed25519 signature over the serialized bytes (excluding this field)
Version uint8 // Wire format version (currently 1)
AuthorityPubkey [32]byte // Signer's public key (metrics publisher key or probe signing key)
SenderPubkey [32]byte // Device public key (DZD or Probe)
MeasurementSlot uint64 // Current DoubleZero Slot when measurement was taken
MeasuredRttNs uint64 // Measured RTT in nanoseconds (minimum observed)
Lat float64 // Reference point latitude in WGS84 (decimal degrees)
Lng float64 // Reference point longitude in WGS84 (decimal degrees)
RttNs uint64 // Accumulated RTT to target in nanoseconds from lat/lng
TargetIP [4]byte // IPv4 address of the TWAMP measurement target
NumReferences uint8 // Number of reference offsets in the chain
References []LocationOffset // Reference offsets (recursive chain for verification)
}

// IPToTargetIP converts an IP address string to a [4]byte for use in TargetIP.
func IPToTargetIP(host string) [4]byte {
ip := net.ParseIP(host)
if ip == nil {
return [4]byte{}
}
ip4 := ip.To4()
if ip4 == nil {
return [4]byte{}
}
return [4]byte{ip4[0], ip4[1], ip4[2], ip4[3]}
}

// FormatTargetIP formats a [4]byte TargetIP as a dotted-decimal string.
func FormatTargetIP(ip [4]byte) string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}

// Marshal serializes the LocationOffset to bytes using Borsh encoding.
func (o *LocationOffset) Marshal() ([]byte, error) {
buf := make([]byte, 0, 256)
Expand All @@ -42,6 +65,9 @@ func (o *LocationOffset) Marshal() ([]byte, error) {
if err := enc.Encode(o.Signature); err != nil {
return nil, fmt.Errorf("failed to encode signature: %w", err)
}
if err := enc.Encode(o.Version); err != nil {
return nil, fmt.Errorf("failed to encode version: %w", err)
}
if err := enc.Encode(o.AuthorityPubkey); err != nil {
return nil, fmt.Errorf("failed to encode authority pubkey: %w", err)
}
Expand All @@ -63,6 +89,9 @@ func (o *LocationOffset) Marshal() ([]byte, error) {
if err := enc.Encode(o.RttNs); err != nil {
return nil, fmt.Errorf("failed to encode rtt: %w", err)
}
if err := enc.Encode(o.TargetIP); err != nil {
return nil, fmt.Errorf("failed to encode target ip: %w", err)
}
if err := enc.Encode(o.NumReferences); err != nil {
return nil, fmt.Errorf("failed to encode num references: %w", err)
}
Expand Down Expand Up @@ -108,6 +137,12 @@ func (o *LocationOffset) unmarshalHelper(data []byte, dec *bin.Decoder, depth in
if err := dec.Decode(&o.Signature); err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
if err := dec.Decode(&o.Version); err != nil {
return fmt.Errorf("failed to decode version: %w", err)
}
if o.Version != LocationOffsetVersion {
return fmt.Errorf("unsupported location offset version %d (expected %d)", o.Version, LocationOffsetVersion)
}
if err := dec.Decode(&o.AuthorityPubkey); err != nil {
return fmt.Errorf("failed to decode authority pubkey: %w", err)
}
Expand All @@ -129,6 +164,9 @@ func (o *LocationOffset) unmarshalHelper(data []byte, dec *bin.Decoder, depth in
if err := dec.Decode(&o.RttNs); err != nil {
return fmt.Errorf("failed to decode rtt: %w", err)
}
if err := dec.Decode(&o.TargetIP); err != nil {
return fmt.Errorf("failed to decode target ip: %w", err)
}
if err := dec.Decode(&o.NumReferences); err != nil {
return fmt.Errorf("failed to decode num references: %w", err)
}
Expand All @@ -149,6 +187,9 @@ func (o *LocationOffset) GetSigningBytes() ([]byte, error) {
w := &bytesWriter{buf: buf}
enc := bin.NewBorshEncoder(w)

if err := enc.Encode(o.Version); err != nil {
return nil, fmt.Errorf("failed to encode version: %w", err)
}
if err := enc.Encode(o.AuthorityPubkey); err != nil {
return nil, fmt.Errorf("failed to encode authority pubkey: %w", err)
}
Expand All @@ -170,6 +211,9 @@ func (o *LocationOffset) GetSigningBytes() ([]byte, error) {
if err := enc.Encode(o.RttNs); err != nil {
return nil, fmt.Errorf("failed to encode rtt: %w", err)
}
if err := enc.Encode(o.TargetIP); err != nil {
return nil, fmt.Errorf("failed to encode target ip: %w", err)
}
if err := enc.Encode(o.NumReferences); err != nil {
return nil, fmt.Errorf("failed to encode num references: %w", err)
}
Expand Down
Loading
Loading