aboutsummaryrefslogtreecommitdiff
path: root/internal/qna/qna.go
blob: aa27d07d09e7dd67d2726d9d7db0690028408df9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package qna

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/url"
)

type Question struct {
	Domain string // domain name to visit via HTTPS
}

type Answer struct {
	Domain string // domain name of the visited HTTPS site
	HTTP   string // value set in the Onion-Location HTTP header (if any)
	HTML   string // value set in the Onion-Location HTML attribute (if any)

	ReqErr error // nil if HTTP GET request could be constructed
	DoErr  error // nil if HTTP GET request could be executed
}

func (a Answer) String() string {
	if a.ReqErr != nil {
		return fmt.Sprintf("%s: %v", a.ReqErr)
	}
	if a.DoErr != nil {
		return fmt.Sprintf("%s: %v", a.DoErr)
	}
	if a.HTTP == "" && a.HTML == "" {
		return fmt.Sprintf("%s: connected but found no Onion-Location")
	}
	return fmt.Sprintf("%s header=%s attribute=%s", a.Domain, a.HTTP, a.HTML)
}

type Progress struct {
	NumReqErr int // error before sending requests
	NumDoErr  int // error while sending request
	NumOK     int // got response
	NumOnion  int // found onion in response

	// More details about deadline errors while sending request
	NumDeadline int

	// More details about DNS errors while sending request
	NumDNSNotFound int
	NumDNSTimeout  int
	NumDNSOther    int
}

func (p Progress) String() string {
	str := fmt.Sprintf("  Processed: %d\n", p.NumAnswer())
	str += fmt.Sprintf("    Success: %d (Onion-Location:%d)\n", p.NumOK, p.NumOnion)
	str += fmt.Sprintf("    Failure: %d (Req:%d Do:%d)\n", p.NumError(), p.NumReqErr, p.NumDoErr)
	str += fmt.Sprintf("        CTX: %d (Deadline exceeded)\n", p.NumDeadline)
	str += fmt.Sprintf("        DNS: %d (NotFound:%d Timeout:%d Other:%d)",
		p.NumDNSErr(), p.NumDNSNotFound, p.NumDNSTimeout, p.NumDNSOther)
	return str
}

func (p *Progress) NumAnswer() int {
	return p.NumReqErr + p.NumDoErr + p.NumOK
}

func (p *Progress) NumError() int {
	return p.NumReqErr + p.NumDoErr
}

func (p *Progress) NumDNSErr() int {
	return p.NumDNSNotFound + p.NumDNSTimeout + p.NumDNSOther
}

func (p *Progress) AddAnswer(a Answer) {
	if a.ReqErr != nil {
		p.NumReqErr += 1
		return
	}
	if a.DoErr != nil {
		p.NumDoErr += 1
		if isDeadlineError(a.DoErr) {
			p.NumDeadline += 1
		}
		if err := dnsError(a.DoErr); err != nil {
			if err.IsTimeout {
				p.NumDNSTimeout += 1
			} else if err.IsNotFound {
				p.NumDNSNotFound += 1
			} else {
				p.NumDNSOther += 1
			}
		}
		return
	}

	p.NumOK += 1
	if a.HTTP != "" || a.HTML != "" {
		p.NumOnion += 1
	}
}

func dnsError(err error) *net.DNSError {
	if err == nil {
		return nil
	}

	urlErr, ok := err.(*url.Error)
	if !ok {
		return nil
	}

	err = urlErr.Err
	opErr, ok := err.(*net.OpError)
	if !ok {
		return nil
	}

	err = opErr.Err
	dnsErr, ok := err.(*net.DNSError)
	if !ok {
		return nil
	}

	return dnsErr
}

func isDeadlineError(err error) bool {
	urlErr, ok := err.(*url.Error)
	return ok && errors.Is(urlErr.Err, context.DeadlineExceeded)
}