Utilisation de la bibliothèque miekg/dns pour la gestion DNS en Go

La bibliothèque github.com/miekg/dns est l'outil de référence dans l'écosystème Go pour manipuler le protocole DNS. Elle permet de construire des clients performants, de développer des serveurs personnalisés et de manipuler finement les messages DNS.

Caractéristiques principales

  • Support complet des types d'enregistrements (A, AAAA, MX, NS, TXT, SRV, etc.).
  • Construction et analyse (parsing) de messages DNS au niveau binaire.
  • Capacités de création de serveurs UDP et TCP.
  • Support des extensions comme EDNS0 et DNSSEC.

Installation

go get github.com/miekg/dns

Implémentation d'un Client DNS

Requête de base pour différents enregistrements

L'exemple suivant montre comment interroger un serveur DNS pour obtenir différents types d'enregistrements (A, AAAA, TXT) de manière modulaire.

package main

import (
	"fmt"
	"github.com/miekg/dns"
)

func effectuerRequete(nomDomaine string, typeDNS uint16) {
	configServeur := "8.8.8.8:53"
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(nomDomaine), typeDNS)
	
	co := new(dns.Client)
	reponse, _, err := co.Exchange(m, configServeur)
	
	if err != nil {
		fmt.Printf("Erreur lors de la requête : %v\n", err)
		return
	}

	if reponse.Rcode != dns.RcodeSuccess {
		fmt.Printf("Échec de la requête avec le code : %d\n", reponse.Rcode)
		return
	}

	for _, rr := range reponse.Answer {
		fmt.Println(rr.String())
	}
}

func main() {
	cible := "google.com"
	fmt.Println("--- Enregistrements A ---")
	effectuerRequete(cible, dns.TypeA)
	fmt.Println("--- Enregistrements TXT ---")
	effectuerRequete(cible, dns.TypeTXT)
}

Configuration avancée : Timeout et Récursion

Il est souvent nécessaire de définir des délais d'attente ou d'activer explicitement la récursion.

package main

import (
	"time"
	"github.com/miekg/dns"
)

func main() {
	c := &dns.Client{
		Timeout: 3 * time.Second,
	}
	
	msg := new(dns.Msg)
	msg.RecursionDesired = true
	msg.SetQuestion(dns.Fqdn("example.com"), dns.TypeA)
	
	// Utilisation de Exchange pour envoyer la requête
	_, _, _ = c.Exchange(msg, "1.1.1.1:53")
}

Utilisation de EDNS0 Client Subnet (ECS)

Le mécanisme ECS permet de transmettre une partie de l'adresse IP du client final au serveur DNS récursif afin d'optimiser le routage géographique (très utilisé par les CDN).

package main

import (
	"net"
	"github.com/miekg/dns"
)

func requeteAvecSubnet(domaine string, subnetIP string) {
	req := new(dns.Msg)
	req.SetQuestion(dns.Fqdn(domaine), dns.TypeA)

	opt := new(dns.OPT)
	opt.Hdr.Name = "."
	opt.Hdr.Rrtype = dns.TypeOPT
	
	e := &dns.EDNS0_SUBNET{
		Code:          dns.EDNS0SUBNET,
		Address:       net.ParseIP(subnetIP).To4(),
		SourceNetmask: 24,
		Family:        1, // IPv4
	}
	
	opt.Option = append(opt.Option, e)
	req.Extra = append(req.Extra, opt)

	c := new(dns.Client)
	c.Exchange(req, "8.8.8.8:53")
}

Création d'un Serveur DNS

La bibliothèque permet de monter un serveur DNS capable de répondre à des requêtes spécifiques.

package main

import (
	"log"
	"net"
	"github.com/miekg/dns"
)

func gestionnaireDNS(w dns.ResponseWriter, r *dns.Msg) {
	reponse := new(dns.Msg)
	reponse.SetReply(r)
	reponse.Authoritative = true

	for _, q := range r.Question {
		if q.Qtype == dns.TypeA {
			rr := &dns.A{
				Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 300},
				A:   net.ParseIP("127.0.0.1"),
			}
			reponse.Answer = append(reponse.Answer, rr)
		}
	}
	w.WriteMsg(reponse)
}

func main() {
	dns.HandleFunc("mon-domaine.local.", gestionnaireDNS)
	serveur := &dns.Server{Addr: ":5353", Net: "udp"}
	log.Println("Serveur DNS démarré sur :5353")
	if err := serveur.ListenAndServe(); err != nil {
		log.Fatalf("Erreur serveur : %v", err)
	}
}

Interception et Modification de Flux

On peut agir comme un proxy DNS pour modifier les réponses à la volée, par exemple pour rediriger un domaine spécifique vers une adresse IP différente.

package main

import (
	"net"
	"github.com/miekg/dns"
)

func intercepteur(w dns.ResponseWriter, req *dns.Msg) {
	client := new(dns.Client)
	// On transfère la requête vers un résolveur public
	resp, _, err := client.Exchange(req, "8.8.8.8:53")
	if err != nil {
		dns.HandleFailed(w, req)
		return
	}

	// Altération de la réponse si elle concerne un domaine précis
	for _, rr := range resp.Answer {
		if a, ok := rr.(*dns.A); ok {
			if a.Hdr.Name == "cible.com." {
				a.A = net.ParseIP("10.0.0.1")
			}
		}
	}

	w.WriteMsg(resp)
}

func main() {
	dns.HandleFunc(".", intercepteur)
	srv := &dns.Server{Addr: ":53", Net: "udp"}
	srv.ListenAndServe()
}

Étiquettes: golang DNS networking miekg-dns backend

Publié le 16 juillet à 07h17