Skip to content

Instantly share code, notes, and snippets.

@dong-zeyu
Last active October 10, 2023 20:10
Show Gist options
  • Save dong-zeyu/a67183007973d89c7f9696862bd106f0 to your computer and use it in GitHub Desktop.
Save dong-zeyu/a67183007973d89c7f9696862bd106f0 to your computer and use it in GitHub Desktop.
#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;
}
#!/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)
[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