Skip to content

Commit

Permalink
Create find-strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
fortuna committed Oct 17, 2023
1 parent c3fd000 commit d38dbcd
Showing 1 changed file with 172 additions and 0 deletions.
172 changes: 172 additions & 0 deletions x/examples/find-strategy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2023 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strings"
"time"

"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/x/config"
"github.com/miekg/dns"
)

var debugLog log.Logger = *log.New(io.Discard, "", 0)

type dialFunc func(context.Context, string) (net.Conn, error)

func makeDialFunc[C net.Conn](d func(context.Context, string) (C, error)) dialFunc {
return func(ctx context.Context, addr string) (net.Conn, error) {
return d(ctx, addr)
}
}

type dnsClient struct {
dial dialFunc
client dns.Client
}

func (c *dnsClient) ExchangeContext(ctx context.Context, m *dns.Msg, addr string) (r *dns.Msg, rtt time.Duration, err error) {
conn, err := c.dial(ctx, addr)
if err != nil {
return nil, 0, err
}
// TODO: don't close for TCP keepopen
defer conn.Close()
dnsConn := &dns.Conn{Conn: conn}
return c.client.ExchangeWithConnContext(ctx, m, dnsConn)
}

func makeTCPClient(sd transport.StreamDialer) *dnsClient {
return &dnsClient{
dial: makeDialFunc(sd.Dial),
client: dns.Client{Net: "tcp"},
}
}

func makeUDPClient(pd transport.PacketDialer) *dnsClient {
return &dnsClient{
dial: makeDialFunc(pd.Dial),
client: dns.Client{Net: ""},
}
}

func main() {
verboseFlag := flag.Bool("v", false, "Enable debug output")
// typeFlag := flag.String("type", "A", "The type of the query (A, AAAA, CNAME, NS or TXT).")
// resolverFlag := flag.String("resolver", "", "The address of the recursive DNS resolver to use in host:port format. If the port is missing, it's assumed to be 53")
transportFlag := flag.String("transport", "", "The transport for the connection to the recursive DNS resolver")
// tcpFlag := flag.Bool("tcp", false, "Force TCP when querying the DNS resolver")
domainFlag := flag.String("domain", "", "The test domain to find strategies")

flag.Parse()
if *verboseFlag {
debugLog = *log.New(os.Stderr, "[DEBUG] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
}

clients := []*dnsClient{}
packetDialer, err := config.NewPacketDialer(*transportFlag)
if err != nil {
log.Fatalf("Could not create packet dialer: %v", err)
}
clients = append(clients, makeUDPClient(packetDialer))
streamDialer, err := config.NewStreamDialer(*transportFlag)
if err != nil {
log.Fatalf("Could not create stream dialer: %v", err)
}
clients = append(clients, makeTCPClient(streamDialer))

nsList, err := net.LookupNS(".")
if err != nil {
log.Fatalf("Could not get list of root nameservers: %v", err)
}
if len(nsList) == 0 {
log.Fatalf("Empty list of root nameservers")
}
rootNS := nsList[0].Host
debugLog.Printf("Root nameserver is %v", rootNS)

allNSIPs, err := net.LookupIP(rootNS)
if err != nil {
log.Fatalf("Failed to resolve root nameserver: %v", err)
}
ips := []net.IP{}
for _, ip := range allNSIPs {
if ip.To4() != nil {
ips = append(ips, ip)
break
}
}
for _, ip := range allNSIPs {
if ip.To16() != nil {
ips = append(ips, ip)
break
}
}

testDomain := dns.Fqdn(*domainFlag)

for _, rootNSIP := range ips {
resolvedNS := net.JoinHostPort(rootNSIP.String(), "53")
for _, client := range clients {
switch client.client.Net {
case "tcp":
fmt.Print("proto=tcp")
default:
fmt.Print("proto=udp")
}
if rootNSIP.To4() != nil {
fmt.Print("4")
} else {
fmt.Print("6")
}

var request dns.Msg
requestDomain := strings.ToUpper(testDomain)
request.SetQuestion(requestDomain, dns.TypeA)
response, _, err := client.ExchangeContext(context.Background(), &request, resolvedNS)
if err != nil {
fmt.Printf("; status=error: %v\n", err)
continue
}
debugLog.Printf(";Response: %v", response)
if len(response.Answer) > 0 {
fmt.Printf("; status=unexpected answer: %v\n", response.Answer)
// TODO: use as blocking fingerprint.
continue
}
// if response.Answer[0].Header().Name != requestDomain {
// fmt.Print("; status=case mismatch\n")
// continue
// }
fmt.Print("; status=ok\n")
}
}

// TODO:
// Go over list of public resolvers, restricted to working categories.
// Prefer those that preserve case in the answer, and return public IPs.
// Also match against blocking fingerprint.
// Perhaps make a score function?
// If no working category, try alternative ports.
// Define DNS strategy object. Or perhaps Client with debug info.
}

0 comments on commit d38dbcd

Please sign in to comment.