From d9e746dee2f556d0f037e55f397fa6e077263fc0 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Tue, 23 Jun 2026 09:53:41 +0200 Subject: [PATCH 1/7] working poc of icmp ttl exceeded reading --- internal/experiment/tlsmiddlebox/conn_test.go | 4 +- internal/experiment/tlsmiddlebox/dialer.go | 9 +- internal/experiment/tlsmiddlebox/tracing.go | 124 ++++++++++++++++-- .../experiment/tlsmiddlebox/tracing_test.go | 8 +- 4 files changed, 129 insertions(+), 16 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/conn_test.go b/internal/experiment/tlsmiddlebox/conn_test.go index 99f8b9b753..938d929480 100644 --- a/internal/experiment/tlsmiddlebox/conn_test.go +++ b/internal/experiment/tlsmiddlebox/conn_test.go @@ -100,7 +100,7 @@ func TestSetTTL(t *testing.T) { if testing.Short() { t.Skip("skip test in short mode") } - d := NewDialerTTLWrapper() + d := NewDialerTTLWrapper(5000) ctx := context.Background() conn, err := d.DialContext(ctx, "tcp", "1.1.1.1:80") if err != nil { @@ -145,7 +145,7 @@ func TestGetSoErr(t *testing.T) { defer srvr.Close() URL, err := url.Parse(srvr.URL) runtimex.PanicOnError(err, "url.Parse failed") - d := NewDialerTTLWrapper() + d := NewDialerTTLWrapper(5000) ctx := context.Background() conn, err := d.DialContext(ctx, "tcp", URL.Host) if err != nil { diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index 5db437b727..65036bbbbb 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -15,9 +15,14 @@ import ( const timeout time.Duration = 15 * time.Second -func NewDialerTTLWrapper() model.Dialer { +func NewDialerTTLWrapper(localPort int) model.Dialer { return &dialerTTLWrapper{ - Dialer: &net.Dialer{Timeout: timeout}, + Dialer: &net.Dialer{ + Timeout: timeout, + LocalAddr: &net.TCPAddr{ + Port: localPort, + }, + }, } } diff --git a/internal/experiment/tlsmiddlebox/tracing.go b/internal/experiment/tlsmiddlebox/tracing.go index dd55b40457..206a037e6c 100644 --- a/internal/experiment/tlsmiddlebox/tracing.go +++ b/internal/experiment/tlsmiddlebox/tracing.go @@ -7,7 +7,9 @@ package tlsmiddlebox import ( "context" "crypto/tls" + "encoding/binary" "errors" + "fmt" "net" "sort" "sync" @@ -19,6 +21,8 @@ import ( "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" utls "gitlab.com/yawning/utls.git" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" ) // ClientIDs to map configurable inputs to uTLS fingerprints @@ -30,36 +34,137 @@ var ClientIDs = map[int]*utls.ClientHelloID{ 4: &utls.HelloIOS_Auto, } +// ICMP listener for TTL-exceeded messages +func (m *Measurer) ListenTTLExceeded(ctx context.Context, logger model.Logger, target string) error { + // The target string contains both the IP and port + host, _, err := net.SplitHostPort(target) + if err != nil { + return err + } + + targetIP := net.ParseIP(host) + if targetIP == nil { + return fmt.Errorf("invalid target IP: %q", target) + } + + fmt.Println("Target IP:", host) + + conn, err := icmp.ListenPacket("ip4:1", "0.0.0.0") + if err != nil { + return err + } + + defer conn.Close() + + go func() { + <-ctx.Done() + conn.Close() + }() + + buf := make([]byte, 1500) + + for { + n, peer, err := conn.ReadFrom(buf) + if err != nil { + return err + } + + msg, err := icmp.ParseMessage(1, buf[:n]) + if err != nil { + continue + } + + switch msg.Type { + case ipv4.ICMPTypeTimeExceeded: + te, ok := msg.Body.(*icmp.TimeExceeded) + if !ok { + continue + } + + data := te.Data + + innerIP, err := ipv4.ParseHeader(data) + if err != nil { + continue + } + + if !innerIP.Dst.Equal(targetIP) { + continue + } + + ipHeaderLen := innerIP.Len + tcpData := te.Data[ipHeaderLen:] + + srcPort := binary.BigEndian.Uint16(tcpData[0:2]) + + ttl := -1 + + //randomize the base values here + if srcPort > 4000 && srcPort < 5000 { + ttl = int(srcPort) - 4000 + } else if srcPort > 5000 { + ttl = int(srcPort) - 5000 + } + + // dstPort := binary.BigEndian.Uint16(tcpData[2:4]) + + // fmt.Printf( + // "TTL expired for probe -> src=%s dst=%s ttl=%d router=%s src_port=%d dst_port=%d\n", + // innerIP.Src, + // innerIP.Dst, + // innerIP.TTL, + // peer, + // srcPort, + // dstPort, + // ) + + logger.Infof("TTL expired probe --> Source IP=%s Initial TTL:%d", peer, ttl) + + // logger.Infof("ICMP TTL exceeded from %v", peer) + // logger.Infof("ICMP message %v", te) + } + } +} + // TLSTrace performs tracing using control and target SNI func (m *Measurer) TLSTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, targetSNI string, trace *CompleteTrace) { + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + go m.ListenTTLExceeded(ctx, logger, address) + // perform an iterative trace with the control SNI - trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol()) + trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol(), "control") // perform an iterative trace with the target SNI - trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI) + trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI, "censored") } // startIterativeTrace creates a Trace and calls iterativeTrace func (m *Measurer) startIterativeTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string) (tr *IterativeTrace) { + address string, sni string, controlOrCensored string) (tr *IterativeTrace) { tr = &IterativeTrace{ SNI: sni, Iterations: []*Iteration{}, } maxTTL := m.config.maxttl() - m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr) + if controlOrCensored == "control" { + m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr, 4000) + } else { + m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr, 5000) + } tr.Iterations = alignIterations(tr.Iterations) return } // traceWithIncreasingTTLs performs iterative tracing with increasing TTL values func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string, maxTTL int64, trace *IterativeTrace) { + address string, sni string, maxTTL int64, trace *IterativeTrace, basePort int) { ticker := time.NewTicker(m.config.delay()) wg := new(sync.WaitGroup) for i := int64(1); i <= maxTTL; i++ { wg.Add(1) - go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg) + go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg, basePort) <-ticker.C } wg.Wait() @@ -67,12 +172,15 @@ func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zer // handshakeWithTTL performs the TLS Handshake using the passed ttl value func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) { + address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup, basePort int) { defer wg.Done() trace := measurexlite.NewTrace(index, zeroTime) // 1. Connect to the target IP // TODO(DecFox, bassosimone): Do we need a trace for this TCP connect? - d := NewDialerTTLWrapper() + + localPort := basePort + int(index*1000) + ttl + d := NewDialerTTLWrapper(localPort) + ol := logx.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni) conn, err := d.DialContext(ctx, "tcp", address) if err != nil { diff --git a/internal/experiment/tlsmiddlebox/tracing_test.go b/internal/experiment/tlsmiddlebox/tracing_test.go index e6dc2f897c..d5c3d1de52 100644 --- a/internal/experiment/tlsmiddlebox/tracing_test.go +++ b/internal/experiment/tlsmiddlebox/tracing_test.go @@ -31,7 +31,7 @@ func TestStartIterativeTrace(t *testing.T) { m := NewExperimentMeasurer(Config{}) zeroTime := time.Now() ctx := context.Background() - trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com") + trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", "control") if trace.SNI != "example.com" { t.Fatal("unexpected servername") } @@ -60,7 +60,7 @@ func TestStartIterativeTrace(t *testing.T) { m := NewExperimentMeasurer(Config{}) zeroTime := time.Now() ctx := context.Background() - trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com") + trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", "control") if trace.SNI != "example.com" { t.Fatal("unexpected servername") } @@ -91,7 +91,7 @@ func TestHandshakeWithTTL(t *testing.T) { ctx := context.Background() wg := new(sync.WaitGroup) wg.Add(1) - m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg) + m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg, 5000) if len(tr.Iterations) != 1 { t.Fatal("unexpected number of iterations") } @@ -122,7 +122,7 @@ func TestHandshakeWithTTL(t *testing.T) { ctx := context.Background() wg := new(sync.WaitGroup) wg.Add(1) - m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg) + m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg, 5000) if len(tr.Iterations) != 1 { t.Fatal("unexpected number of iterations") } From 15e8ff7804f51532d6069b0c175dece552a19b26 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Thu, 25 Jun 2026 14:20:22 +0200 Subject: [PATCH 2/7] added MSG_ERRQUEUE and IP_RECVERR --- internal/experiment/tlsmiddlebox/dialer.go | 112 ++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index 65036bbbbb..e0c841bb6e 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -11,10 +11,17 @@ import ( "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" + "golang.org/x/sys/unix" ) const timeout time.Duration = 15 * time.Second +// Define the Linux socket option enabling error queueing +const IP_RECVERR = 12 + +// Define the Linux recvmsg flag for reading the error queue +const MSG_ERRQUEUE = 2 + func NewDialerTTLWrapper(localPort int) model.Dialer { return &dialerTTLWrapper{ Dialer: &net.Dialer{ @@ -31,6 +38,18 @@ type dialerTTLWrapper struct { Dialer model.SimpleDialer } +// ttlConn wraps the TCP connection +type ttlConn struct { + *net.TCPConn + fd int +} + +type Hop struct { + Addr net.IP + Type uint8 + Code uint8 +} + var _ model.Dialer = &dialerTTLWrapper{} // DialContext implements model.Dialer.DialContext @@ -39,8 +58,39 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr if err != nil { return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) } - return &dialerTTLWrapperConn{ - Conn: conn, + + tcp := conn.(*net.TCPConn) + + raw, err := tcp.SyscallConn() + if err != nil { + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + // Extract underlying socket file descriptor from TCP connection + var fd int + err = raw.Control(func(f uintptr) { + fd = int(f) + }) + + if err != nil { + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + // Set the IP_RECVERR socket option to enable ICMP errors to be stored in the socket error queue + // Such errors will be subsequently read with MSG_ERRQUEUE + err = raw.Control(func(f uintptr) { + unix.SetsockoptInt(int(f), unix.IPPROTO_IP, IP_RECVERR, 1) + }) + + // Set the SO_TIMESTAMPNS socket option to enable + + if err != nil { + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + return &ttlConn{ + TCPConn: tcp, + fd: fd, }, nil } @@ -48,3 +98,61 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr func (d *dialerTTLWrapper) CloseIdleConnections() { // nothing to do here } + +// ReadErrQueue() uses MSG_ERRQUEUE to read ICMP messages +func (c *ttlConn) ReadErrQueue() (*Hop, error) { + buf := make([]byte, 256) + oob := make([]byte, 512) + + _, oobn, _, _, err := unix.Recvmsg( + c.fd, + buf, + oob, + MSG_ERRQUEUE, + ) + + if err != nil { + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + cms, err := unix.ParseSocketControlMessage((oob[:oobn])) + if err != nil { + return nil, err + } + + for _, cm := range cms { + if cm.Header.Level != unix.IPPROTO_IP { + continue + } + + if cm.Header.Type != IP_RECVERR { + continue + } + + ee := cm.Data + + if len(ee) < 16 { + continue + } + + icmpType := ee[4] + icmpCode := ee[5] + + sa := ee[16:] + + if len(sa) < 8 { + continue + } + + routerIP := net.IPv4(sa[4], sa[5], sa[6], sa[7]) + + return &Hop{ + Addr: routerIP, + Type: icmpType, + Code: icmpCode, + }, nil + + } + + return nil, nil +} From 74ff1aa33ad7f2924e97c69137a91e7cf04ecfaa Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Fri, 26 Jun 2026 09:53:32 +0200 Subject: [PATCH 3/7] wip for msg_errqueue --- internal/experiment/tlsmiddlebox/conn_test.go | 4 +- internal/experiment/tlsmiddlebox/dialer.go | 163 ++++++++++++------ internal/experiment/tlsmiddlebox/tracing.go | 143 +++------------ .../experiment/tlsmiddlebox/tracing_test.go | 8 +- 4 files changed, 145 insertions(+), 173 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/conn_test.go b/internal/experiment/tlsmiddlebox/conn_test.go index 938d929480..99f8b9b753 100644 --- a/internal/experiment/tlsmiddlebox/conn_test.go +++ b/internal/experiment/tlsmiddlebox/conn_test.go @@ -100,7 +100,7 @@ func TestSetTTL(t *testing.T) { if testing.Short() { t.Skip("skip test in short mode") } - d := NewDialerTTLWrapper(5000) + d := NewDialerTTLWrapper() ctx := context.Background() conn, err := d.DialContext(ctx, "tcp", "1.1.1.1:80") if err != nil { @@ -145,7 +145,7 @@ func TestGetSoErr(t *testing.T) { defer srvr.Close() URL, err := url.Parse(srvr.URL) runtimex.PanicOnError(err, "url.Parse failed") - d := NewDialerTTLWrapper(5000) + d := NewDialerTTLWrapper() ctx := context.Background() conn, err := d.DialContext(ctx, "tcp", URL.Host) if err != nil { diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index e0c841bb6e..dbdcc5b97f 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -6,6 +6,7 @@ package tlsmiddlebox import ( "context" + "fmt" "net" "time" @@ -16,20 +17,21 @@ import ( const timeout time.Duration = 15 * time.Second -// Define the Linux socket option enabling error queueing +// Define the Linux IPPROTO_IP socket option enabling error queueing const IP_RECVERR = 12 // Define the Linux recvmsg flag for reading the error queue const MSG_ERRQUEUE = 2 -func NewDialerTTLWrapper(localPort int) model.Dialer { +// Define the Linux SOL_SOCKET socket option enabling nanosecond timestamps +const SO_TIMESTAMPNS = 35 + +// Define the Linux socket control message type for nanosecond timestamps +const SCM_TIMESTAMPNS = 35 + +func NewDialerTTLWrapper() model.Dialer { return &dialerTTLWrapper{ - Dialer: &net.Dialer{ - Timeout: timeout, - LocalAddr: &net.TCPAddr{ - Port: localPort, - }, - }, + Dialer: &net.Dialer{Timeout: timeout}, } } @@ -45,9 +47,11 @@ type ttlConn struct { } type Hop struct { - Addr net.IP - Type uint8 - Code uint8 + Addr net.IP + Type uint8 + Code uint8 + Timestamp time.Time + TTL uint8 } var _ model.Dialer = &dialerTTLWrapper{} @@ -82,7 +86,14 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr unix.SetsockoptInt(int(f), unix.IPPROTO_IP, IP_RECVERR, 1) }) - // Set the SO_TIMESTAMPNS socket option to enable + if err != nil { + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + // Set the SO_TIMESTAMPNS socket option to enable nanosecond timestamps + err = raw.Control(func(f uintptr) { + unix.SetsockoptInt(int(f), unix.SOL_SOCKET, SO_TIMESTAMPNS, 1) + }) if err != nil { return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) @@ -102,57 +113,105 @@ func (d *dialerTTLWrapper) CloseIdleConnections() { // ReadErrQueue() uses MSG_ERRQUEUE to read ICMP messages func (c *ttlConn) ReadErrQueue() (*Hop, error) { buf := make([]byte, 256) - oob := make([]byte, 512) + oob := make([]byte, 4096) - _, oobn, _, _, err := unix.Recvmsg( + n, oobn, _, _, err := unix.Recvmsg( c.fd, buf, oob, MSG_ERRQUEUE, ) - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } + fmt.Println("recvmsg called") + fmt.Println("n:", n, "oobn:", oobn, "err:", err) - cms, err := unix.ParseSocketControlMessage((oob[:oobn])) if err != nil { return nil, err } - for _, cm := range cms { - if cm.Header.Level != unix.IPPROTO_IP { - continue - } - - if cm.Header.Type != IP_RECVERR { - continue - } - - ee := cm.Data - - if len(ee) < 16 { - continue - } - - icmpType := ee[4] - icmpCode := ee[5] - - sa := ee[16:] - - if len(sa) < 8 { - continue - } - - routerIP := net.IPv4(sa[4], sa[5], sa[6], sa[7]) - - return &Hop{ - Addr: routerIP, - Type: icmpType, - Code: icmpCode, - }, nil - - } - + fmt.Printf("RAW OOB: %x\n", oob[:oobn]) return nil, nil + + // buf := make([]byte, 256) + // oob := make([]byte, 512) + + // // Read from MSG_ERRQUEUE + // _, oobn, _, _, err := unix.Recvmsg( + // c.fd, + // buf, + // oob, + // MSG_ERRQUEUE, + // ) + + // if err != nil { + // return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + // } + + // var ( + // hop Hop + // haveICMP bool + // haveTimestamp bool + // ts time.Time + // ) + + // cms, err := unix.ParseSocketControlMessage((oob[:oobn])) + // if err != nil { + // return nil, err + // } + + // for i, cm := range cms { + // fmt.Printf("=== CMS[%d] ===\n", i) + // fmt.Printf("Level: %d Type: %d Len: %d\n", + // cm.Header.Level, + // cm.Header.Type, + // len(cm.Data), + // ) + + // fmt.Printf("RAW DATA: %x\n", cm.Data) + // } + + // for _, cm := range cms { + + // // Check for timestamp error + // if cm.Header.Level == unix.SOL_SOCKET && cm.Header.Type == SCM_TIMESTAMPNS { + // sec := int64(binary.LittleEndian.Uint64(cm.Data[0:8])) + // nsec := int64(binary.LittleEndian.Uint64(cm.Data[8:16])) + // ts = time.Unix(sec, nsec) + // haveTimestamp = true + // continue + // } + + // // Check for ICMP error + // if cm.Header.Level == unix.IPPROTO_IP && cm.Header.Type == IP_RECVERR { + // fmt.Printf("\n--- IP_RECVERR ---\n") + // fmt.Printf("len(cm.Data): %d\n", len(cm.Data)) + // fmt.Printf("hex dump: %x\n", cm.Data) + + // ee := cm.Data + + // if len(ee) < 16 { + // continue + // } + + // hop.Type = ee[4] + // hop.Code = ee[5] + + // sa := ee[16:] + // if len(sa) >= 8 { + // hop.Addr = net.IPv4(sa[4], sa[5], sa[6], sa[7]) + // } + + // haveICMP = true + // } + // } + + // if !haveICMP { + // return nil, nil + // } + + // if haveTimestamp { + // hop.Timestamp = ts + // } + + // return &hop, nil } diff --git a/internal/experiment/tlsmiddlebox/tracing.go b/internal/experiment/tlsmiddlebox/tracing.go index 206a037e6c..545c6e5b87 100644 --- a/internal/experiment/tlsmiddlebox/tracing.go +++ b/internal/experiment/tlsmiddlebox/tracing.go @@ -7,7 +7,6 @@ package tlsmiddlebox import ( "context" "crypto/tls" - "encoding/binary" "errors" "fmt" "net" @@ -21,8 +20,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" utls "gitlab.com/yawning/utls.git" - "golang.org/x/net/icmp" - "golang.org/x/net/ipv4" ) // ClientIDs to map configurable inputs to uTLS fingerprints @@ -34,137 +31,36 @@ var ClientIDs = map[int]*utls.ClientHelloID{ 4: &utls.HelloIOS_Auto, } -// ICMP listener for TTL-exceeded messages -func (m *Measurer) ListenTTLExceeded(ctx context.Context, logger model.Logger, target string) error { - // The target string contains both the IP and port - host, _, err := net.SplitHostPort(target) - if err != nil { - return err - } - - targetIP := net.ParseIP(host) - if targetIP == nil { - return fmt.Errorf("invalid target IP: %q", target) - } - - fmt.Println("Target IP:", host) - - conn, err := icmp.ListenPacket("ip4:1", "0.0.0.0") - if err != nil { - return err - } - - defer conn.Close() - - go func() { - <-ctx.Done() - conn.Close() - }() - - buf := make([]byte, 1500) - - for { - n, peer, err := conn.ReadFrom(buf) - if err != nil { - return err - } - - msg, err := icmp.ParseMessage(1, buf[:n]) - if err != nil { - continue - } - - switch msg.Type { - case ipv4.ICMPTypeTimeExceeded: - te, ok := msg.Body.(*icmp.TimeExceeded) - if !ok { - continue - } - - data := te.Data - - innerIP, err := ipv4.ParseHeader(data) - if err != nil { - continue - } - - if !innerIP.Dst.Equal(targetIP) { - continue - } - - ipHeaderLen := innerIP.Len - tcpData := te.Data[ipHeaderLen:] - - srcPort := binary.BigEndian.Uint16(tcpData[0:2]) - - ttl := -1 - - //randomize the base values here - if srcPort > 4000 && srcPort < 5000 { - ttl = int(srcPort) - 4000 - } else if srcPort > 5000 { - ttl = int(srcPort) - 5000 - } - - // dstPort := binary.BigEndian.Uint16(tcpData[2:4]) - - // fmt.Printf( - // "TTL expired for probe -> src=%s dst=%s ttl=%d router=%s src_port=%d dst_port=%d\n", - // innerIP.Src, - // innerIP.Dst, - // innerIP.TTL, - // peer, - // srcPort, - // dstPort, - // ) - - logger.Infof("TTL expired probe --> Source IP=%s Initial TTL:%d", peer, ttl) - - // logger.Infof("ICMP TTL exceeded from %v", peer) - // logger.Infof("ICMP message %v", te) - } - } -} - // TLSTrace performs tracing using control and target SNI func (m *Measurer) TLSTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, targetSNI string, trace *CompleteTrace) { - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - go m.ListenTTLExceeded(ctx, logger, address) - // perform an iterative trace with the control SNI - trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol(), "control") + trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol()) // perform an iterative trace with the target SNI - trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI, "censored") + trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI) } // startIterativeTrace creates a Trace and calls iterativeTrace func (m *Measurer) startIterativeTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string, controlOrCensored string) (tr *IterativeTrace) { + address string, sni string) (tr *IterativeTrace) { tr = &IterativeTrace{ SNI: sni, Iterations: []*Iteration{}, } maxTTL := m.config.maxttl() - if controlOrCensored == "control" { - m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr, 4000) - } else { - m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr, 5000) - } + m.traceWithIncreasingTTLs(ctx, index, zeroTime, logger, address, sni, maxTTL, tr) tr.Iterations = alignIterations(tr.Iterations) return } // traceWithIncreasingTTLs performs iterative tracing with increasing TTL values func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string, maxTTL int64, trace *IterativeTrace, basePort int) { + address string, sni string, maxTTL int64, trace *IterativeTrace) { ticker := time.NewTicker(m.config.delay()) wg := new(sync.WaitGroup) for i := int64(1); i <= maxTTL; i++ { wg.Add(1) - go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg, basePort) + go m.handshakeWithTTL(ctx, index, zeroTime, logger, address, sni, int(i), trace, wg) <-ticker.C } wg.Wait() @@ -172,15 +68,12 @@ func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zer // handshakeWithTTL performs the TLS Handshake using the passed ttl value func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, - address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup, basePort int) { + address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) { defer wg.Done() trace := measurexlite.NewTrace(index, zeroTime) // 1. Connect to the target IP // TODO(DecFox, bassosimone): Do we need a trace for this TCP connect? - - localPort := basePort + int(index*1000) + ttl - d := NewDialerTTLWrapper(localPort) - + d := NewDialerTTLWrapper() ol := logx.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni) conn, err := d.DialContext(ctx, "tcp", address) if err != nil { @@ -189,6 +82,26 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t ol.Stop(err) return } + + tc := conn.(*ttlConn) + go func() { + for { + select { + case <-ctx.Done(): + return + default: + } + hop, err := tc.ReadErrQueue() + if err != nil { + return + } + + if hop != nil { + fmt.Printf("TTL=%d router=%s ts=%v\n", hop.Type, hop.Addr, hop.Timestamp) + } + } + }() + defer conn.Close() // 2. Set the TTL to the passed value err = setConnTTL(conn, ttl) diff --git a/internal/experiment/tlsmiddlebox/tracing_test.go b/internal/experiment/tlsmiddlebox/tracing_test.go index d5c3d1de52..e6dc2f897c 100644 --- a/internal/experiment/tlsmiddlebox/tracing_test.go +++ b/internal/experiment/tlsmiddlebox/tracing_test.go @@ -31,7 +31,7 @@ func TestStartIterativeTrace(t *testing.T) { m := NewExperimentMeasurer(Config{}) zeroTime := time.Now() ctx := context.Background() - trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", "control") + trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com") if trace.SNI != "example.com" { t.Fatal("unexpected servername") } @@ -60,7 +60,7 @@ func TestStartIterativeTrace(t *testing.T) { m := NewExperimentMeasurer(Config{}) zeroTime := time.Now() ctx := context.Background() - trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", "control") + trace := m.startIterativeTrace(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com") if trace.SNI != "example.com" { t.Fatal("unexpected servername") } @@ -91,7 +91,7 @@ func TestHandshakeWithTTL(t *testing.T) { ctx := context.Background() wg := new(sync.WaitGroup) wg.Add(1) - m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg, 5000) + m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg) if len(tr.Iterations) != 1 { t.Fatal("unexpected number of iterations") } @@ -122,7 +122,7 @@ func TestHandshakeWithTTL(t *testing.T) { ctx := context.Background() wg := new(sync.WaitGroup) wg.Add(1) - m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg, 5000) + m.handshakeWithTTL(ctx, 0, zeroTime, model.DiscardLogger, URL.Host, "example.com", 3, tr, wg) if len(tr.Iterations) != 1 { t.Fatal("unexpected number of iterations") } From b729373a00f61686ff6c3ac7491d70dab59076d7 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Tue, 7 Jul 2026 13:35:36 +0200 Subject: [PATCH 4/7] wip for socket options --- internal/experiment/tlsmiddlebox/dialer.go | 84 +++++++++++---------- internal/experiment/tlsmiddlebox/tracing.go | 40 +++++++++- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index dbdcc5b97f..c72ccf1ee6 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -29,21 +29,22 @@ const SO_TIMESTAMPNS = 35 // Define the Linux socket control message type for nanosecond timestamps const SCM_TIMESTAMPNS = 35 -func NewDialerTTLWrapper() model.Dialer { +func NewDialerTTLWrapper(ttl int) model.Dialer { return &dialerTTLWrapper{ Dialer: &net.Dialer{Timeout: timeout}, + TTL: ttl, } } // dialerTTLWrapper wraps errors and also returns a TTL wrapped conn type dialerTTLWrapper struct { Dialer model.SimpleDialer + TTL int } // ttlConn wraps the TCP connection type ttlConn struct { *net.TCPConn - fd int } type Hop struct { @@ -54,8 +55,6 @@ type Hop struct { TTL uint8 } -var _ model.Dialer = &dialerTTLWrapper{} - // DialContext implements model.Dialer.DialContext func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, address string) (net.Conn, error) { conn, err := d.Dialer.DialContext(ctx, network, address) @@ -70,11 +69,19 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) } - // Extract underlying socket file descriptor from TCP connection - var fd int - err = raw.Control(func(f uintptr) { - fd = int(f) - }) + _ = raw.Control(func(fd uintptr)) { + //Set the TTL + _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TTL, d.TTL) + // Set the IP_RECVERR socket option to enable ICMP errors to be stored in the socket error queue + // Such errors will be subsequently read with MSG_ERRQUEUE + _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVERR, 1) + // Set the SO_TIMESTAMPNS socket option to enable nanosecond timestamps + _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_TIMESTAMPNS, 1) + + return &ttlConn{ + TCPConn: tcp, + }, nil + } if err != nil { return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) @@ -85,24 +92,6 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr err = raw.Control(func(f uintptr) { unix.SetsockoptInt(int(f), unix.IPPROTO_IP, IP_RECVERR, 1) }) - - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } - - // Set the SO_TIMESTAMPNS socket option to enable nanosecond timestamps - err = raw.Control(func(f uintptr) { - unix.SetsockoptInt(int(f), unix.SOL_SOCKET, SO_TIMESTAMPNS, 1) - }) - - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } - - return &ttlConn{ - TCPConn: tcp, - fd: fd, - }, nil } // CloseIdleConnections implements model.Dialer.CloseIdleConnections @@ -115,22 +104,39 @@ func (c *ttlConn) ReadErrQueue() (*Hop, error) { buf := make([]byte, 256) oob := make([]byte, 4096) - n, oobn, _, _, err := unix.Recvmsg( - c.fd, - buf, - oob, - MSG_ERRQUEUE, - ) - - fmt.Println("recvmsg called") - fmt.Println("n:", n, "oobn:", oobn, "err:", err) + var hop *Hop + raw, err := c.SyscallConn() if err != nil { - return nil, err + return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) + } + + err = raw.Read(func(fd uintptr) bool) { + n, oobn, _, _, err := unix.Recvmsg( + int(fd), + buf, + oob, + unix.MSG_ERRQUEUE, + ) + + fmt.Println("recvmsg n=%d oobn=%d err=%v\n", n, oobn, err) + + if err == unix.EAGAIN { //nothing to be read from MSG_ERRQUEUE + return false + } + if err != nil { + fmt.Printf("recvmsg error: %v\n", err) + return false + } + if oobn == 0 { + return false + } + + hop = &Hop{} + return false } - fmt.Printf("RAW OOB: %x\n", oob[:oobn]) - return nil, nil + return hop, nil // buf := make([]byte, 256) // oob := make([]byte, 512) diff --git a/internal/experiment/tlsmiddlebox/tracing.go b/internal/experiment/tlsmiddlebox/tracing.go index 545c6e5b87..bfb4345266 100644 --- a/internal/experiment/tlsmiddlebox/tracing.go +++ b/internal/experiment/tlsmiddlebox/tracing.go @@ -70,10 +70,13 @@ func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zer func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) { defer wg.Done() + trace := measurexlite.NewTrace(index, zeroTime) + // 1. Connect to the target IP // TODO(DecFox, bassosimone): Do we need a trace for this TCP connect? - d := NewDialerTTLWrapper() + d := NewDialerTTLWrapper(ttl) + ol := logx.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni) conn, err := d.DialContext(ctx, "tcp", address) if err != nil { @@ -102,6 +105,33 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t } }() + // go func() { + // // poll for a short window only + // deadline := time.NewTimer(800 * time.Millisecond) + // defer deadline.Stop() + + // for { + // select { + // case <-ctx.Done(): + // return + // case <-deadline.C: + // return + // default: + // } + + // hop, _ := tc.ReadErrQueue() + // if hop != nil { + // select { + // case done <- hop: + // default: + // } + // return + // } + + // time.Sleep(10 * time.Millisecond) + // } + // }() + defer conn.Close() // 2. Set the TTL to the passed value err = setConnTTL(conn, ttl) @@ -124,6 +154,14 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t // 4. reset the TTL value to ensure that conn closes successfully // Note: Do not check for errors here _ = setConnTTL(conn, 64) + + // 5. wait for possible ICMP event (non-blocking) + var hop *Hop + select { + case hop = <-done: + case <-time.After(100 * time.Millisecond): + } + iteration := newIterationFromHandshake(ttl, nil, soErr, trace.FirstTLSHandshakeOrNil()) tr.addIterations(iteration) } From 87fe49b52816924b2f997b3f5d95308d72512899 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Tue, 7 Jul 2026 12:29:16 +0000 Subject: [PATCH 5/7] testing reading from msg_errqueue --- internal/experiment/tlsmiddlebox/dialer.go | 32 +++++------ internal/experiment/tlsmiddlebox/tracing.go | 59 ++++++++++----------- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index c72ccf1ee6..91ed2077f2 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -39,7 +39,7 @@ func NewDialerTTLWrapper(ttl int) model.Dialer { // dialerTTLWrapper wraps errors and also returns a TTL wrapped conn type dialerTTLWrapper struct { Dialer model.SimpleDialer - TTL int + TTL int } // ttlConn wraps the TCP connection @@ -69,29 +69,19 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) } - _ = raw.Control(func(fd uintptr)) { - //Set the TTL + _ = raw.Control(func(fd uintptr) { + // Set the TTL _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TTL, d.TTL) // Set the IP_RECVERR socket option to enable ICMP errors to be stored in the socket error queue // Such errors will be subsequently read with MSG_ERRQUEUE _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVERR, 1) // Set the SO_TIMESTAMPNS socket option to enable nanosecond timestamps _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_TIMESTAMPNS, 1) - - return &ttlConn{ - TCPConn: tcp, - }, nil - } - - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } - - // Set the IP_RECVERR socket option to enable ICMP errors to be stored in the socket error queue - // Such errors will be subsequently read with MSG_ERRQUEUE - err = raw.Control(func(f uintptr) { - unix.SetsockoptInt(int(f), unix.IPPROTO_IP, IP_RECVERR, 1) }) + + return &ttlConn{ + TCPConn: tcp, + }, nil } // CloseIdleConnections implements model.Dialer.CloseIdleConnections @@ -111,7 +101,9 @@ func (c *ttlConn) ReadErrQueue() (*Hop, error) { return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) } - err = raw.Read(func(fd uintptr) bool) { + fmt.Printf("SO_ERROR=%v\n", extractSoError(c)) + + err = raw.Read(func(fd uintptr) bool { n, oobn, _, _, err := unix.Recvmsg( int(fd), buf, @@ -119,7 +111,7 @@ func (c *ttlConn) ReadErrQueue() (*Hop, error) { unix.MSG_ERRQUEUE, ) - fmt.Println("recvmsg n=%d oobn=%d err=%v\n", n, oobn, err) + fmt.Printf("recvmsg n=%d oobn=%d err=%v\n", n, oobn, err) if err == unix.EAGAIN { //nothing to be read from MSG_ERRQUEUE return false @@ -134,7 +126,7 @@ func (c *ttlConn) ReadErrQueue() (*Hop, error) { hop = &Hop{} return false - } + }) return hop, nil diff --git a/internal/experiment/tlsmiddlebox/tracing.go b/internal/experiment/tlsmiddlebox/tracing.go index bfb4345266..f131c67ab1 100644 --- a/internal/experiment/tlsmiddlebox/tracing.go +++ b/internal/experiment/tlsmiddlebox/tracing.go @@ -85,25 +85,12 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t ol.Stop(err) return } + defer conn.Close() tc := conn.(*ttlConn) - go func() { - for { - select { - case <-ctx.Done(): - return - default: - } - hop, err := tc.ReadErrQueue() - if err != nil { - return - } - - if hop != nil { - fmt.Printf("TTL=%d router=%s ts=%v\n", hop.Type, hop.Addr, hop.Timestamp) - } - } - }() + + // // 2. start errqueue reader BEFORE handshake + // done := make(chan *Hop, 1) // go func() { // // poll for a short window only @@ -132,15 +119,7 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t // } // }() - defer conn.Close() - // 2. Set the TTL to the passed value - err = setConnTTL(conn, ttl) - if err != nil { - iteration := newIterationFromHandshake(ttl, err, nil, nil) - tr.addIterations(iteration) - ol.Stop(err) - return - } + // 3. Perform the handshake and extract the SO_ERROR value (if any) // Note: we switch to a uTLS Handshaker if the configured ClientID is non-zero thx := trace.NewTLSHandshakerStdlib(logger) @@ -151,18 +130,34 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t _, err = thx.Handshake(ctx, conn, genTLSConfig(sni)) ol.Stop(err) soErr := extractSoError(conn) + + hop, err := tc.ReadErrQueue() + if err != nil { + fmt.Println("errqueue error:", err) + } + + if hop != nil { + fmt.Printf("got hop: %+v\n", hop) + } + // 4. reset the TTL value to ensure that conn closes successfully // Note: Do not check for errors here _ = setConnTTL(conn, 64) - // 5. wait for possible ICMP event (non-blocking) - var hop *Hop - select { - case hop = <-done: - case <-time.After(100 * time.Millisecond): - } + // // 5. wait for ICMP messages + // var hop *Hop + // select { + // case hop = <-done: + // case <-time.After(100 * time.Millisecond): + // } iteration := newIterationFromHandshake(ttl, nil, soErr, trace.FirstTLSHandshakeOrNil()) + + // if hop != nil { + // fmt.Printf("TTL=%d ICMP=%s type=%d code=%d\n", + // ttl, hop.Addr, hop.Type, hop.Code, + // ) + // } tr.addIterations(iteration) } From d8cda8f201505a172047129b04cc1a3de2495604 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Thu, 23 Jul 2026 13:15:28 +0000 Subject: [PATCH 6/7] tcp traceroute working --- internal/experiment/tlsmiddlebox/dialer.go | 178 +----------------- .../tlsmiddlebox/tcp_traceroute_probe.go | 162 ++++++++++++++++ internal/experiment/tlsmiddlebox/trace.go | 47 ++++- internal/experiment/tlsmiddlebox/tracing.go | 97 ++++------ internal/model/archival.go | 30 +++ 5 files changed, 274 insertions(+), 240 deletions(-) create mode 100644 internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go diff --git a/internal/experiment/tlsmiddlebox/dialer.go b/internal/experiment/tlsmiddlebox/dialer.go index 91ed2077f2..5db437b727 100644 --- a/internal/experiment/tlsmiddlebox/dialer.go +++ b/internal/experiment/tlsmiddlebox/dialer.go @@ -6,54 +6,27 @@ package tlsmiddlebox import ( "context" - "fmt" "net" "time" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" - "golang.org/x/sys/unix" ) const timeout time.Duration = 15 * time.Second -// Define the Linux IPPROTO_IP socket option enabling error queueing -const IP_RECVERR = 12 - -// Define the Linux recvmsg flag for reading the error queue -const MSG_ERRQUEUE = 2 - -// Define the Linux SOL_SOCKET socket option enabling nanosecond timestamps -const SO_TIMESTAMPNS = 35 - -// Define the Linux socket control message type for nanosecond timestamps -const SCM_TIMESTAMPNS = 35 - -func NewDialerTTLWrapper(ttl int) model.Dialer { +func NewDialerTTLWrapper() model.Dialer { return &dialerTTLWrapper{ Dialer: &net.Dialer{Timeout: timeout}, - TTL: ttl, } } // dialerTTLWrapper wraps errors and also returns a TTL wrapped conn type dialerTTLWrapper struct { Dialer model.SimpleDialer - TTL int -} - -// ttlConn wraps the TCP connection -type ttlConn struct { - *net.TCPConn } -type Hop struct { - Addr net.IP - Type uint8 - Code uint8 - Timestamp time.Time - TTL uint8 -} +var _ model.Dialer = &dialerTTLWrapper{} // DialContext implements model.Dialer.DialContext func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, address string) (net.Conn, error) { @@ -61,26 +34,8 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr if err != nil { return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) } - - tcp := conn.(*net.TCPConn) - - raw, err := tcp.SyscallConn() - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } - - _ = raw.Control(func(fd uintptr) { - // Set the TTL - _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TTL, d.TTL) - // Set the IP_RECVERR socket option to enable ICMP errors to be stored in the socket error queue - // Such errors will be subsequently read with MSG_ERRQUEUE - _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVERR, 1) - // Set the SO_TIMESTAMPNS socket option to enable nanosecond timestamps - _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_TIMESTAMPNS, 1) - }) - - return &ttlConn{ - TCPConn: tcp, + return &dialerTTLWrapperConn{ + Conn: conn, }, nil } @@ -88,128 +43,3 @@ func (d *dialerTTLWrapper) DialContext(ctx context.Context, network string, addr func (d *dialerTTLWrapper) CloseIdleConnections() { // nothing to do here } - -// ReadErrQueue() uses MSG_ERRQUEUE to read ICMP messages -func (c *ttlConn) ReadErrQueue() (*Hop, error) { - buf := make([]byte, 256) - oob := make([]byte, 4096) - - var hop *Hop - - raw, err := c.SyscallConn() - if err != nil { - return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - } - - fmt.Printf("SO_ERROR=%v\n", extractSoError(c)) - - err = raw.Read(func(fd uintptr) bool { - n, oobn, _, _, err := unix.Recvmsg( - int(fd), - buf, - oob, - unix.MSG_ERRQUEUE, - ) - - fmt.Printf("recvmsg n=%d oobn=%d err=%v\n", n, oobn, err) - - if err == unix.EAGAIN { //nothing to be read from MSG_ERRQUEUE - return false - } - if err != nil { - fmt.Printf("recvmsg error: %v\n", err) - return false - } - if oobn == 0 { - return false - } - - hop = &Hop{} - return false - }) - - return hop, nil - - // buf := make([]byte, 256) - // oob := make([]byte, 512) - - // // Read from MSG_ERRQUEUE - // _, oobn, _, _, err := unix.Recvmsg( - // c.fd, - // buf, - // oob, - // MSG_ERRQUEUE, - // ) - - // if err != nil { - // return nil, netxlite.NewErrWrapper(netxlite.ClassifyGenericError, netxlite.ConnectOperation, err) - // } - - // var ( - // hop Hop - // haveICMP bool - // haveTimestamp bool - // ts time.Time - // ) - - // cms, err := unix.ParseSocketControlMessage((oob[:oobn])) - // if err != nil { - // return nil, err - // } - - // for i, cm := range cms { - // fmt.Printf("=== CMS[%d] ===\n", i) - // fmt.Printf("Level: %d Type: %d Len: %d\n", - // cm.Header.Level, - // cm.Header.Type, - // len(cm.Data), - // ) - - // fmt.Printf("RAW DATA: %x\n", cm.Data) - // } - - // for _, cm := range cms { - - // // Check for timestamp error - // if cm.Header.Level == unix.SOL_SOCKET && cm.Header.Type == SCM_TIMESTAMPNS { - // sec := int64(binary.LittleEndian.Uint64(cm.Data[0:8])) - // nsec := int64(binary.LittleEndian.Uint64(cm.Data[8:16])) - // ts = time.Unix(sec, nsec) - // haveTimestamp = true - // continue - // } - - // // Check for ICMP error - // if cm.Header.Level == unix.IPPROTO_IP && cm.Header.Type == IP_RECVERR { - // fmt.Printf("\n--- IP_RECVERR ---\n") - // fmt.Printf("len(cm.Data): %d\n", len(cm.Data)) - // fmt.Printf("hex dump: %x\n", cm.Data) - - // ee := cm.Data - - // if len(ee) < 16 { - // continue - // } - - // hop.Type = ee[4] - // hop.Code = ee[5] - - // sa := ee[16:] - // if len(sa) >= 8 { - // hop.Addr = net.IPv4(sa[4], sa[5], sa[6], sa[7]) - // } - - // haveICMP = true - // } - // } - - // if !haveICMP { - // return nil, nil - // } - - // if haveTimestamp { - // hop.Timestamp = ts - // } - - // return &hop, nil -} diff --git a/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go b/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go new file mode 100644 index 0000000000..16a3ab0c31 --- /dev/null +++ b/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go @@ -0,0 +1,162 @@ +package tlsmiddlebox + +import ( + "encoding/binary" + "fmt" + "net" + "strconv" + "sync" + + "github.com/ooni/probe-cli/v3/internal/logx" + "github.com/ooni/probe-cli/v3/internal/model" + "golang.org/x/sys/unix" +) + +func probeTCP(address string, ttl int, timeoutMS int, wg *sync.WaitGroup, logger model.Logger, index int64) (*ICMPIteration, error) { + defer wg.Done() + host, portString, err := net.SplitHostPort(address) + + port, err := strconv.Atoi(portString) + + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) + + if err != nil { + return nil, err + } + + defer unix.Close(fd) + + if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVERR, 1); err != nil { + return nil, err + } + + if err := unix.SetNonblock(fd, true); err != nil { + return nil, err + } + + if err := unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_TTL, ttl); err != nil { + return nil, err + } + + ip := net.ParseIP(host).To4() + if ip == nil { + return nil, fmt.Errorf("invalid IPv4 address") + } + + var addr [4]byte + copy(addr[:], ip) + + sa := &unix.SockaddrInet4{ + Port: port, + Addr: addr, + } + + err = unix.Connect(fd, sa) + if err != nil && err != unix.EINPROGRESS { + return nil, err + } + + pfds := []unix.PollFd{ + { + Fd: int32(fd), + Events: unix.POLLOUT | unix.POLLERR, + }, + } + + n, err := unix.Poll(pfds, timeoutMS) + if err != nil { + return nil, err + } + + if n == 0 { + ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s TIMEOUT", index, ttl, address) + ol.Stop(nil) + return nil, nil + } + + //fmt.Printf("revents = %#x\n", pfds[0].Revents) + + if pfds[0].Revents&unix.POLLERR != 0 { + + buf := make([]byte, 64) + oob := make([]byte, 512) + + _, oobn, _, _, err := unix.Recvmsg(fd, buf, oob, unix.MSG_ERRQUEUE) + //fmt.Printf("Recvmsg: n=%d oob=%d flags=%#x err=%v\n", n, oobn, flags, err) + + if err == nil { + cms, err := unix.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return nil, err + } + + //fmt.Printf("received %d control messages\n", len(cms)) + + for _, cm := range cms { + if cm.Header.Level != unix.IPPROTO_IP || + cm.Header.Type != unix.IP_RECVERR { + continue + } + + data := cm.Data + + if len(data) < 16 { + continue + } + + // struct sock_extended_err + // ee_errno := binary.LittleEndian.Uint32(data[0:4]) + // ee_origin := data[4] + ee_type := data[5] + ee_code := data[6] + + // fmt.Printf("errno = %d\n", ee_errno) + // fmt.Printf("origin = %d\n", ee_origin) + // fmt.Printf("type = %d\n", ee_type) + // fmt.Printf("code = %d\n", ee_code) + + // offender sockaddr_in follows sock_extended_err + if len(data) >= 24 { + family := binary.LittleEndian.Uint16(data[16:18]) + + if family == unix.AF_INET { + ip := net.IP(data[20:24]) + ii := &ICMPIteration{ + TTL: ttl, + ICMPError: &model.ArchivalICMPErrorMessage{ + Timeout: "no", + SrcIP: ip.String(), + Type: int(ee_type), + Code: int(ee_code), + }, + } + ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s Router %s", index, ttl, address, ip.String()) + ol.Stop(err) + // fmt.Printf("offender = %s\n", ip.String()) + return ii, nil + } + } + } + } + + return nil, nil + + } + + if pfds[0].Revents&unix.POLLOUT != 0 { + soerr, err := unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_ERROR) + if err != nil { + return nil, err + } + + if soerr == 0 { + ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s CONNECTED", index, ttl, address) + ol.Stop(nil) + } else { + ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s SO_ERROR %v", index, ttl, address, unix.Errno(soerr)) + ol.Stop(nil) + } + } + + return nil, nil +} diff --git a/internal/experiment/tlsmiddlebox/trace.go b/internal/experiment/tlsmiddlebox/trace.go index a3fbae9b26..49aac7114c 100644 --- a/internal/experiment/tlsmiddlebox/trace.go +++ b/internal/experiment/tlsmiddlebox/trace.go @@ -10,9 +10,10 @@ import ( // CompleteTrace records the result of the network trace // using a control SNI and a target SNI type CompleteTrace struct { - Address string `json:"address"` - ControlTrace *IterativeTrace `json:"control_trace"` - TargetTrace *IterativeTrace `json:"target_trace"` + Address string `json:"address"` + TCPTraceroute *IterativeTraceroute `json:"tcp_traceroute"` + ControlTrace *IterativeTrace `json:"control_trace"` + TargetTrace *IterativeTrace `json:"target_trace"` } // Trace is an iterative trace for the corresponding servername and address @@ -29,6 +30,21 @@ type Iteration struct { Handshake *model.ArchivalTLSOrQUICHandshakeResult `json:"handshake"` } +// IterativeTraceroute is an iterative traceroute towards the address +type IterativeTraceroute struct { + SNI string `json:"server_name"` + Iterations []*ICMPIteration `json:"iterations"` + + mu sync.Mutex +} + +// ICMPIteration is a single ICMP error message associated with a TTL-limited +// probe when used for traceroute +type ICMPIteration struct { + TTL int `json:"ttl"` + ICMPError *model.ArchivalICMPErrorMessage `json:"icmp_error"` +} + // NewIterationFromHandshake returns a new iteration from a model.ArchivalTLSOrQUICHandshakeResult func newIterationFromHandshake(ttl int, err error, soErr error, handshake *model.ArchivalTLSOrQUICHandshakeResult) *Iteration { if err != nil { @@ -46,9 +62,34 @@ func newIterationFromHandshake(ttl int, err error, soErr error, handshake *model } } +// NewICMPIterationFromHandshake returns a new iteration from a model.ArchivalICMPErrorMessage +func newICMPIterationFromHandshake(ttl int, err error, soErr error, icmpError *model.ArchivalICMPErrorMessage) *ICMPIteration { + if err != nil { + return &ICMPIteration{ + TTL: ttl, + ICMPError: &model.ArchivalICMPErrorMessage{ + Timeout: "yes", + }, + } + } + + return &ICMPIteration{ + TTL: ttl, + ICMPError: icmpError, + } + +} + // addIterations adds iterations to the trace func (t *IterativeTrace) addIterations(ev ...*Iteration) { t.mu.Lock() t.Iterations = append(t.Iterations, ev...) t.mu.Unlock() } + +// addIterationsTraceroute adds iterations to the traceroute +func (t *IterativeTraceroute) addIterationsTraceroute(ev ...*ICMPIteration) { + t.mu.Lock() + t.Iterations = append(t.Iterations, ev...) + t.mu.Unlock() +} diff --git a/internal/experiment/tlsmiddlebox/tracing.go b/internal/experiment/tlsmiddlebox/tracing.go index f131c67ab1..ffc3a78112 100644 --- a/internal/experiment/tlsmiddlebox/tracing.go +++ b/internal/experiment/tlsmiddlebox/tracing.go @@ -8,7 +8,6 @@ import ( "context" "crypto/tls" "errors" - "fmt" "net" "sort" "sync" @@ -34,12 +33,37 @@ var ClientIDs = map[int]*utls.ClientHelloID{ // TLSTrace performs tracing using control and target SNI func (m *Measurer) TLSTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, targetSNI string, trace *CompleteTrace) { + // perform a TCP traceroute + trace.TCPTraceroute = m.runTraceroute(ctx, index, zeroTime, logger, address, targetSNI) // perform an iterative trace with the control SNI trace.ControlTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, m.config.snicontrol()) // perform an iterative trace with the target SNI trace.TargetTrace = m.startIterativeTrace(ctx, index, zeroTime, logger, address, targetSNI) } +func (m *Measurer) runTraceroute(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, + address string, sni string) (tr *IterativeTraceroute) { + tr = &IterativeTraceroute{ + SNI: sni, + Iterations: []*ICMPIteration{}, + } + maxTTL := m.config.maxttl() + ticker := time.NewTicker(m.config.delay()) + wg := new(sync.WaitGroup) + for i := int64(1); i <= maxTTL; i++ { + wg.Add(1) + icmpIteration, err := probeTCP(address, int(i), 3000, wg, logger, index) + if err != nil { + return + } + tr.addIterationsTraceroute(icmpIteration) + + <-ticker.C + } + wg.Wait() + return +} + // startIterativeTrace creates a Trace and calls iterativeTrace func (m *Measurer) startIterativeTrace(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, sni string) (tr *IterativeTrace) { @@ -70,13 +94,10 @@ func (m *Measurer) traceWithIncreasingTTLs(ctx context.Context, index int64, zer func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime time.Time, logger model.Logger, address string, sni string, ttl int, tr *IterativeTrace, wg *sync.WaitGroup) { defer wg.Done() - trace := measurexlite.NewTrace(index, zeroTime) - // 1. Connect to the target IP // TODO(DecFox, bassosimone): Do we need a trace for this TCP connect? - d := NewDialerTTLWrapper(ttl) - + d := NewDialerTTLWrapper() ol := logx.NewOperationLogger(logger, "Handshake Trace #%d TTL %d %s %s", index, ttl, address, sni) conn, err := d.DialContext(ctx, "tcp", address) if err != nil { @@ -86,40 +107,14 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t return } defer conn.Close() - - tc := conn.(*ttlConn) - - // // 2. start errqueue reader BEFORE handshake - // done := make(chan *Hop, 1) - - // go func() { - // // poll for a short window only - // deadline := time.NewTimer(800 * time.Millisecond) - // defer deadline.Stop() - - // for { - // select { - // case <-ctx.Done(): - // return - // case <-deadline.C: - // return - // default: - // } - - // hop, _ := tc.ReadErrQueue() - // if hop != nil { - // select { - // case done <- hop: - // default: - // } - // return - // } - - // time.Sleep(10 * time.Millisecond) - // } - // }() - - + // 2. Set the TTL to the passed value + err = setConnTTL(conn, ttl) + if err != nil { + iteration := newIterationFromHandshake(ttl, err, nil, nil) + tr.addIterations(iteration) + ol.Stop(err) + return + } // 3. Perform the handshake and extract the SO_ERROR value (if any) // Note: we switch to a uTLS Handshaker if the configured ClientID is non-zero thx := trace.NewTLSHandshakerStdlib(logger) @@ -130,34 +125,10 @@ func (m *Measurer) handshakeWithTTL(ctx context.Context, index int64, zeroTime t _, err = thx.Handshake(ctx, conn, genTLSConfig(sni)) ol.Stop(err) soErr := extractSoError(conn) - - hop, err := tc.ReadErrQueue() - if err != nil { - fmt.Println("errqueue error:", err) - } - - if hop != nil { - fmt.Printf("got hop: %+v\n", hop) - } - // 4. reset the TTL value to ensure that conn closes successfully // Note: Do not check for errors here _ = setConnTTL(conn, 64) - - // // 5. wait for ICMP messages - // var hop *Hop - // select { - // case hop = <-done: - // case <-time.After(100 * time.Millisecond): - // } - iteration := newIterationFromHandshake(ttl, nil, soErr, trace.FirstTLSHandshakeOrNil()) - - // if hop != nil { - // fmt.Printf("TTL=%d ICMP=%s type=%d code=%d\n", - // ttl, hop.Addr, hop.Type, hop.Code, - // ) - // } tr.addIterations(iteration) } diff --git a/internal/model/archival.go b/internal/model/archival.go index 1930dc53c5..cf5e77489c 100644 --- a/internal/model/archival.go +++ b/internal/model/archival.go @@ -171,6 +171,36 @@ func (value *ArchivalScrubbedMaybeBinaryString) UnmarshalJSON(rawData []byte) er return nil } +// +// ICMP +// + +// ArchivalICMPErrorMessage is the result of any ICMP error message. +// +// See https://github.com/ooni/spec/blob/master/data-formats/df-002-dnst.md. //update this, will also add timestamp info +type ArchivalICMPErrorMessage struct { + Timeout string `json:"timeout"` + SrcIP string `json:"source_ip"` + DstIP string `json:"destination_ip"` + Type int `json:"type"` + Code int `json:"code"` + Quote []ArchivalICMPQuotation `json:"quote"` + T0 float64 `json:"t0,omitempty"` + T float64 `json:"t"` +} + +// ArchivalICMPQuotation is the quotation of an ICMP error message. +type ArchivalICMPQuotation struct { + SrcIP string `json:"source_ip"` + DstIP string `json:"destination_ip"` + Protocol string `json:"protocol"` + SrcPort int `json:"source_port"` + DstPort int `json:"destination_port"` + TCPSequenceNumber int `json:"tcp_sequence_number"` + UDPLength int `json:"udp_length"` + UDPChecksum int `json:"udp_checksum"` +} + // // DNS lookup // From 990d25368517a9523d84734a949c132420395838 Mon Sep 17 00:00:00 2001 From: Sadia Nourin Date: Fri, 24 Jul 2026 09:06:10 +0000 Subject: [PATCH 7/7] timestamps done --- .../tlsmiddlebox/tcp_traceroute_probe.go | 129 +++++++++++------- 1 file changed, 83 insertions(+), 46 deletions(-) diff --git a/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go b/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go index 16a3ab0c31..ab7cb5f784 100644 --- a/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go +++ b/internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go @@ -6,6 +6,8 @@ import ( "net" "strconv" "sync" + "time" + "unsafe" "github.com/ooni/probe-cli/v3/internal/logx" "github.com/ooni/probe-cli/v3/internal/model" @@ -38,6 +40,14 @@ func probeTCP(address string, ttl int, timeoutMS int, wg *sync.WaitGroup, logger return nil, err } + timestampFlags := unix.SOF_TIMESTAMPING_TX_SOFTWARE | + unix.SOF_TIMESTAMPING_RX_SOFTWARE | + unix.SOF_TIMESTAMPING_SOFTWARE + + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMPING, timestampFlags); err != nil { + return nil, err + } + ip := net.ParseIP(host).To4() if ip == nil { return nil, fmt.Errorf("invalid IPv4 address") @@ -51,6 +61,11 @@ func probeTCP(address string, ttl int, timeoutMS int, wg *sync.WaitGroup, logger Addr: addr, } + var txTime *time.Time + + txTimeVal := time.Now() + txTime = &txTimeVal + err = unix.Connect(fd, sa) if err != nil && err != unix.EINPROGRESS { return nil, err @@ -71,76 +86,98 @@ func probeTCP(address string, ttl int, timeoutMS int, wg *sync.WaitGroup, logger if n == 0 { ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s TIMEOUT", index, ttl, address) ol.Stop(nil) - return nil, nil + return nil, nil //change this } - //fmt.Printf("revents = %#x\n", pfds[0].Revents) - if pfds[0].Revents&unix.POLLERR != 0 { buf := make([]byte, 64) oob := make([]byte, 512) - _, oobn, _, _, err := unix.Recvmsg(fd, buf, oob, unix.MSG_ERRQUEUE) - //fmt.Printf("Recvmsg: n=%d oob=%d flags=%#x err=%v\n", n, oobn, flags, err) + var data []byte + var rxTime *time.Time + var eeType byte + var eeCode byte + var ip net.IP + + for { + _, oobn, _, _, err := unix.Recvmsg(fd, buf, oob, unix.MSG_ERRQUEUE) + + if err == unix.EAGAIN { + break + } - if err == nil { - cms, err := unix.ParseSocketControlMessage(oob[:oobn]) if err != nil { return nil, err } - //fmt.Printf("received %d control messages\n", len(cms)) + cms, err := unix.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return nil, err + } for _, cm := range cms { - if cm.Header.Level != unix.IPPROTO_IP || - cm.Header.Type != unix.IP_RECVERR { - continue - } - data := cm.Data + switch { + case cm.Header.Level == unix.SOL_SOCKET && + cm.Header.Type == unix.SO_TIMESTAMPING: + var ts [3]unix.Timespec + if len(cm.Data) >= int(unsafe.Sizeof([3]unix.Timespec{})) { + ts = *(*[3]unix.Timespec)(unsafe.Pointer(&cm.Data[0])) + } - if len(data) < 16 { - continue - } + if ts[0].Sec != 0 { + t := time.Unix(ts[0].Sec, ts[0].Nsec) + rxTime = &t + } + + case cm.Header.Level == unix.IPPROTO_IP && + cm.Header.Type == unix.IP_RECVERR: + data = cm.Data + + if len(data) < 16 { + continue + } + + eeType = data[5] + eeCode = data[6] - // struct sock_extended_err - // ee_errno := binary.LittleEndian.Uint32(data[0:4]) - // ee_origin := data[4] - ee_type := data[5] - ee_code := data[6] - - // fmt.Printf("errno = %d\n", ee_errno) - // fmt.Printf("origin = %d\n", ee_origin) - // fmt.Printf("type = %d\n", ee_type) - // fmt.Printf("code = %d\n", ee_code) - - // offender sockaddr_in follows sock_extended_err - if len(data) >= 24 { - family := binary.LittleEndian.Uint16(data[16:18]) - - if family == unix.AF_INET { - ip := net.IP(data[20:24]) - ii := &ICMPIteration{ - TTL: ttl, - ICMPError: &model.ArchivalICMPErrorMessage{ - Timeout: "no", - SrcIP: ip.String(), - Type: int(ee_type), - Code: int(ee_code), - }, + if len(data) >= 24 { + family := binary.LittleEndian.Uint16(data[16:18]) + + if family == unix.AF_INET { + ip = net.IP(data[20:24]) } - ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s Router %s", index, ttl, address, ip.String()) - ol.Stop(err) - // fmt.Printf("offender = %s\n", ip.String()) - return ii, nil } + } } } - return nil, nil + var t0, t float64 + + if txTime != nil { + t0 = float64(txTime.UnixMilli()) + } + if rxTime != nil { + t = float64(rxTime.UnixMilli()) + } + + ii := &ICMPIteration{ + TTL: ttl, + ICMPError: &model.ArchivalICMPErrorMessage{ + Timeout: "no", + SrcIP: ip.String(), + Type: int(eeType), + Code: int(eeCode), + T0: t0, + T: t, + }, + } + ol := logx.NewOperationLogger(logger, "Traceroute #%d TTL %d %s Router %s", index, ttl, address, ip.String()) + ol.Stop(err) + return ii, nil } if pfds[0].Revents&unix.POLLOUT != 0 {