6
6
the current IP address, leveraging the Cloudflare API.
7
7
"""
8
8
import logging
9
+ import socket
9
10
from typing import Callable , Optional , Any , Tuple
10
11
from datetime import datetime
11
12
@@ -90,6 +91,7 @@ def _obtain_record(self, record_name: str) -> Optional[Tuple[str, datetime, Opti
90
91
record .name or 'domain' : record for record in result
91
92
}
92
93
94
+
93
95
domain_record = records .get (record_name )
94
96
95
97
if domain_record is None :
@@ -103,46 +105,120 @@ def _obtain_record(self, record_name: str) -> Optional[Tuple[str, datetime, Opti
103
105
return self .storage .retrieve_record (record_name )
104
106
105
107
@cf_error_handler
106
- def update_dns (self , ip_address : str , record_name : Optional [str ] = None ) -> None :
108
+ def check_cloudflare_ip (self , record_name : str ) -> Optional [str ]:
109
+ """
110
+ Checks the A record IP address in cloudflare utilizing the API
107
111
108
- """
109
- Updates IP address for specified record
112
+ This is recomended if you have a proxied connection.
113
+ """
110
114
111
- Automatically infers record_name if it is defined in the ddns.ini file.
115
+ if record_name is None :
116
+ logging .error ("CloudFlare DNS: In _obtain_record: Record_name not provided." )
117
+ raise ValueError ("CloudFlare DNS: Record name cannot be None" )
118
+
119
+ response = self .storage .retrieve_record (record_name )
120
+
121
+ if not response :
122
+ response = self ._obtain_record (record_name )
123
+
124
+ record_id = response [2 ]
125
+ api_res = self .cf_client .dns .records .get (zone_id = self .zone_id , dns_record_id = record_id )
126
+
127
+ if not api_res :
128
+ logging .error ("Cloudflare DNS: API call failed." )
129
+ return None
130
+
131
+ return api_res .content
132
+
133
+ def cloudflare_dns_lookup (self , record_name : str ) -> str :
134
+ """
135
+ Performs a DNS lookup for the record name for Cloudflare.
136
+
137
+ This will not return the correct IP if you have a proxied connection.
112
138
"""
113
- record_name = record_name or self .config .get (self .service_name ,'record_name' )
139
+ logging .debug ("CloudFlare DNS: Performing DNS lookup for %s" , record_name )
140
+ return socket .gethostbyname (record_name )
141
+
142
+ def check_and_update_dns (self , record_name : Optional [str ] = None ) -> None :
143
+ """
144
+ Compares the actual Cloudflare A record with the local database record.
145
+ If they are different, updates Cloudflare with the current IP address.
146
+ """
147
+ record_name = record_name or self .config .get (self .service_name , 'record_name' )
114
148
115
149
if not record_name :
116
150
raise ValueError ("CloudFlare DNS: Record name cannot be None" )
117
151
118
- logging .debug ("CloudFlare DNS: Preparing to uppdate %s with IP:" , record_name )
152
+ logging .debug ("CloudFlare DNS: Checking current IP for %s" , record_name )
153
+ cloudflare_ip = self .check_cloudflare_ip (record_name )
154
+ logging .debug ("CloudFlare DNS: Current IP for %s is %s" , record_name , cloudflare_ip )
119
155
120
156
record : Optional [Tuple [str , datetime , str ]] = self ._obtain_record (record_name )
121
157
122
158
if not record :
123
159
logging .error ("CloudFlare DNS: No record found for %s." , record_name )
124
160
return
125
161
126
- current_ip : str = record [0 ]
127
- record_id : str = record [2 ]
162
+ current_ip = self .get_ipv4 ()
163
+ db_ip = record [0 ]
164
+ logging .debug ("CloudFlare DNS: IP from database is %s" , db_ip )
128
165
129
- if current_ip == ip_address :
166
+ if current_ip != db_ip :
130
167
logging .info (
131
- "CloudFlare DNS: No update needed for %s - IP is already %s ." ,
132
- record_name , ip_address
168
+ "CloudFlare DNS: Local IP has changed from %s to %s, updating Cloudflare ." ,
169
+ db_ip , current_ip
133
170
)
171
+ self .update_dns (current_ip , record_name )
134
172
return
135
173
174
+ if db_ip != cloudflare_ip :
175
+ logging .info (
176
+ "CloudFlare DNS: A record has changed from %s to %s for %s, updating Cloudflare" ,
177
+ db_ip , cloudflare_ip , record_name
178
+ )
179
+ self .update_dns (current_ip , record_name )
180
+ return
181
+
182
+ logging .info (
183
+ "CloudFlare DNS: No update needed for %s - IP is still %s" ,
184
+ record_name , db_ip
185
+ )
186
+ return
187
+
188
+ @cf_error_handler
189
+ def update_dns (self , ip_address : str , record_name : Optional [str ] = None ) -> None :
190
+ """
191
+ Updates IP address for specified record
192
+ Automatically infers record_name if it is defined in the ddns.ini file.
193
+ """
194
+ record_name = record_name or self .config .get (self .service_name , 'record_name' )
195
+
196
+ if not record_name :
197
+ raise ValueError ("CloudFlare DNS: Record name cannot be None" )
198
+
199
+ logging .info ("CloudFlare DNS: Preparing to update %s with IP: %s" , record_name , ip_address )
200
+
201
+ record : Optional [Tuple [str , datetime , str ]] = self ._obtain_record (record_name )
202
+
203
+ if not record :
204
+ logging .error ("CloudFlare DNS: No record found for %s." , record_name )
205
+ return
206
+
207
+ record_id : str = record [2 ]
208
+
209
+ comment = f"Updated on { datetime .now ()} by py_ddns."
136
210
response = self .cf_client .dns .records .update (
137
- content = ip_address ,
138
- zone_id = self .zone_id ,
139
- name = record_name or NOT_GIVEN ,
140
- dns_record_id = record_id ,
141
- comment = f"Updated on { datetime .now ()} by py_ddns." ,
211
+ content = ip_address ,
212
+ zone_id = self .zone_id ,
213
+ type = "A" ,
214
+ proxied = True ,
215
+ name = record_name or NOT_GIVEN ,
216
+ dns_record_id = record_id ,
217
+ comment = comment ,
142
218
)
143
219
144
220
if response is None :
145
- logging .error ("CloudFlare DNS: No response recienved from cloudflare ." )
221
+ logging .error ("CloudFlare DNS: No response received from Cloudflare ." )
146
222
return
147
223
148
224
self .storage .update_ip (self .service_name , record_name , response .content )
0 commit comments