Last active
October 10, 2023 20:10
-
-
Save dong-zeyu/a67183007973d89c7f9696862bd106f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <curl/curl.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <arpa/inet.h> | |
#include <sys/socket.h> | |
#include <ifaddrs.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
// #define DEBUG | |
static char *token; | |
struct MemoryStruct { | |
char *memory; | |
size_t size; | |
}; | |
static size_t | |
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) | |
{ | |
size_t realsize = size * nmemb; | |
struct MemoryStruct *mem = (struct MemoryStruct *)userp; | |
char *ptr = realloc(mem->memory, mem->size + realsize + 1); | |
if(!ptr) { | |
/* out of memory! */ | |
printf("not enough memory (realloc returned NULL)\n"); | |
return 0; | |
} | |
mem->memory = ptr; | |
memcpy(&(mem->memory[mem->size]), contents, realsize); | |
mem->size += realsize; | |
mem->memory[mem->size] = 0; | |
return realsize; | |
} | |
int update_ip(char ip[], char subdomain[], char domain_id[], char record_id[]) { | |
const char post_format[] = "login_token=%s&format=json&domain_id=%s&record_id=%s&sub_domain=%s&value=%s&record_type=%s&record_line_id=0&status=enable"; | |
char *record_type; | |
if (strstr(ip, ".") != NULL) { | |
record_type = "A"; | |
} else { | |
record_type = "AAAA"; | |
} | |
int size = snprintf(NULL, 0, post_format, token, domain_id, record_id, subdomain, ip, record_type); | |
char *post_data = malloc(size + 1); | |
snprintf(post_data, size + 1, post_format, token, domain_id, record_id, subdomain, ip, record_type); | |
struct MemoryStruct result; | |
result.memory = malloc(1); | |
result.memory[0] = '\0'; | |
result.size = 0; | |
CURL *curl = curl_easy_init(); | |
curl_easy_setopt(curl, CURLOPT_URL, "https://dnsapi.cn/Record.Modify"); | |
#ifdef DEBUG | |
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); | |
#endif | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); | |
curl_easy_setopt(curl, CURLOPT_POST, 1); | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); | |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)& result); | |
CURLcode res = curl_easy_perform(curl); | |
#ifdef DEBUG | |
printf("POST: %s\n", post_data); | |
printf("RESP: %s\n", result.memory); | |
#endif | |
int ret = 0; | |
// code: 1 success | |
if (strstr(result.memory, "\"code\":\"1\"") == NULL) { | |
printf("Failed to finish request\n"); | |
#ifndef DEBUG | |
printf("POST: %s\n", post_data); | |
printf("RESP: %s\n", result.memory); | |
#endif | |
ret = -1; | |
} | |
free(post_data); | |
free(result.memory); | |
curl_easy_cleanup(curl); | |
return ret; | |
} | |
int main(int argc, char *argv[]) { | |
curl_global_init(CURL_GLOBAL_ALL); | |
if (argc != 7) { | |
printf("Usage: %s <ifname> <seq> <subdomain> <domain_id> <record_id> <token>\n", argv[0]); | |
return 1; | |
} | |
size_t n = strlen(argv[6]); | |
token = malloc(n + 1); | |
memcpy(token, argv[6], n + 1); | |
memset(argv[6], ' ', n); | |
int seq = atoi(argv[2]); | |
struct ifaddrs *ifap, *ifa; | |
int seq_count; | |
char addr[INET6_ADDRSTRLEN]; | |
char addr_new[INET6_ADDRSTRLEN]; | |
while(1) { | |
seq_count = 0; | |
getifaddrs(&ifap); | |
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { | |
if (ifa->ifa_name && strcmp(ifa->ifa_name, argv[1]) == 0 && ifa->ifa_addr && (ifa->ifa_addr->sa_family==AF_INET | ifa->ifa_addr->sa_family==AF_INET6)) { | |
seq_count += 1; | |
if (seq_count < seq) { | |
continue; | |
} else if (seq_count > seq) { | |
break; | |
} | |
if (ifa->ifa_addr->sa_family==AF_INET) { | |
struct sockaddr_in *sa = (struct sockaddr_in *) ifa->ifa_addr; | |
inet_ntop(AF_INET, &sa->sin_addr, addr_new, sizeof(addr_new)); | |
} else { | |
struct sockaddr_in6 *sa = (struct sockaddr_in6 *) ifa->ifa_addr; | |
inet_ntop(AF_INET6, &sa->sin6_addr, addr_new, sizeof(addr_new)); | |
} | |
#ifdef DEBUG | |
printf("IP: %s\n", addr_new); | |
#endif | |
if (strcmp(addr, addr_new) != 0) { | |
printf("Update %s\n", addr_new); | |
if (update_ip(addr_new, argv[3], argv[4], argv[5]) == 0) { | |
strncpy(addr, addr_new, INET6_ADDRSTRLEN); | |
} else { | |
sleep(60); | |
} | |
} | |
} | |
} | |
sleep(10); | |
freeifaddrs(ifap); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import os | |
import requests | |
import logging | |
import time | |
import json | |
import netifaces | |
os.chdir(sys.path[0]) | |
log_file = logging.FileHandler("ip-update.log") | |
log_file.setLevel(logging.INFO) | |
log_std = logging.StreamHandler(sys.stdout) | |
log_std.setLevel(logging.INFO) | |
log_err = logging.StreamHandler(sys.stderr) | |
log_err.setLevel(logging.DEBUG) | |
logging.basicConfig( | |
format="[%(asctime)s.%(msecs)03d] [%(levelname)s] %(message)s", | |
#format=".%(msecs)03d - %(levelname)s - %(message)s", | |
datefmt='%Y/%b/%d %H:%M:%S', | |
level=logging.DEBUG, | |
handlers=[log_file, log_std, log_err]) | |
# Config | |
data_t = { | |
"login_token": ddddd,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, | |
"format": "json", | |
"domain_id": dddddddd, | |
"record_line_id": "0", | |
"sub_domain": SUBDOMAIN, | |
"status": "enable", | |
} | |
# return a filter that iterate e in l such that f(e,t) returns True for all t in tests | |
allof_filter = lambda f, tests, l: filter(lambda x: not list(filter(lambda t: not f(x,t) , tests)), l) | |
records = [] # all record-id | |
bypass_inte = [ | |
'lo', # Linux Loopback | |
'docker', # Linux docker | |
] | |
# When select_inte is not empty, bypass_inte is not used. | |
select_inte = [ | |
'eth0', # Linux LAN | |
'wlan0', # Linux WLAN | |
] | |
subdomain_map = { | |
"eth0": "lan", | |
} | |
bypass_prefix = ['127.', 'fe80:', '169.', '::1', '10.'] | |
def ensure_network(): | |
# Check network availability | |
while True: | |
try: | |
with requests.get("https://www.baidu.com/", timeout=5): | |
break | |
except Exception: | |
logging.info("Wait for Network...") | |
time.sleep(10) | |
def get_ips(): | |
ips = [] | |
flt = filter(lambda x: x in select_inte, netifaces.interfaces()) if select_inte \ | |
else filter(lambda x: not x in bypass_inte, netifaces.interfaces()) | |
for interface in flt: | |
inte = netifaces.ifaddresses(interface) | |
logging.debug("Interface: %s\n%s", interface, inte) | |
for tp in [netifaces.AF_INET, netifaces.AF_INET6]: | |
addrs = inte.get(tp, []) | |
for addr in allof_filter(lambda x, t: not x['addr'].startswith(t), bypass_prefix, addrs): | |
ip = addr['addr'].split("%")[0] | |
ips.append({ | |
"interface": interface, | |
"ip": ip, | |
"type": tp | |
}) | |
logging.debug("Find ip address %s", ip) | |
return ips | |
def update_request(ips, records): | |
updates = [] | |
records = records.copy() | |
for ip in ips: | |
try: | |
data = data_t.copy() | |
data["record_id"] = records.pop() | |
data["record_type"] = "AAAA" if ip["type"] == netifaces.AF_INET6 else "A" | |
data["value"] = ip["ip"] | |
data["sub_domain"] = subdomain_map.get(ip["interface"], data['sub_domain']) | |
logging.info("Adding Record: %s to subdomain %s", ip["ip"], data["sub_domain"]) | |
updates.append(data) | |
except IndexError: | |
logging.warning("No enough ip record! Skip %s", ip["ip"]) | |
# Remain records are disables | |
for record in records: | |
data = data_t.copy() | |
data["record_id"] = record | |
data["record_type"] = "A" | |
data["value"] = "127.0.0.1" | |
data["status"] = "disable" | |
updates.append(data) | |
# Send update requests | |
retry = 10 | |
logging.debug("Data\n%s", updates) | |
while len(updates) > 0: | |
if retry == 0: | |
logging.warning("Too Many Retries! Exit.") | |
break | |
update = updates.pop() | |
try: | |
with requests.post('https://dnsapi.cn/Record.Modify', data=update, timeout=10) as re: | |
if not re.status_code == 200: | |
raise Exception(f"Server returned with status code {re.status_code}!") | |
msg = json.loads(re.text) | |
logging.debug("Response: %s", msg) | |
if msg['status']['code'] == '1': | |
logging.info("Update %s succeed", update["value"]) | |
else: | |
logging.warning("Failed to update %s: %s", update['value'], msg['status']['message']) | |
except Exception as e: | |
logging.warning("Error updating %s: %s", update["value"], e) | |
retry = retry - 1 | |
updates.insert(0, update) | |
time.sleep(30) | |
if __name__ == "__main__": | |
ips = [] | |
while True: | |
ensure_network() | |
ips_new = get_ips() | |
if not ips_new == ips: | |
ips = ips_new | |
update_request(ips, records) | |
time.sleep(60) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Unit] | |
Description=IP update service | |
After=network.target network-online.target nss-lookup.target | |
StartLimitIntervalSec=0 | |
[Service] | |
Type=simple | |
Restart=on-failure | |
RestartSec=5 | |
User=daemon | |
ExecStart=/usr/local/bin/update-ip | |
StandardError=null | |
[Install] | |
WantedBy=multi-user.target |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment