commit 43d20527ba369b096c36f20155cd25a70ad2eb76 Author: ipc Date: Sun May 28 17:52:52 2023 +0200 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..957940c --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# nlup + +nlup is a small domain name lookup utility that interacts +directly with the nameserver. diff --git a/dns.go b/dns.go new file mode 100644 index 0000000..e8e6e55 --- /dev/null +++ b/dns.go @@ -0,0 +1,143 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "net" + "os" + "strings" + "time" +) + +const ( + ARecord = 0x0001 + INClass = 0x0001 +) + +const DNSPort = 53 +const Timeout = 2 + +type dnsConn struct { + net.Conn +} + +type header struct { + Id, Flags uint16 + Qdcount uint16 + Ancount uint16 + Nscount uint16 + Arcount uint16 +} + +type answer struct { + Ntype byte + Ptr uint16 + Qtype uint16 + Class uint16 + Ttl uint32 + Addrlen uint16 + Addr uint32 +} + +func findNsAddr() (string, error) { + conf := []string{ + "/etc/resolv.conf", + } + + for _, i := range conf { + f, err := os.Open(i) + + if err != nil { + return "", err + } + + defer f.Close() + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + s := scanner.Text() + + if s[0] == '#' { + continue + } + + args := strings.Split(s, " ") + if args[0] == "nameserver" && len(args) > 1 { + return args[1], nil + } + } + } + + return "", errors.New("nameserver not found") +} + +func read(buf *bytes.Buffer, data any) error { + return binary.Read(buf, binary.BigEndian, data) +} + +func write(buf *bytes.Buffer, data any) error { + return binary.Write(buf, binary.BigEndian, data) +} + +func writeName(buf *bytes.Buffer, name string) (n int) { + for _, i := range strings.Split(name, ".") { + buf.WriteByte(byte(len(i))) + n++ + buf.WriteString(i) + n += len(i) + } + + buf.WriteByte(0) + return +} + +func dial(server string) (dnsConn, error) { + c, err := net.Dial("udp", fmt.Sprintf("%s:%d", server, DNSPort)) + + if err == nil { + c.SetDeadline(time.Now().Add(Timeout * time.Second)) + } + + return dnsConn{c}, err +} + +func (c dnsConn) sendQuery(name string) (answer, error) { + buf := &bytes.Buffer{} + ah := &header{} + a := &answer{} + + h := header{ + 0x4c3f, + 1 << 8, // recursion + 0x0001, // single question + 0x0000, + 0x0000, + 0x0000, + } + + write(buf, h) + nc := writeName(buf, name) + write(buf, []uint16{ARecord, INClass}) + if _, err := c.Write(buf.Bytes()); err != nil { + return *a, err + } + + b := make([]byte, 64) + if _, err := c.Read(b); err != nil { + return *a, err + } + + abuf := bytes.NewBuffer(b) + read(abuf, ah) + abuf.Next(nc + 4) + read(abuf, a) + + if a.Addrlen != 4 { + return *a, errors.New("invalid name") + } + + return *a, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4e11a33 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/rtcall/nlup + +go 1.20 diff --git a/main.go b/main.go new file mode 100644 index 0000000..f313f77 --- /dev/null +++ b/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "flag" + "fmt" + "os" +) + +func main() { + ns := flag.String("n", "", "nameserver to use") + v := flag.Bool("v", false, "verbose output") + flag.Parse() + + if len(flag.Args()) == 0 { + fmt.Printf("usage: %s [-n nameserver] [-v] name\n", os.Args[0]) + os.Exit(1) + } + + if *ns == "" { + if s, err := findNsAddr(); err != nil { + fmt.Printf("error: %s\n", err) + os.Exit(1) + } else { + *ns = s + } + } + + name := flag.Arg(0) + c, err := dial(*ns) + if err != nil { + fmt.Printf("error: %s\n", err) + os.Exit(1) + } + + ans, err := c.sendQuery(name) + if err != nil { + fmt.Printf("error: %s\n", err) + os.Exit(1) + } + + if *v { + fmt.Printf("nameserver: %s\ntype: %d\nttl: %d\n", + *ns, ans.Ntype, ans.Ttl) + } + + fmt.Printf("%s: %d.%d.%d.%d\n", name, + (ans.Addr>>24)&0xff, (ans.Addr>>16)&0xff, (ans.Addr>>8)&0xff, + ans.Addr&0xff) +}