Skip to content
Open
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
199 changes: 199 additions & 0 deletions internal/experiment/tlsmiddlebox/tcp_traceroute_probe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package tlsmiddlebox

import (
"encoding/binary"
"fmt"
"net"
"strconv"
"sync"
"time"
"unsafe"

"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
}

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")
}

var addr [4]byte
copy(addr[:], ip)

sa := &unix.SockaddrInet4{
Port: port,
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
}

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 //change this
}

if pfds[0].Revents&unix.POLLERR != 0 {

buf := make([]byte, 64)
oob := make([]byte, 512)

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 {
return nil, err
}

cms, err := unix.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, err
}

for _, cm := range cms {

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 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]

if len(data) >= 24 {
family := binary.LittleEndian.Uint16(data[16:18])

if family == unix.AF_INET {
ip = net.IP(data[20:24])
}
}

}
}
}

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 {
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
}
47 changes: 44 additions & 3 deletions internal/experiment/tlsmiddlebox/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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()
}
25 changes: 25 additions & 0 deletions internal/experiment/tlsmiddlebox/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,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) {
Expand Down
30 changes: 30 additions & 0 deletions internal/model/archival.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
Loading