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()
}