2 * API Reference: https://api.gandi.net/docs/livedns/
4 * Some similar implementations:
5 * - https://github.com/rmarchant/gandi-ddns/blob/master/gandi_ddns.py
6 * - https://github.com/brianhp2/gandi-automatic-dns
10 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
13 net
::{IpAddr
, Ipv4Addr
},
17 use serde_json
::{json
, Value
};
21 error
::{Error
, Result
},
27 const FILE_CONF
: &str = "config.ron";
29 fn main() -> Result
<()> {
30 println!("=== GANDI LiveDNS updater ===");
32 let config
= Config
::read(FILE_CONF
)?
;
35 "Configuration: {:?}",
37 api_key
: String
::from("*****"),
43 let time_beginning_loop
= time
::Instant
::now();
45 if let Err(err
) = check_and_update_dns(&config
.api_key
, &config
.domains
, config
.ttl
) {
46 println!("!! {}", err
);
49 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
51 if elapsed
< config
.delay_between_check
{
52 let to_wait
= config
.delay_between_check
- elapsed
;
53 thread
::sleep(to_wait
);
58 fn check_and_update_dns(api_key
: &str, domains
: &Vec
<(String
, String
)>, ttl
: i32) -> Result
<()> {
59 let real_ip
= get_real_ip()?
;
61 for (hostname
, domain
) in domains
{
62 let current_ip
= get_current_record_ip(api_key
, hostname
, domain
)?
;
64 if real_ip
!= current_ip
{
66 "IP addresses don't match for domain {}: real = {}, dns = {}. Renewing DNS...",
67 domain
, real_ip
, current_ip
69 update_record_ip(api_key
, hostname
, domain
, real_ip
, ttl
)?
;
70 println!("Renewing of {}.{} successfully", hostname
, domain
);
77 fn get_real_ip() -> Result
<Ipv4Addr
> {
78 let url
= "https://api.ipify.org";
79 let client
= reqwest
::blocking
::Client
::new();
81 match client
.get(url
).send() {
83 if resp
.status().is_success() {
84 let content
= resp
.text().unwrap();
85 match content
.parse() {
86 Ok(IpAddr
::V4(ip_v4
)) => Ok(ip_v4
),
87 _
=> Err(Box
::new(Error
{
88 message
: String
::from("Can't parse IPv4 from ipify"),
93 message
: format!("Request unsuccessful to {}: {:#?}", url
, resp
),
97 Err(error
) => Err(Box
::new(Error
{
98 message
: format!("Error during request to {}: {:?}", url
, error
),
103 fn get_current_record_ip(api_key
: &str, name
: &str, fqdn
: &str) -> Result
<Ipv4Addr
> {
104 let json_value
= request_livedns_gandi(
106 &format!("domains/{}/records/{}/A", fqdn
, name
),
110 match &json_value
["rrset_values"][0] {
111 Value
::String(ip_str
) => Ok(ip_str
.parse()?
),
112 _
=> Result
::Err(Box
::new(Error
{
114 "Unable to extract the IP from the JSON answer: {}",
121 fn update_record_ip(api_key
: &str, name
: &str, fqdn
: &str, ip
: Ipv4Addr
, ttl
: i32) -> Result
<()> {
122 let json_body
= json!(
124 "rrset_values": [ format!("{}", ip
) ],
129 let json_value
= request_livedns_gandi(
131 &format!("domains/{}/records/{}/A", fqdn
, name
),
132 Method
::Put(json_body
.to_string()),
135 println!("Update response: {}", json_value
);
145 fn request_livedns_gandi(api_key
: &str, url_fragment
: &str, method
: Method
) -> Result
<Value
> {
146 let url
= format!("https://api.gandi.net/v5/livedns/{}", url_fragment
);
147 let client
= reqwest
::blocking
::Client
::new();
149 let request_builder
= match method
{
150 Method
::Put(body
) => client
.put(&url
).body(body
),
151 Method
::Get
=> client
.get(&url
),
154 match request_builder
155 .header("Authorization", format!("Apikey {}", api_key
))
159 if resp
.status().is_success() {
160 let content
= resp
.text().unwrap();
161 Ok(serde_json
::from_str(&content
).unwrap())
164 message
: format!("Request unsuccessful to {}: {:#?}", &url
, resp
),
168 Err(error
) => Err(Box
::new(Error
{
169 message
: format!("Error during request to {}: {:?}", &url
, error
),