ooni-probe-cli/internal/engine/cmd/jafar/resolver/resolver.go
Simone Basso d57c78bc71
chore: merge probe-engine into probe-cli (#201)
This is how I did it:

1. `git clone https://github.com/ooni/probe-engine internal/engine`

2. ```
(cd internal/engine && git describe --tags)
v0.23.0
```

3. `nvim go.mod` (merging `go.mod` with `internal/engine/go.mod`

4. `rm -rf internal/.git internal/engine/go.{mod,sum}`

5. `git add internal/engine`

6. `find . -type f -name \*.go -exec sed -i 's@/ooni/probe-engine@/ooni/probe-cli/v3/internal/engine@g' {} \;`

7. `go build ./...` (passes)

8. `go test -race ./...` (temporary failure on RiseupVPN)

9. `go mod tidy`

10. this commit message

Once this piece of work is done, we can build a new version of `ooniprobe` that
is using `internal/engine` directly. We need to do more work to ensure all the
other functionality in `probe-engine` (e.g. making mobile packages) are still WAI.

Part of https://github.com/ooni/probe/issues/1335
2021-02-02 12:05:47 +01:00

131 lines
3.2 KiB
Go

// Package resolver contains a censoring DNS resolver. Most queries are
// answered without censorship, but selected queries could either be
// discarded or replied to with a bogon or NXDOMAIN answer.
package resolver
import (
"context"
"net"
"strings"
"github.com/miekg/dns"
"github.com/ooni/probe-cli/v3/internal/engine/netx"
)
// CensoringResolver is a censoring resolver.
type CensoringResolver struct {
blocked []string
hijacked []string
ignored []string
lookupHost func(ctx context.Context, host string) ([]string, error)
}
// NewCensoringResolver creates a new CensoringResolver instance using
// the specified list of keywords to censor. blocked is the list of
// keywords that trigger NXDOMAIN if they appear in a query. hijacked
// is similar but redirects to 127.0.0.1, where the transparent HTTP
// and TLS proxies will pick them up. dnsNetwork and dnsAddress are the
// settings to configure the upstream, non censored DNS.
func NewCensoringResolver(
blocked, hijacked, ignored []string, uncensored netx.Resolver,
) *CensoringResolver {
return &CensoringResolver{
blocked: blocked,
hijacked: hijacked,
ignored: ignored,
lookupHost: uncensored.LookupHost,
}
}
func (r *CensoringResolver) roundtrip(rw dns.ResponseWriter, req *dns.Msg) {
name := req.Question[0].Name
addrs, err := r.lookupHost(context.Background(), name)
var ips []net.IP
if err == nil {
for _, addr := range addrs {
if ip := net.ParseIP(addr); ip != nil {
ips = append(ips, ip)
}
}
}
r.reply(rw, req, ips)
}
func (r *CensoringResolver) reply(
rw dns.ResponseWriter, req *dns.Msg, ips []net.IP,
) {
m := new(dns.Msg)
m.Compress = true
m.MsgHdr.RecursionAvailable = true
m.SetReply(req)
for _, ip := range ips {
ipv6 := strings.Contains(ip.String(), ":")
if !ipv6 && req.Question[0].Qtype == dns.TypeA {
m.Answer = append(m.Answer, &dns.A{
Hdr: dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 0,
},
A: ip,
})
}
}
if m.Answer == nil {
m.SetRcode(req, dns.RcodeNameError)
}
rw.WriteMsg(m)
}
func (r *CensoringResolver) failure(rw dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.Compress = true
m.MsgHdr.RecursionAvailable = true
m.SetRcode(req, dns.RcodeServerFailure)
rw.WriteMsg(m)
}
// ServeDNS serves a DNS request
func (r *CensoringResolver) ServeDNS(rw dns.ResponseWriter, req *dns.Msg) {
if len(req.Question) < 1 {
r.failure(rw, req)
return
}
name := req.Question[0].Name
for _, pattern := range r.blocked {
if strings.Contains(name, pattern) {
r.reply(rw, req, nil)
return
}
}
for _, pattern := range r.hijacked {
if strings.Contains(name, pattern) {
r.reply(rw, req, []net.IP{net.IPv4(127, 0, 0, 1)})
return
}
}
for _, pattern := range r.ignored {
if strings.Contains(name, pattern) {
return
}
}
r.roundtrip(rw, req)
}
// Start starts the DNS resolver
func (r *CensoringResolver) Start(address string) (*dns.Server, error) {
packetconn, err := net.ListenPacket("udp", address)
if err != nil {
return nil, err
}
server := &dns.Server{
Addr: address,
Handler: r,
Net: "udp",
PacketConn: packetconn,
}
go server.ActivateAndServe()
return server, nil
}