CoreDNS 是一個鏈接外掛程式的 DNS 伺服器。外掛程式定義為一個方法:ServeDNS()
,它會取得請求,並回應客戶端或將請求傳遞給下一個外掛程式。如果沒有任何外掛程式處理請求,則會傳回 SERVFAIL 的預設回應。
這篇部落格文章詳細說明如何將外掛程式新增至 CoreDNS。我們以 *whoami* 外掛程式為例,它是一個 CoreDNS 外掛程式,如果沒有指定 Corefile,則預設會載入它。
請注意,這裡的所有程式碼範例都是使用 Go 語言,因為 CoreDNS 是用 Go 語言撰寫的。
第一個問題應該是:「我的外掛程式應該做什麼?」。以 *whoami* 外掛程式為例,其目的是回傳客戶端的 IP (IPv4 或 IPv6)、使用的傳輸協定 (「tcp」或「udp」) 和請求的連接埠號碼。
下一個問題是:「這個新外掛程式的名稱是什麼?」。請嘗試為此找到一個簡短、描述性的名稱。在這種情況下,我們已經有一個 (好) 名稱:*whoami*。
外掛程式
外掛程式由多個部分組成
- 外掛程式的註冊
- 設定函數,解析 Corefile 中的 *whoami* 外掛程式和可能的參數
ServeDNS()
和Name()
方法
在定義好各部分後,我們可以
- 掛鉤它
- 使用它
1. 註冊
通常,外掛程式會有一個名為 setup.go
的檔案來處理註冊。在其中,init
函數應如下所示
func init() { plugin.Register("whoami", setup) }
setup
是設定函數的名稱,負責解析 Corefile。它的工作是傳回一個實作 plugin.Handler
介面的類型。
因此,每當 Corefile 解析器看到「whoami」時,就會呼叫 whoami.setup
。
2. 設定函數
由於 *whoami* 外掛程式不允許任何選項,因此設定函數相對簡單
func setupWhoami(c *caddy.Controller) error {
c.Next() // 'whoami'
if c.NextArg() {
return plugin.Error("whoami", c.ArgErr())
}
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return Whoami{Next: next} // Set the Next field, so the plugin chaining works.
})
return nil
}
我們使用 *caddy.Controller
從 Corefile 接收權杖並對其執行動作。在這裡,我們只檢查在權杖 whoami
之後是否沒有指定任何內容。如果您需要執行更多操作,則可以使用 c.Val()
、c.Args()
和 friends。
完整的 *whoami* setup.go
在這裡。
請注意,您也應該測試解析,請參閱 setup_test.go。
3. ServeDNS() 和 Name()
我們先從簡單的 Name()
方法開始,它用於讓其他外掛程式檢查是否已載入特定的外掛程式。該方法只會傳回字串 whoami
。
// Name implements the Handler interface.
func (wh Whoami) Name() string { return "whoami" }
接下來,最重要的部分是 ServeDNS
方法。我們將逐行查看此方法。
// ServeDNS implements the plugin.Handler interface.
func (wh Whoami) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
state := request.Request{W: w, Req: r}
如您所見,該函數會取得幾個參數,其中 w
是客戶端:寫入 w
會傳回對客戶端的回應。如下所示,可以從 dns.ResponseWriter 擷取客戶端連線的所有相關屬性。r
是傳入的查詢。ServeDNS
方法會傳回一個整數和/或一個錯誤。整數可以取的值是 DNS RCODE,例如 dns.RcodeServerFailure、dns.RcodeNotImplemented、dns.RcodeSuccess 等。成功的傳回值表示外掛程式已寫入客戶端。
request.Request
是一個協助程式結構,它會抽象化並快取一些客戶端屬性,例如 EDNS0 記錄和 DNSSEC OK 位元。
接下來,我們設定回覆訊息
a := &dns.Msg{}
a.SetReply(r)
a.Authoritative = true
我們建立一個新訊息,並將相關位元從傳入的回覆複製到我們計劃傳回的回覆。我們修改一些訊息位元,並將其設定為權威。
然後,我們將透過 state
協助程式結構檢查傳入的訊息,以查看我們應該傳回什麼。
ip := state.IP()
var rr dns.RR
switch state.Family() {
case 1:
rr = &dns.A{}
rr.(*dns.A).Hdr = dns.RR_Header{Name: state.QName(),
Rrtype: dns.TypeA, Class: state.QClass()}
rr.(*dns.A).A = net.ParseIP(ip).To4()
case 2:
rr = &dns.AAAA{}
rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: state.QName(),
Rrtype: dns.TypeAAAA, Class: state.QClass()}
rr.(*dns.AAAA).AAAA = net.ParseIP(ip)
}
IP()
會傳回客戶端的 IP 位址。Family()
會傳回使用的 IP 版本。根據系列,我們會建立一個包含客戶端位址的 A 或 AAAA 記錄。請注意,我們沒有指定 TTL,這表示它將為零;表示不應快取這些記錄。
接下來,我們想要編碼客戶端的來源連接埠和使用的傳輸協定。
srv := &dns.SRV{}
srv.Hdr = dns.RR_Header{Name: "_" + state.Proto() + "." + state.QName(),
Rrtype: dns.TypeSRV, Class: state.QClass()}
port, _ := strconv.Atoi(state.Port())
srv.Port = uint16(port)
srv.Target = "."
SRV 記錄非常適合此操作。網域名稱會加上 _tcp
或 _udp
的前置詞,而 SRV 記錄的連接埠號碼會重複使用,以回傳客戶端的連接埠號碼,這表示我們會建立類似這樣的內容:_tcp.example.org. 0 IN SRV 0 0 <portNr>
。
在此方法的最後一部分中,我們建立完整的訊息並傳送它
a.Extra = []dns.RR{rr, srv}
w.WriteMsg(a)
return 0, nil
首先,我們將兩個建立的資源記錄 (rr
和 srv
) 新增至答案訊息的附加區段:a.Extra
。
然後,最後,我們在 w
上呼叫 WriteMsg
方法,這會將訊息寫回客戶端。我們透過傳回 0 和 nil
來表示成功 (即使它可能失敗 - 我們沒有檢查 WriteMsg
的傳回值) 的寫入。
4. 掛鉤它
接下來,我們需要告訴 CoreDNS 編譯並使用這個新的外掛程式。新增外掛程式最近已簡化,只需要編輯 plugin.cfg
並新增以下行即可
whoami:whoami
初始數字用於排序外掛程式 (稍後會詳細說明),然後是註冊中使用的外掛程式名稱,接著是 CoreDNS 外掛程式目錄內的套件。
每個外掛程式在所有其他外掛程式的清單中都有一個位置。例如,快取或度量外掛程式需要提早出現,以便它可以「看到」查詢和回應,並對其執行快取或度量相關的操作。*whoami* 外掛程式沒有那麼特殊,可以放在清單中相對較晚的位置 (因此數字較高)。
現在執行 make
(或 go generate && go build
) 以取得一個 coredns
二進位檔,它可以與我們全新的外掛程式一起使用。此二進位檔在呼叫 -plugins
時應包含 dns.whoami
。
5. 使用它
撰寫 Corefile
. {
whoami
}
用文字來說,這表示:對根 .
及以下具有權威性,表示所有可能的查詢都會命中此 stanza。並且對於每個請求,呼叫 *whoami*。
使用以下命令啟動 CoreDNS:coredns -conf Corefile -dns.port 1053
。這裡有幾個注意事項。CoreDNS 將會在目前目錄中尋找 Corefile,因此 -conf Corefile
僅在此處為了完整性而給出。-dns.port
將會在連接埠 1053 上啟動 CoreDNS,因此我們不需要以根使用者身分執行。
CoreDNS 將輸出以下內容
.:1053
CoreDNS-1.6.4
linux/amd64, go1.13.1, b139ba3
.:1053
表示它已解析我們的 Corefile,並在連接埠 1053 上接聽根 .
區域及以下的查詢。
因此,讓我們用 dig
傳送一個查詢
% dig +nocmd @localhost mx example.org -p1053 +noall +additional
example.org. 0 IN A 127.0.0.1
_udp.example.org. 0 IN SRV 0 0 58359 .
太棒了。它正在運作。檢查回應,此請求是透過 UDP 從連接埠 58359 使用 IPv4 傳送的。
讓我們嘗試使用 TCP
% dig +nocmd @localhost a example.org -p1053 +noall +additional +tcp
example.org. 0 IN A 127.0.0.1
_tcp.example.org. 0 IN SRV 0 0 33435 .
是的,它正確地看到我們這次使用了 TCP (當然還有不同的連接埠)。
CoreDNS 中已經有很多不同的外掛程式。我們隨時歡迎新的、令人興奮的外掛程式。因此,如果您有任何想法,請在追蹤器上開啟一個問題。
這是關於撰寫 CoreDNS 外掛程式的先前部落格的更新版本。
另請參閱範例外掛程式,這是一個更簡單的外掛程式,僅作為範例。