[Go] API 활용해 DNS 아이피 고정하기

Posted on Oct 5, 2021

집에서 서비스 배포 후 도메인에 연결시키기 위해서는 레코드에 아이피를 입력해줘야 하는데 가정집의 경우 유동 아이피이기 때문에 업데이를 계속 해주거나 DDNS 서비스를 활용해서 연결해야한다. 그래서 DDNS 서비스를 사용하던 중 Namesilo 사에서 api로 레코드 정보를 조회하고 업데이트할 수 있는 방법이 있어서 이 방법으로 하려고 한다.

순서

도메인의 레코드 조회 -> A레코드에 해당하는 값들 저장 -> 아이피 조회 -> A레코드에 아이피 업데이트 -> 빌드 후 crontab에 등록

Namesilo의 API키 발급

발급은 여기에서 할 수 있다. 내가 이용할 API는 조회업데이트이다.

도메인의 레코드 조회

도메인의 레코드를 조회하는 함수이다

func aDomain() []ResourceRecord {
        url := "https://www.namesilo.com/api/dnsListRecords?version=1&type=xml&key=" + apiKey + "&domain=" + myDomain
        res, err := http.Get(url)
        checkErr(err)
        defer res.Body.Close()
        data, err := ioutil.ReadAll(res.Body)
        checkErr(err)
        var name NameSilo
        err = xml.Unmarshal(data, &name)
        checkErr(err)
        var aDomains []ResourceRecord

        for _, resource := range name.Reply.ResourceRecord {
                if resource.Typee == "A" {
                        aDomains = append(aDomains, resource)
                }
        }
        return aDomains
}

url을 만들고 get으로 요청을 보내서 응답을 받는다. 이때 응답은 xml로 이루어지기 때문에 xml 구조체를 정의해야 한다.

type NameSilo struct {
        XMLName xml.Name `xml:"namesilo"`
        Request Request  `xml:"request"`
        Reply   Reply    `xml:"reply"`
}

type Request struct {
        XMLName   xml.Name `xml:"request"`
        Operation string   `xml:"operation"`
        Ip        string   `xml:"ip"`
}

type Reply struct {
        XMLName        xml.Name         `xml:"reply"`
        Code           string           `xml:"code"`
        Detail         string           `xml:"detail"`
        ResourceRecord []ResourceRecord `xml:"resource_record"`
}
type ResourceRecord struct {
        XMLName  xml.Name `xml:"resource_record"`
        RecordId string   `xml:"record_id"`
        Typee    string   `xml:"type"`
        Host     string   `xml:"host"`
        Value    string   `xml:"value"`
        Ttl      string   `xml:"ttl"`
        Distance string   `xml:"distance"`
}

아까 링크에서 볼 수 있는 예시응답을 참조하여서 만들었다. A레코드만을 가져와서 저장하게끔 함수를 만들어주었다. 사실 만약 탑레벨 도메인이 A레코드로 연결돼있고, 서브도메인에 와일드카드를 사용했다면 그냥 바로 아이피 조회 후 업데이트를 할 수 있다.

ip 조회

ip 조회는 ifconfig.me에 요청하면 받을 수 있는 ip주소의 값을 사용했다.

func ipAddr() string {
        resp, err := http.Get("https://ifconfig.me")
        checkErr(err)

        defer resp.Body.Close()

        data, err := ioutil.ReadAll(resp.Body)
        checkErr(err)

        return string(data)
}

레코드 업데이트

이제 레코드를 업데이트하기 위한 함수를 만든다.

func update(aDomains []ResourceRecord, ip string) {
        for _, resource := range aDomains {
                var urlString string
                if resource.Host == myDomain {
                        urlString = "https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key=" + apiKey + "&domain=" + myDomain + "&rrid=" + resource.RecordId + "&rrvalue=" + ip
                } else {
                        urlString = "https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key=" + apiKey + "&domain=" + myDomain + "&rrid=" + resource.RecordId + "&rrhost=" + strings.Split(resource.Host, "."+myDomain)[0] + "&rrvalue=" + ip
                }

                respo, err := http.Get(urlString)
                checkErr(err)
                defer respo.Body.Close()
                dataaa, err := ioutil.ReadAll(respo.Body)
                checkErr(err)
                var updatesilo NamesiloUpdate
                err = xml.Unmarshal(dataaa, &updatesilo)
                checkErr(err)
                log.Println(updatesilo)
        }
}

아까의 함수들을 이용해 생성한 값들을 인자로 넘겨받을 수 있게 하였다. 응답받을 때의 host 값은 서브.도메인.컴 이런 식으로 받는다. 요청할 때는 host값을 서브도>메인 혹은 탑레벨일 경우 값 없이 보내줘야 하기 때문에 if 구문을 추가하여서 확인을 한 번 거치게끔 하였다.

그 후 응답이 어떻게 왔는지 확인하기 위해서 xml응답을 저장 후 출력하게끔 만들었다. 이를 위해 또 xml 구조체를 만들어야 하는데 이는 다음과 같다.

type NamesiloUpdate struct {
        XMLNAME xml.Name      `xml:"namesilo"`
        Request RequestUpdate `xml:"request"`
        Reply   ReplyUpdate   `xml:"reply"`
}
type RequestUpdate struct {
        Operation string `xml:"operation"`
        Ip        string `xml:"ip"`
}
type ReplyUpdate struct {
        Code     string `xml:"code"`
        Detail   string `xml:"detail"`
        RecordId string `xml:"record_id"`
}

전체코드