如何將外掛程式新增至 CoreDNS

如何將外掛程式新增至 CoreDNS;教學。

CoreDNS 是一個鏈接外掛程式的 DNS 伺服器。外掛程式定義為一個方法:ServeDNS(),它會取得請求,並回應客戶端或將請求傳遞給下一個外掛程式。如果沒有任何外掛程式處理請求,則會傳回 SERVFAIL 的預設回應。

這篇部落格文章詳細說明如何將外掛程式新增至 CoreDNS。我們以 *whoami* 外掛程式為例,它是一個 CoreDNS 外掛程式,如果沒有指定 Corefile,則預設會載入它。

請注意,這裡的所有程式碼範例都是使用 Go 語言,因為 CoreDNS 是用 Go 語言撰寫的。

第一個問題應該是:「我的外掛程式應該做什麼?」。以 *whoami* 外掛程式為例,其目的是回傳客戶端的 IP (IPv4 或 IPv6)、使用的傳輸協定 (「tcp」或「udp」) 和請求的連接埠號碼。

下一個問題是:「這個新外掛程式的名稱是什麼?」。請嘗試為此找到一個簡短、描述性的名稱。在這種情況下,我們已經有一個 (好) 名稱:*whoami*。

外掛程式

外掛程式由多個部分組成

  1. 外掛程式的註冊
  2. 設定函數,解析 Corefile 中的 *whoami* 外掛程式和可能的參數
  3. ServeDNS()Name() 方法

在定義好各部分後,我們可以

  1. 掛鉤它
  2. 使用它

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

首先,我們將兩個建立的資源記錄 (rrsrv) 新增至答案訊息的附加區段: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 外掛程式的先前部落格的更新版本。

另請參閱範例外掛程式,這是一個更簡單的外掛程式,僅作為範例。

Miek Gieben
發佈時間:,標籤為 文件操作說明外掛程式教學,使用 1332 個字。