Cloudflareを使用したGoのカスタムダイナミックDNS

なぜこれが必要なのですか?



仕事から自宅のコンピューターにsshでアクセスする必要が非常に頻繁にあり、プロバイダーが白いが動的に変化するIPアドレスを提供することがありました。 もちろん、ダイナミックDNSを選択したため、最初に出会った無料のno-ipプロバイダーを選びました。 彼らのデーモンは、サービスから無料の第3レベルドメインのdnsレコードを変更するという素晴らしい仕事をしました。そして、CNAMEは私のドメインでそのドメインに登録されました。



これはすべて、Zyxel Keenetic Gigaを購入するまで完璧に機能しました。 彼はすぐに使えるIPアドレスのない友人ですが、何らかの理由で今はドメインにアクセスできませんでした。 この問題は、プロバイダーから静的IPを購入し、 amarao優れたガイドを使用してssh構成に書き込むことで解決できますが、それも面白くありません! それで、あなたのサービスを書く時です!



実際、どこでIPアドレスを取得しますか?



私が最初に尋ねたのは、まさにこの質問でした。 無料のSTUNサーバーの1つを使用することができました(幸いなことに、goのstunクライアントはgithub上にあります)、一部のサービスを恐怖に陥れる可能性がありますが、できるだけ頻繁に自分のアドレスを確認するつもりでした。 私は自分で何でもインストールできるサーバーを持っているので、私は狂気に単純なサービスを書くことにしました。



upd:問題を解決するいくつかの方法:





IPクライアントを単に発行するサービス


youripと呼びます。 GETリクエストで/ ipにipを返すだけです。

簡単にするためにhttprouterを使用することにしました。これは外出先で最速で最も簡単なルーターです。 これが最初で唯一のハンドラーです。

func PrintIp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, r.Header.Get("X-Real-IP")) }
      
      





応答でヘッダー「X-Real-IP」の値を書き込むだけです。 このヘッダーは、構成するとnginxによって渡されます。 また、リバースプロキシ経由ではなく直接このサービスにアクセスする場合は、r.Header.Get( "X-Real-IP")の代わりにr.RemoteAddrを使用する必要があります。



プログラムコード全体(githubでも入手可能 ):

 package main import ( "fmt" "github.com/julienschmidt/httprouter" "log" "net/http" "flag" ) //   var ( port = flag.Int("port", 80, "port") // ,   host = flag.String("host", "", "host") //  prefix = flag.String("prefix", "/ip", "uri prefix") //   ) func PrintIp(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, r.Header.Get("X-Real-IP")) } func main() { //   flag.Parse() //   addr := fmt.Sprintf("%s:%d", *host, *port) log.Println("listening on", addr) router := httprouter.New() //    url router.GET(*prefix, PrintIp) //     log.Fatal(http.ListenAndServe(addr, router)) }
      
      





nginxを構成するために残ります。 このようなもので十分でしょう:

 upstream yourip { server locahost:888; #        } server { listen 80; location /ip { proxy_set_header X-Real-IP $remote_addr; proxy_pass http://yourip; } }
      
      





そして、例えば./yourip -port = 888などのサービスを開始します

このリンクをクリックすると、サービスの動作を確認できます。サービスをホストする場所がない場合は、 このリンクを使用することもできます。



Cloudflareでレコードを更新する方法は?



Cloudflare apiには、特定のドメインのエントリを変更できるrec_editメソッドがあります。

レコード識別子を見つける


最初にレコードのIDを何らかの方法で見つける必要があります。別の方法がこれに役立ちます-rec_load_all



次のようなPOST要求を行う必要があります。

 curl https://www.cloudflare.com/api_json.html \ -d 'a=rec_load_all' \ -d 'tkn=8afbe6dea02407989af4dd4c97bb6e25' \ -d 'email=sample@example.com' \ -d 'z=example.com'
      
      





そして、あなたはそれを外出先で行う必要があります。 すばらしいnet / urlおよびnet / httpパッケージがこれを助けてくれます。

まず、ベースURLを準備します

 //    ,    func Url() (u url.URL) { u.Host = "www.cloudflare.com" u.Scheme = "https" u.Path = "api_json.html" return }
      
      





この関数は、コードを繰り返さないようにするのに役立ちます。 APIに対して合計2つのリクエストを行います。

次に、パラメーターを追加します。

 u := Url() //    //  ()    url values := u.Query() values.Add("email", *email) values.Add("tkn", *token) values.Add("a", "rec_load_all") values.Add("z", *domain) //    RawQuery ,     u.RawQuery = values.Encode() reqUrl := u.String()
      
      





理解を深めるには、「 URLタイプと 」を参照してください。



リクエストを作成して実行します。

 client = http.Client{} req, _ := http.NewRequest("POST", reqUrl, nil) res, err := client.Do(req)
      
      







jsonで応答を処理するには、応答を何らかの構造に逆シリアル化する必要があります。 答えの例を見て、これをコンパイルしました:

 type AllResponse struct { Response struct { Records struct { Objects []struct { Id string `json:"rec_id"` Name string `json:"name"` Type string `json:"type"` Content string `json:"content"` } `json:"objs"` } `json:"recs"` } `json:"response"` }
      
      





したがって、答えを解析するときに必要なデータのみを取得します。

 //  ,    response := &AllResponse{} //   decoder := json.NewDecoder(res.Body) //        err = decoder.Decode(response)
      
      





次に、受信したデータを処理して、すべてのレコードを調べます。

 for _, v := range response.Response.Records.Objects { //        if v.Name == *target && v.Type == "A" { //       id, _ := strconv.Atoi(v.Id) return id, v.Content, nil } }
      
      





最後に、必要なものを見つけました-識別子



レコードを変更する


再度リクエストを作成する必要があります。 URLの収集を始めましょう:

 u := Url() values := u.Query() values.Add("email", *email) values.Add("tkn", *token) values.Add("a", "rec_edit") values.Add("z", *domain) values.Add("type", "A") values.Add("name", *target) values.Add("service_mode", "0") values.Add("content", ip) values.Add("id", strconv.Itoa(id)) values.Add("ttl", fmt.Sprint(*ttl))
      
      





これで、IPアドレスを置き換えるために必要なすべての情報が得られました。 前回と同様に、リクエストを作成して実行するだけです。

 req, _ := http.NewRequest("POST", reqUrl, nil) res, err := client.Do(req)
      
      





実際、これが楽しみの終わりです。 これら2つのクエリは別々の関数に送信され、必要な変数はすべてフラグに送信され、メインの無限ループが作成されます。



 func main() { flag.Parse() //  id   ip id, previousIp, err := GetDnsId() if err != nil { log.Fatalln("unable to get dns record id:", err) } //  ,      // 5   ip  ticker := time.NewTicker(time.Second * 5) //     for _ = range ticker.C { ip, err := GetIp() if err != nil { continue } if previousIp != ip { err = SetIp(ip, id) if err != nil { continue } } log.Println("updated to", ip) previousIp = ip } }
      
      







それだけです コードはgithubにあります。



完全なコード
 package main import ( "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "net/http" "net/url" "strconv" "time" ) //      api type AllResponse struct { Response struct { Records struct { Objects []struct { Id string `json:"rec_id"` Name string `json:"name"` Type string `json:"type"` Content string `json:"content"` } `json:"objs"` } `json:"recs"` } `json:"response"` } //     var ( yourIpUrl = flag.String("url", "https://cydev.ru/ip", "Yourip service url") domain = flag.String("domain", "cydev.ru", "Cloudflare domain") target = flag.String("target", "me.cydev.ru", "Target domain") email = flag.String("email", "ernado@ya.ru", "The e-mail address associated with the API key") token = flag.String("token", "-", "This is the API key made available on your Account page") ttl = flag.Int("ttl", 120, "TTL of record in seconds. 1 = Automatic, otherwise, value must in between 120 and 86400 seconds") // http  -     Do,    client = http.Client{} ) //    ,    func Url() (u url.URL) { u.Host = "www.cloudflare.com" u.Scheme = "https" u.Path = "api_json.html" return } // SetIp      id func SetIp(ip string, id int) error { u := Url() values := u.Query() values.Add("email", *email) values.Add("tkn", *token) values.Add("a", "rec_edit") values.Add("z", *domain) values.Add("type", "A") values.Add("name", *target) values.Add("service_mode", "0") values.Add("content", ip) values.Add("id", strconv.Itoa(id)) values.Add("ttl", fmt.Sprint(*ttl)) u.RawQuery = values.Encode() reqUrl := u.String() log.Println("POST", reqUrl) req, err := http.NewRequest("POST", reqUrl, nil) if err != nil { return err } res, err := client.Do(req) if err != nil { return err } if res.StatusCode != http.StatusOK { return errors.New(fmt.Sprintf("bad status %d", res.StatusCode)) } return nil } // GetDnsId  id      func GetDnsId() (int, string, error) { log.Println("getting dns record id") //   url u := Url() //    values := u.Query() values.Add("email", *email) values.Add("tkn", *token) values.Add("a", "rec_load_all") values.Add("z", *domain) u.RawQuery = values.Encode() reqUrl := u.String() //  ,      log.Println("POST", reqUrl) req, err := http.NewRequest("POST", reqUrl, nil) res, err := client.Do(req) if err != nil { return 0, "", err } if res.StatusCode != http.StatusOK { return 0, "", errors.New(fmt.Sprintf("bad status %d", res.StatusCode)) } response := &AllResponse{} //   decoder := json.NewDecoder(res.Body) //        err = decoder.Decode(response) if err != nil { return 0, "", err } //     for _, v := range response.Response.Records.Objects { //        if v.Name == *target && v.Type == "A" { //       id, _ := strconv.Atoi(v.Id) return id, v.Content, nil } } //      return 0, "", errors.New("not found") } // GetIp()   yourip     ip  func GetIp() (string, error) { res, err := client.Get(*yourIpUrl) if err != nil { return "", err } if res.StatusCode != http.StatusOK { return "", errors.New(fmt.Sprintf("bad status %d", res.StatusCode)) } body, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } return string(body), nil } func main() { flag.Parse() id, previousIp, err := GetDnsId() if err != nil { log.Fatalln("unable to get dns record id:", err) } log.Println("found record", id, "=", previousIp) //  ,      // 5   ip  ticker := time.NewTicker(time.Second * 5) //     for _ = range ticker.C { ip, err := GetIp() if err != nil { log.Println("err", err) continue } if previousIp != ip { err = SetIp(ip, id) if err != nil { log.Println("unable to set ip:", err) continue } } log.Println("updated to", ip) previousIp = ip } }
      
      








All Articles