mirror of
https://github.com/drakmor/nanoDNS.git
synced 2026-10-18 17:50:00 -05:00
2127 lines
53 KiB
C
2127 lines
53 KiB
C
#include <arpa/inet.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <netinet/in.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
#if defined(PLATFORM_PS4)
|
|
#include <ps4/kernel.h>
|
|
#include <ps4/klog.h>
|
|
#define PAYLOAD_EXEC_NAME "nanodns-ps4.elf"
|
|
#elif defined(PLATFORM_PS5)
|
|
#include <ps5/kernel.h>
|
|
#include <ps5/klog.h>
|
|
#define PAYLOAD_EXEC_NAME "nanodns.elf"
|
|
#else
|
|
#error "Define PLATFORM_PS4 or PLATFORM_PS5"
|
|
#endif
|
|
|
|
#define APP_NAME "NanoDNS"
|
|
#define APP_VERSION "0.4"
|
|
#define APP_COPYRIGHT "(c) Drakmor"
|
|
#define DATA_DIR "/data/nanodns"
|
|
#define CONFIG_PATH DATA_DIR "/nanodns.ini"
|
|
#define DEFAULT_LOG_PATH DATA_DIR "/nanodns.log"
|
|
#define DNS_PORT 53
|
|
#define MAX_DNS_PACKET 4096
|
|
#define MAX_UPSTREAMS 8
|
|
#define MAX_RULES 128
|
|
#define MAX_EXCEPTIONS 128
|
|
#define MAX_CONFIG_WARNINGS 128
|
|
#define MAX_CONFIG_WARNING_LEN 256
|
|
#define MAX_DOMAIN_LEN 256
|
|
#define MAX_LOG_PATH 256
|
|
#define DEFAULT_TIMEOUT_MS 1500
|
|
#define LISTENER_RETRY_MS 1000
|
|
#define OVERRIDE_TTL 60
|
|
#define PRIVILEGED_AUTHID 0x4801000000000013L
|
|
#define KI_PID_OFFSET 72
|
|
#define KI_TDNAME_OFFSET 447
|
|
|
|
enum {
|
|
SERVER_LISTENER_IPV4 = 0,
|
|
SERVER_LISTENER_IPV6 = 1,
|
|
SERVER_LISTENER_COUNT = 2,
|
|
};
|
|
|
|
int sceNetInit(void);
|
|
int sceNetPoolCreate(const char *name, int size, int flags);
|
|
int sceNetPoolDestroy(int memid);
|
|
int sceNetTerm(void);
|
|
int sceKernelSendNotificationRequest(int, void *, size_t, int);
|
|
|
|
typedef struct notify_request {
|
|
char useless1[45];
|
|
char message[3075];
|
|
} notify_request_t;
|
|
|
|
typedef struct {
|
|
struct in_addr addr;
|
|
char text[INET_ADDRSTRLEN];
|
|
} upstream_t;
|
|
|
|
typedef struct {
|
|
char mask[MAX_DOMAIN_LEN];
|
|
struct in_addr addr;
|
|
char text[INET_ADDRSTRLEN];
|
|
} override_rule_t;
|
|
|
|
typedef struct {
|
|
char mask[MAX_DOMAIN_LEN];
|
|
} exception_rule_t;
|
|
|
|
typedef struct {
|
|
char text[MAX_CONFIG_WARNING_LEN];
|
|
} config_warning_t;
|
|
|
|
typedef struct {
|
|
upstream_t upstreams[MAX_UPSTREAMS];
|
|
size_t upstream_count;
|
|
override_rule_t rules[MAX_RULES];
|
|
size_t rule_count;
|
|
exception_rule_t exceptions[MAX_EXCEPTIONS];
|
|
size_t exception_count;
|
|
int timeout_ms;
|
|
int debug_enabled;
|
|
int quiet_enabled;
|
|
struct in_addr bind_addr;
|
|
char bind_text[INET_ADDRSTRLEN];
|
|
bool bind6_enabled;
|
|
struct in6_addr bind6_addr;
|
|
char bind6_text[INET6_ADDRSTRLEN];
|
|
char log_path[MAX_LOG_PATH];
|
|
config_warning_t warnings[MAX_CONFIG_WARNINGS];
|
|
size_t warning_count;
|
|
} app_config_t;
|
|
|
|
typedef struct {
|
|
uint16_t id;
|
|
uint16_t flags;
|
|
uint16_t qdcount;
|
|
uint16_t ancount;
|
|
uint16_t nscount;
|
|
uint16_t arcount;
|
|
size_t qname_offset;
|
|
size_t question_end;
|
|
char qname[MAX_DOMAIN_LEN];
|
|
uint16_t qtype;
|
|
uint16_t qclass;
|
|
} dns_question_t;
|
|
|
|
typedef struct {
|
|
int fd;
|
|
int family;
|
|
const char *name;
|
|
bool retry_enabled;
|
|
int64_t retry_at_ms;
|
|
} server_listener_t;
|
|
|
|
static volatile sig_atomic_t g_running = 1;
|
|
static int g_libnet_mem_id = -1;
|
|
static int g_debug_enabled = 1;
|
|
static FILE *g_log_file = NULL;
|
|
|
|
static const char *k_default_config =
|
|
"# " APP_NAME " configuration\n"
|
|
"#\n"
|
|
"# [general]\n"
|
|
"# log=<path>\n"
|
|
"# debug=0|1\n"
|
|
"# quiet=0|1\n"
|
|
"# bind=<IPv4>\n"
|
|
"# bind6=<IPv6>|off\n"
|
|
"# Use 0.0.0.0 to listen on all IPv4 interfaces\n"
|
|
"# Use :: to listen on all IPv6 interfaces\n"
|
|
"# Use off to disable the IPv6 listener\n"
|
|
"#\n"
|
|
"# [upstream]\n"
|
|
"# server=<IPv4>\n"
|
|
"# Servers are tried in order until one replies\n"
|
|
"# timeout_ms=<integer-total-budget>\n"
|
|
"#\n"
|
|
"# [overrides]\n"
|
|
"# <dns-mask>=<IPv4>\n"
|
|
"# Supports shell-style masks: *.example.com, api??.test.local, [ab]*.lab\n"
|
|
"#\n"
|
|
"# [exceptions]\n"
|
|
"# One hostname or mask per line; these bypass local overrides\n"
|
|
"\n"
|
|
"[general]\n"
|
|
"log=" DEFAULT_LOG_PATH "\n"
|
|
"debug=0\n"
|
|
"quiet=0\n"
|
|
"bind=127.0.0.1\n"
|
|
"bind6=::1\n"
|
|
"\n"
|
|
"[upstream]\n"
|
|
"server=1.1.1.1\n"
|
|
"server=8.8.8.8\n"
|
|
"server=77.88.8.8\n"
|
|
"timeout_ms=1500\n"
|
|
"\n"
|
|
"[overrides]\n"
|
|
"*.playstation.com=0.0.0.0\n"
|
|
"*.playstation.com.*=0.0.0.0\n"
|
|
"playstation.com=0.0.0.0\n"
|
|
"*.playstation.net=0.0.0.0\n"
|
|
"*.playstation.net.*=0.0.0.0\n"
|
|
"*.psndl.net=0.0.0.0\n"
|
|
"playstation.net=0.0.0.0\n"
|
|
"psndl.net=0.0.0.0\n"
|
|
"# *.example.com=192.168.0.10\n"
|
|
"# exact.host.local=10.0.0.42\n"
|
|
"\n"
|
|
"[exceptions]\n"
|
|
"feature.api.playstation.com\n"
|
|
"*.stun.playstation.net\n"
|
|
"stun.*.playstation.net\n"
|
|
"ena.net.playstation.net\n"
|
|
"post.net.playstation.net\n"
|
|
"gst.prod.dl.playstation.net\n"
|
|
"# auth.api.playstation.net\n"
|
|
"# *.allowed.playstation.net\n";
|
|
|
|
static void
|
|
log_emit_direct(const char *buf, int to_debug) {
|
|
if(g_log_file != NULL) {
|
|
fputs(buf, g_log_file);
|
|
fflush(g_log_file);
|
|
}
|
|
|
|
if(to_debug) {
|
|
fputs(buf, stdout);
|
|
fflush(stdout);
|
|
klog_printf("%s", buf);
|
|
}
|
|
}
|
|
|
|
static int
|
|
logger_init(const app_config_t *cfg) {
|
|
char buf[512];
|
|
|
|
if(g_log_file != NULL) {
|
|
fclose(g_log_file);
|
|
g_log_file = NULL;
|
|
}
|
|
|
|
g_debug_enabled = cfg->debug_enabled ? 1 : 0;
|
|
g_log_file = fopen(cfg->log_path, "w");
|
|
if(g_log_file == NULL) {
|
|
if(!g_debug_enabled) {
|
|
g_debug_enabled = 1;
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "[nanodns] failed to open log file %s: %s\n",
|
|
cfg->log_path, strerror(errno));
|
|
log_emit_direct(buf, g_debug_enabled);
|
|
return -1;
|
|
}
|
|
|
|
setvbuf(g_log_file, NULL, _IOLBF, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
logger_fini(void) {
|
|
if(g_log_file != NULL) {
|
|
fclose(g_log_file);
|
|
g_log_file = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
log_printf(const char *fmt, ...) {
|
|
char buf[1024];
|
|
va_list ap;
|
|
int written;
|
|
|
|
va_start(ap, fmt);
|
|
written = vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if(written < 0) {
|
|
return;
|
|
}
|
|
|
|
log_emit_direct(buf, g_debug_enabled);
|
|
}
|
|
|
|
static void
|
|
log_errno(const char *what) {
|
|
log_printf("[nanodns] %s: %s\n", what, strerror(errno));
|
|
}
|
|
|
|
static const char *
|
|
dns_type_to_string(uint16_t qtype) {
|
|
switch(qtype) {
|
|
case 1:
|
|
return "A";
|
|
case 2:
|
|
return "NS";
|
|
case 5:
|
|
return "CNAME";
|
|
case 6:
|
|
return "SOA";
|
|
case 12:
|
|
return "PTR";
|
|
case 15:
|
|
return "MX";
|
|
case 16:
|
|
return "TXT";
|
|
case 28:
|
|
return "AAAA";
|
|
case 33:
|
|
return "SRV";
|
|
case 41:
|
|
return "OPT";
|
|
case 255:
|
|
return "ANY";
|
|
default:
|
|
return "OTHER";
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
dns_rcode_to_string(uint16_t rcode) {
|
|
switch(rcode) {
|
|
case 0:
|
|
return "NOERROR";
|
|
case 1:
|
|
return "FORMERR";
|
|
case 2:
|
|
return "SERVFAIL";
|
|
case 3:
|
|
return "NXDOMAIN";
|
|
case 4:
|
|
return "NOTIMP";
|
|
case 5:
|
|
return "REFUSED";
|
|
default:
|
|
return "OTHER";
|
|
}
|
|
}
|
|
|
|
static uint16_t
|
|
read_u16(const uint8_t *ptr) {
|
|
return (uint16_t)(((uint16_t)ptr[0] << 8) | ptr[1]);
|
|
}
|
|
|
|
static uint32_t
|
|
read_u32(const uint8_t *ptr) {
|
|
return ((uint32_t)ptr[0] << 24) | ((uint32_t)ptr[1] << 16) |
|
|
((uint32_t)ptr[2] << 8) | ptr[3];
|
|
}
|
|
|
|
static void
|
|
write_u16(uint8_t *ptr, uint16_t value) {
|
|
ptr[0] = (uint8_t)((value >> 8) & 0xff);
|
|
ptr[1] = (uint8_t)(value & 0xff);
|
|
}
|
|
|
|
static void
|
|
write_u32(uint8_t *ptr, uint32_t value) {
|
|
ptr[0] = (uint8_t)((value >> 24) & 0xff);
|
|
ptr[1] = (uint8_t)((value >> 16) & 0xff);
|
|
ptr[2] = (uint8_t)((value >> 8) & 0xff);
|
|
ptr[3] = (uint8_t)(value & 0xff);
|
|
}
|
|
|
|
static char *
|
|
ltrim(char *s) {
|
|
while(*s && isspace((unsigned char)*s)) {
|
|
++s;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
rtrim(char *s) {
|
|
size_t len = strlen(s);
|
|
|
|
while(len > 0 && isspace((unsigned char)s[len - 1])) {
|
|
s[--len] = '\0';
|
|
}
|
|
}
|
|
|
|
static char *
|
|
trim(char *s) {
|
|
s = ltrim(s);
|
|
rtrim(s);
|
|
return s;
|
|
}
|
|
|
|
static void
|
|
strip_inline_comment(char *s) {
|
|
char *hash = strchr(s, '#');
|
|
char *semi = strchr(s, ';');
|
|
char *cut = NULL;
|
|
|
|
if(hash && semi) {
|
|
cut = hash < semi ? hash : semi;
|
|
} else if(hash) {
|
|
cut = hash;
|
|
} else if(semi) {
|
|
cut = semi;
|
|
}
|
|
|
|
if(cut) {
|
|
*cut = '\0';
|
|
rtrim(s);
|
|
}
|
|
}
|
|
|
|
static void
|
|
normalize_domain(const char *input, char *output, size_t output_size) {
|
|
size_t out = 0;
|
|
|
|
if(output_size == 0) {
|
|
return;
|
|
}
|
|
|
|
for(; *input != '\0' && out + 1 < output_size; ++input) {
|
|
output[out++] = (char)tolower((unsigned char)*input);
|
|
}
|
|
|
|
while(out > 0 && output[out - 1] == '.') {
|
|
--out;
|
|
}
|
|
|
|
output[out] = '\0';
|
|
}
|
|
|
|
static int
|
|
parse_int_strict(const char *text, int *value) {
|
|
char *end = NULL;
|
|
long parsed;
|
|
|
|
errno = 0;
|
|
parsed = strtol(text, &end, 10);
|
|
if(errno != 0 || end == text || *end != '\0' || parsed < INT_MIN ||
|
|
parsed > INT_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
*value = (int)parsed;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
config_add_warning(app_config_t *cfg, const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
if(cfg->warning_count >= MAX_CONFIG_WARNINGS) {
|
|
return;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(cfg->warnings[cfg->warning_count].text, MAX_CONFIG_WARNING_LEN, fmt,
|
|
ap);
|
|
va_end(ap);
|
|
++cfg->warning_count;
|
|
}
|
|
|
|
static int
|
|
config_set_bind_address(app_config_t *cfg, const char *ip) {
|
|
if(inet_pton(AF_INET, ip, &cfg->bind_addr) != 1) {
|
|
return -1;
|
|
}
|
|
|
|
snprintf(cfg->bind_text, sizeof(cfg->bind_text), "%s", ip);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
config_set_bind6_address(app_config_t *cfg, const char *ip) {
|
|
if(inet_pton(AF_INET6, ip, &cfg->bind6_addr) != 1) {
|
|
return -1;
|
|
}
|
|
|
|
cfg->bind6_enabled = true;
|
|
snprintf(cfg->bind6_text, sizeof(cfg->bind6_text), "%s", ip);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
config_disable_bind6_address(app_config_t *cfg) {
|
|
cfg->bind6_enabled = false;
|
|
memset(&cfg->bind6_addr, 0, sizeof(cfg->bind6_addr));
|
|
snprintf(cfg->bind6_text, sizeof(cfg->bind6_text), "off");
|
|
}
|
|
|
|
static bool
|
|
config_value_is_disabled(const char *value) {
|
|
return !strcasecmp(value, "off") || !strcasecmp(value, "disabled") ||
|
|
!strcasecmp(value, "none") || !strcasecmp(value, "no") ||
|
|
!strcasecmp(value, "false") || !strcmp(value, "0");
|
|
}
|
|
|
|
static void
|
|
config_set_defaults(app_config_t *cfg) {
|
|
memset(cfg, 0, sizeof(*cfg));
|
|
cfg->timeout_ms = DEFAULT_TIMEOUT_MS;
|
|
cfg->debug_enabled = 0;
|
|
cfg->quiet_enabled = 0;
|
|
(void)config_set_bind_address(cfg, "127.0.0.1");
|
|
(void)config_set_bind6_address(cfg, "::1");
|
|
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", DEFAULT_LOG_PATH);
|
|
}
|
|
|
|
static int
|
|
config_add_upstream(app_config_t *cfg, const char *ip) {
|
|
upstream_t *upstream;
|
|
|
|
if(cfg->upstream_count >= MAX_UPSTREAMS) {
|
|
return -1;
|
|
}
|
|
|
|
upstream = &cfg->upstreams[cfg->upstream_count];
|
|
if(inet_pton(AF_INET, ip, &upstream->addr) != 1) {
|
|
return -1;
|
|
}
|
|
|
|
snprintf(upstream->text, sizeof(upstream->text), "%s", ip);
|
|
++cfg->upstream_count;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
config_add_rule(app_config_t *cfg, const char *mask, const char *ip) {
|
|
override_rule_t *rule;
|
|
|
|
if(cfg->rule_count >= MAX_RULES) {
|
|
return -1;
|
|
}
|
|
|
|
rule = &cfg->rules[cfg->rule_count];
|
|
if(inet_pton(AF_INET, ip, &rule->addr) != 1) {
|
|
return -2;
|
|
}
|
|
|
|
snprintf(rule->mask, sizeof(rule->mask), "%s", mask);
|
|
normalize_domain(rule->mask, rule->mask, sizeof(rule->mask));
|
|
snprintf(rule->text, sizeof(rule->text), "%s", ip);
|
|
++cfg->rule_count;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
config_add_exception(app_config_t *cfg, const char *mask) {
|
|
exception_rule_t *rule;
|
|
|
|
if(cfg->exception_count >= MAX_EXCEPTIONS) {
|
|
return;
|
|
}
|
|
|
|
rule = &cfg->exceptions[cfg->exception_count];
|
|
snprintf(rule->mask, sizeof(rule->mask), "%s", mask);
|
|
normalize_domain(rule->mask, rule->mask, sizeof(rule->mask));
|
|
++cfg->exception_count;
|
|
}
|
|
|
|
static void
|
|
config_apply_builtin_exceptions(app_config_t *cfg) {
|
|
cfg->exception_count = 0;
|
|
config_add_exception(cfg, "feature.api.playstation.com");
|
|
config_add_exception(cfg, "*.stun.playstation.net");
|
|
config_add_exception(cfg, "stun.*.playstation.net");
|
|
config_add_exception(cfg, "ena.net.playstation.net");
|
|
config_add_exception(cfg, "post.net.playstation.net");
|
|
config_add_exception(cfg, "gst.prod.dl.playstation.net");
|
|
}
|
|
|
|
static void
|
|
config_apply_builtin_upstreams(app_config_t *cfg) {
|
|
cfg->upstream_count = 0;
|
|
(void)config_add_upstream(cfg, "1.1.1.1");
|
|
(void)config_add_upstream(cfg, "8.8.8.8");
|
|
(void)config_add_upstream(cfg, "77.88.8.8");
|
|
}
|
|
|
|
static int
|
|
config_apply_builtin_overrides(app_config_t *cfg) {
|
|
cfg->rule_count = 0;
|
|
|
|
if(config_add_rule(cfg, "*.playstation.com", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "*.playstation.com.*", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "playstation.com", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "*.playstation.net", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "*.playstation.net.*", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "*.psndl.net", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "playstation.net", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
if(config_add_rule(cfg, "psndl.net", "0.0.0.0") != 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(PLATFORM_PS5)
|
|
static int
|
|
find_pid(const char *name) {
|
|
int mib[4] = {1, 14, 8, 0};
|
|
pid_t mypid = getpid();
|
|
pid_t pid = -1;
|
|
size_t buf_size;
|
|
uint8_t *buf;
|
|
|
|
if(sysctl(mib, 4, NULL, &buf_size, NULL, 0) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
buf = malloc(buf_size);
|
|
if(buf == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if(sysctl(mib, 4, buf, &buf_size, NULL, 0) != 0) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
for(uint8_t *ptr = buf; ptr < (buf + buf_size);) {
|
|
int ki_structsize = *(int *)ptr;
|
|
pid_t ki_pid = *(pid_t *)&ptr[KI_PID_OFFSET];
|
|
char *ki_tdname = (char *)&ptr[KI_TDNAME_OFFSET];
|
|
|
|
ptr += ki_structsize;
|
|
if(!strcmp(name, ki_tdname) && ki_pid != mypid) {
|
|
pid = ki_pid;
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
return pid;
|
|
}
|
|
|
|
static int
|
|
terminate_existing_instances(const char *name) {
|
|
pid_t pid;
|
|
|
|
while((pid = find_pid(name)) > 0) {
|
|
log_printf("[nanodns] terminating previous instance pid=%d name=%s\n", pid,
|
|
name);
|
|
if(kill(pid, SIGKILL) != 0) {
|
|
log_errno("kill(previous instance)");
|
|
return -1;
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
ensure_runtime_dir_exists(const char *path) {
|
|
struct stat st;
|
|
|
|
if(stat(path, &st) == 0) {
|
|
if(S_ISDIR(st.st_mode)) {
|
|
return 0;
|
|
}
|
|
|
|
errno = ENOTDIR;
|
|
return -1;
|
|
}
|
|
|
|
if(mkdir(path, 0777) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
ensure_default_config_exists(const char *path) {
|
|
struct stat st;
|
|
FILE *fp;
|
|
|
|
if(stat(path, &st) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
fp = fopen(path, "w");
|
|
if(fp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if(fputs(k_default_config, fp) == EOF) {
|
|
fclose(fp);
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
if(fclose(fp) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
load_config(const char *path, app_config_t *cfg) {
|
|
enum {
|
|
SECTION_NONE = 0,
|
|
SECTION_GENERAL,
|
|
SECTION_UPSTREAM,
|
|
SECTION_OVERRIDES,
|
|
SECTION_EXCEPTIONS,
|
|
} section = SECTION_NONE;
|
|
FILE *fp;
|
|
char line[512];
|
|
size_t line_no = 0;
|
|
bool replace_upstreams = false;
|
|
|
|
config_set_defaults(cfg);
|
|
config_apply_builtin_upstreams(cfg);
|
|
|
|
fp = fopen(path, "r");
|
|
if(fp == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
cfg->rule_count = 0;
|
|
cfg->exception_count = 0;
|
|
|
|
while(fgets(line, sizeof(line), fp) != NULL) {
|
|
char *s;
|
|
char *eq;
|
|
char *key;
|
|
char *value;
|
|
struct in_addr addr;
|
|
|
|
++line_no;
|
|
|
|
s = trim(line);
|
|
|
|
if(*s == '\0' || *s == '#' || *s == ';') {
|
|
continue;
|
|
}
|
|
|
|
if(*s == '[') {
|
|
char *end = strchr(s, ']');
|
|
if(end == NULL) {
|
|
continue;
|
|
}
|
|
|
|
*end = '\0';
|
|
s = trim(s + 1);
|
|
|
|
if(!strcasecmp(s, "general") || !strcasecmp(s, "settings")) {
|
|
section = SECTION_GENERAL;
|
|
} else if(!strcasecmp(s, "upstream") || !strcasecmp(s, "upstreams")) {
|
|
section = SECTION_UPSTREAM;
|
|
} else if(!strcasecmp(s, "override") || !strcasecmp(s, "overrides")) {
|
|
section = SECTION_OVERRIDES;
|
|
} else if(!strcasecmp(s, "exception") || !strcasecmp(s, "exceptions")) {
|
|
section = SECTION_EXCEPTIONS;
|
|
} else {
|
|
section = SECTION_NONE;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
strip_inline_comment(s);
|
|
if(*s == '\0') {
|
|
continue;
|
|
}
|
|
|
|
if(section == SECTION_EXCEPTIONS) {
|
|
eq = strchr(s, '=');
|
|
if(eq != NULL) {
|
|
*eq = '\0';
|
|
}
|
|
|
|
key = trim(s);
|
|
if(*key != '\0') {
|
|
size_t before = cfg->exception_count;
|
|
config_add_exception(cfg, key);
|
|
if(cfg->exception_count == before) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu too many exception rules, ignoring '%s'\n",
|
|
path, line_no, key);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
eq = strchr(s, '=');
|
|
if(eq == NULL) {
|
|
continue;
|
|
}
|
|
|
|
*eq = '\0';
|
|
key = trim(s);
|
|
value = trim(eq + 1);
|
|
|
|
if(*key == '\0' || *value == '\0') {
|
|
continue;
|
|
}
|
|
|
|
if(section == SECTION_GENERAL ||
|
|
(section == SECTION_NONE &&
|
|
(!strcasecmp(key, "log") || !strcasecmp(key, "debug") ||
|
|
!strcasecmp(key, "quiet") || !strcasecmp(key, "bind") ||
|
|
!strcasecmp(key, "bind6")))) {
|
|
if(!strcasecmp(key, "log")) {
|
|
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", value);
|
|
} else if(!strcasecmp(key, "debug")) {
|
|
int parsed_debug;
|
|
|
|
if(parse_int_strict(value, &parsed_debug) == 0) {
|
|
cfg->debug_enabled = parsed_debug != 0 ? 1 : 0;
|
|
} else {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid debug value '%s', keeping %s\n",
|
|
path, line_no, value,
|
|
cfg->debug_enabled ? "enabled" : "disabled");
|
|
}
|
|
} else if(!strcasecmp(key, "quiet")) {
|
|
int parsed_quiet;
|
|
|
|
if(parse_int_strict(value, &parsed_quiet) == 0) {
|
|
cfg->quiet_enabled = parsed_quiet != 0 ? 1 : 0;
|
|
} else {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid quiet value '%s', keeping %s\n",
|
|
path, line_no, value,
|
|
cfg->quiet_enabled ? "enabled" : "disabled");
|
|
}
|
|
} else if(!strcasecmp(key, "bind")) {
|
|
if(config_set_bind_address(cfg, value) != 0) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid bind address '%s', keeping %s\n",
|
|
path, line_no, value, cfg->bind_text);
|
|
}
|
|
} else if(!strcasecmp(key, "bind6")) {
|
|
if(config_value_is_disabled(value)) {
|
|
config_disable_bind6_address(cfg);
|
|
} else if(config_set_bind6_address(cfg, value) != 0) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid bind6 address '%s', keeping %s\n",
|
|
path, line_no, value, cfg->bind6_text);
|
|
}
|
|
}
|
|
} else if(section == SECTION_UPSTREAM) {
|
|
if(!strcasecmp(key, "server") || !strcasecmp(key, "dns")) {
|
|
if(!replace_upstreams) {
|
|
cfg->upstream_count = 0;
|
|
replace_upstreams = true;
|
|
}
|
|
|
|
if(inet_pton(AF_INET, value, &addr) != 1) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid upstream server '%s', ignoring\n",
|
|
path, line_no, value);
|
|
} else if(config_add_upstream(cfg, value) != 0) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu too many upstream servers, ignoring '%s'\n",
|
|
path, line_no, value);
|
|
}
|
|
} else if(!strcasecmp(key, "timeout_ms")) {
|
|
int parsed_timeout;
|
|
|
|
if(parse_int_strict(value, &parsed_timeout) == 0 &&
|
|
parsed_timeout > 0) {
|
|
cfg->timeout_ms = parsed_timeout;
|
|
} else {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid timeout_ms '%s', using %d\n",
|
|
path, line_no, value, DEFAULT_TIMEOUT_MS);
|
|
cfg->timeout_ms = DEFAULT_TIMEOUT_MS;
|
|
}
|
|
}
|
|
} else if(section == SECTION_OVERRIDES) {
|
|
int rc = config_add_rule(cfg, key, value);
|
|
if(rc == -2) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu invalid override IPv4 '%s' for mask '%s', ignoring\n",
|
|
path, line_no, value, key);
|
|
} else if(rc != 0) {
|
|
config_add_warning(cfg,
|
|
"[nanodns] warning: %s:%zu too many override rules, ignoring '%s=%s'\n",
|
|
path, line_no, key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
if(cfg->upstream_count == 0) {
|
|
config_apply_builtin_upstreams(cfg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
print_banner(void) {
|
|
log_printf("============================================================\n");
|
|
log_printf("%s v%s\n", APP_NAME, APP_VERSION);
|
|
log_printf("Build: %s %s\n", __DATE__, __TIME__);
|
|
log_printf("%s\n", APP_COPYRIGHT);
|
|
log_printf("============================================================\n");
|
|
}
|
|
|
|
static int
|
|
send_startup_notification(const app_config_t *cfg, bool ipv4_listening,
|
|
bool ipv6_listening) {
|
|
notify_request_t req;
|
|
int rc;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
if(ipv4_listening && ipv6_listening) {
|
|
rc = snprintf(req.message, sizeof(req.message),
|
|
"%s v%s %s\nListening on %s:%d and [%s]:%d", APP_NAME,
|
|
APP_VERSION, APP_COPYRIGHT, cfg->bind_text, DNS_PORT,
|
|
cfg->bind6_text, DNS_PORT);
|
|
} else if(ipv6_listening) {
|
|
rc = snprintf(req.message, sizeof(req.message),
|
|
"%s v%s %s\nListening on [%s]:%d", APP_NAME, APP_VERSION,
|
|
APP_COPYRIGHT, cfg->bind6_text, DNS_PORT);
|
|
} else if(ipv4_listening) {
|
|
rc = snprintf(req.message, sizeof(req.message),
|
|
"%s v%s %s\nListening on %s:%d", APP_NAME, APP_VERSION,
|
|
APP_COPYRIGHT, cfg->bind_text, DNS_PORT);
|
|
} else {
|
|
rc = snprintf(req.message, sizeof(req.message),
|
|
"%s v%s %s\nNo DNS listener active", APP_NAME, APP_VERSION,
|
|
APP_COPYRIGHT);
|
|
}
|
|
if(rc < 0 || (size_t)rc >= sizeof(req.message)) {
|
|
log_printf("[nanodns] failed to build startup notification message\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
|
|
if(rc != 0) {
|
|
log_printf("[nanodns] sceKernelSendNotificationRequest failed: %d\n", rc);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
elevate_privileges(void) {
|
|
pid_t pid = getpid();
|
|
|
|
#if defined(PLATFORM_PS4)
|
|
(void)pid;
|
|
return 0;
|
|
#else
|
|
if(kernel_set_ucred_authid(pid, PRIVILEGED_AUTHID) != 0) {
|
|
log_printf("[nanodns] unable to change AuthID for pid %d\n", pid);
|
|
return -1;
|
|
}
|
|
|
|
log_printf("[nanodns] AuthID updated for pid %d\n", pid);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int64_t
|
|
now_ms(void) {
|
|
struct timeval tv;
|
|
|
|
if(gettimeofday(&tv, NULL) != 0) {
|
|
return 0;
|
|
}
|
|
|
|
return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
}
|
|
|
|
static int
|
|
dns_expand_name(const uint8_t *packet, size_t packet_len, size_t *offset,
|
|
char *output, size_t output_len) {
|
|
size_t cursor = *offset;
|
|
size_t end_offset = (size_t)-1;
|
|
size_t out = 0;
|
|
int jumps = 0;
|
|
|
|
if(output_len == 0) {
|
|
return -1;
|
|
}
|
|
|
|
while(cursor < packet_len) {
|
|
uint8_t len = packet[cursor];
|
|
|
|
if((len & 0xc0) == 0xc0) {
|
|
uint16_t ptr;
|
|
|
|
if(cursor + 1 >= packet_len) {
|
|
return -1;
|
|
}
|
|
|
|
ptr = (uint16_t)(((len & 0x3f) << 8) | packet[cursor + 1]);
|
|
if(ptr >= packet_len || ++jumps > 16) {
|
|
return -1;
|
|
}
|
|
|
|
if(end_offset == (size_t)-1) {
|
|
end_offset = cursor + 2;
|
|
}
|
|
|
|
cursor = ptr;
|
|
continue;
|
|
}
|
|
|
|
if((len & 0xc0) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
++cursor;
|
|
if(len == 0) {
|
|
output[out] = '\0';
|
|
*offset = end_offset == (size_t)-1 ? cursor : end_offset;
|
|
return 0;
|
|
}
|
|
|
|
if(cursor + len > packet_len) {
|
|
return -1;
|
|
}
|
|
|
|
if(out != 0) {
|
|
if(out + 1 >= output_len) {
|
|
return -1;
|
|
}
|
|
output[out++] = '.';
|
|
}
|
|
|
|
if(out + len >= output_len) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(&output[out], &packet[cursor], len);
|
|
out += len;
|
|
cursor += len;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
dns_parse_question(const uint8_t *packet, size_t packet_len, dns_question_t *q) {
|
|
size_t offset = 12;
|
|
char normalized[MAX_DOMAIN_LEN];
|
|
|
|
if(packet_len < 12) {
|
|
return -1;
|
|
}
|
|
|
|
memset(q, 0, sizeof(*q));
|
|
q->id = read_u16(&packet[0]);
|
|
q->flags = read_u16(&packet[2]);
|
|
q->qdcount = read_u16(&packet[4]);
|
|
q->ancount = read_u16(&packet[6]);
|
|
q->nscount = read_u16(&packet[8]);
|
|
q->arcount = read_u16(&packet[10]);
|
|
q->qname_offset = offset;
|
|
|
|
if(q->qdcount == 0) {
|
|
return -1;
|
|
}
|
|
|
|
if(dns_expand_name(packet, packet_len, &offset, q->qname, sizeof(q->qname)) !=
|
|
0) {
|
|
return -1;
|
|
}
|
|
|
|
if(offset + 4 > packet_len) {
|
|
return -1;
|
|
}
|
|
|
|
q->qtype = read_u16(&packet[offset]);
|
|
q->qclass = read_u16(&packet[offset + 2]);
|
|
q->question_end = offset + 4;
|
|
|
|
normalize_domain(q->qname, normalized, sizeof(normalized));
|
|
snprintf(q->qname, sizeof(q->qname), "%s", normalized);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
domain_mask_match(const char *mask, const char *domain) {
|
|
while(*mask != '\0') {
|
|
if(*mask == '*') {
|
|
do {
|
|
++mask;
|
|
} while(*mask == '*');
|
|
|
|
if(*mask == '\0') {
|
|
return 1;
|
|
}
|
|
|
|
while(*domain != '\0') {
|
|
if(domain_mask_match(mask, domain)) {
|
|
return 1;
|
|
}
|
|
++domain;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if(*domain == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
if(*mask == '?') {
|
|
++mask;
|
|
++domain;
|
|
continue;
|
|
}
|
|
|
|
if(*mask == '[') {
|
|
bool negate = false;
|
|
bool matched = false;
|
|
const char *p = mask + 1;
|
|
|
|
if(*p == '!' || *p == '^') {
|
|
negate = true;
|
|
++p;
|
|
}
|
|
|
|
if(*p == ']') {
|
|
matched = *domain == ']';
|
|
++p;
|
|
}
|
|
|
|
while(*p != '\0' && *p != ']') {
|
|
unsigned char start;
|
|
unsigned char end;
|
|
|
|
if(*p == '\\' && p[1] != '\0') {
|
|
start = (unsigned char)p[1];
|
|
p += 2;
|
|
} else {
|
|
start = (unsigned char)*p++;
|
|
}
|
|
|
|
end = start;
|
|
if(*p == '-' && p[1] != '\0' && p[1] != ']') {
|
|
++p;
|
|
if(*p == '\\' && p[1] != '\0') {
|
|
end = (unsigned char)p[1];
|
|
p += 2;
|
|
} else {
|
|
end = (unsigned char)*p++;
|
|
}
|
|
}
|
|
|
|
if((unsigned char)*domain >= start && (unsigned char)*domain <= end) {
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
if(*p != ']') {
|
|
if(*mask != *domain) {
|
|
return 0;
|
|
}
|
|
|
|
++mask;
|
|
++domain;
|
|
continue;
|
|
}
|
|
|
|
if(negate ? matched : !matched) {
|
|
return 0;
|
|
}
|
|
|
|
mask = p + 1;
|
|
++domain;
|
|
continue;
|
|
}
|
|
|
|
if(*mask == '\\' && mask[1] != '\0') {
|
|
++mask;
|
|
}
|
|
|
|
if(*mask != *domain) {
|
|
return 0;
|
|
}
|
|
|
|
++mask;
|
|
++domain;
|
|
}
|
|
|
|
return *domain == '\0';
|
|
}
|
|
|
|
static const override_rule_t *
|
|
find_matching_rule(const app_config_t *cfg, const char *domain) {
|
|
for(size_t i = 0; i < cfg->rule_count; ++i) {
|
|
if(domain_mask_match(cfg->rules[i].mask, domain)) {
|
|
return &cfg->rules[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
has_matching_exception(const app_config_t *cfg, const char *domain) {
|
|
for(size_t i = 0; i < cfg->exception_count; ++i) {
|
|
if(domain_mask_match(cfg->exceptions[i].mask, domain)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
build_error_response(const uint8_t *request, size_t request_len,
|
|
const dns_question_t *question, uint16_t rcode,
|
|
uint8_t *response, size_t response_cap,
|
|
size_t *response_len) {
|
|
size_t question_len;
|
|
|
|
if(response_cap < question->question_end) {
|
|
return -1;
|
|
}
|
|
|
|
question_len = question->question_end - 12;
|
|
memcpy(response, request, question->question_end);
|
|
write_u16(&response[2],
|
|
(uint16_t)(0x8000 | (question->flags & 0x0100) | 0x0080 |
|
|
(rcode & 0x000f)));
|
|
write_u16(&response[4], 1);
|
|
write_u16(&response[6], 0);
|
|
write_u16(&response[8], 0);
|
|
write_u16(&response[10], 0);
|
|
*response_len = 12 + question_len;
|
|
|
|
(void)request_len;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
build_nodata_response(const uint8_t *request, size_t request_len,
|
|
const dns_question_t *question, uint8_t *response,
|
|
size_t response_cap, size_t *response_len) {
|
|
return build_error_response(request, request_len, question, 0, response,
|
|
response_cap, response_len);
|
|
}
|
|
|
|
static int
|
|
build_override_response(const uint8_t *request, const dns_question_t *question,
|
|
const struct in_addr *addr, uint8_t *response,
|
|
size_t response_cap, size_t *response_len) {
|
|
size_t question_len = question->question_end - 12;
|
|
size_t offset;
|
|
|
|
if(response_cap < question->question_end + 16) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(response, request, question->question_end);
|
|
write_u16(&response[2],
|
|
(uint16_t)(0x8000 | (question->flags & 0x0100) | 0x0080));
|
|
write_u16(&response[4], 1);
|
|
write_u16(&response[6], 1);
|
|
write_u16(&response[8], 0);
|
|
write_u16(&response[10], 0);
|
|
|
|
offset = 12 + question_len;
|
|
write_u16(&response[offset], 0xc00c);
|
|
offset += 2;
|
|
write_u16(&response[offset], 1);
|
|
offset += 2;
|
|
write_u16(&response[offset], 1);
|
|
offset += 2;
|
|
write_u32(&response[offset], OVERRIDE_TTL);
|
|
offset += 4;
|
|
write_u16(&response[offset], 4);
|
|
offset += 2;
|
|
memcpy(&response[offset], &addr->s_addr, 4);
|
|
offset += 4;
|
|
|
|
*response_len = offset;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
format_sockaddr(const struct sockaddr *addr, char *ip, size_t ip_len,
|
|
uint16_t *port) {
|
|
if(addr->sa_family == AF_INET) {
|
|
const struct sockaddr_in *sin = (const struct sockaddr_in *)addr;
|
|
|
|
if(inet_ntop(AF_INET, &sin->sin_addr, ip, ip_len) == NULL) {
|
|
snprintf(ip, ip_len, "<invalid>");
|
|
}
|
|
*port = ntohs(sin->sin_port);
|
|
} else if(addr->sa_family == AF_INET6) {
|
|
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)addr;
|
|
|
|
if(inet_ntop(AF_INET6, &sin6->sin6_addr, ip, ip_len) == NULL) {
|
|
snprintf(ip, ip_len, "<invalid>");
|
|
}
|
|
*port = ntohs(sin6->sin6_port);
|
|
} else {
|
|
snprintf(ip, ip_len, "<unknown>");
|
|
*port = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
log_dns_query(const dns_question_t *question, const struct sockaddr *client) {
|
|
char client_ip[INET6_ADDRSTRLEN];
|
|
uint16_t client_port;
|
|
|
|
format_sockaddr(client, client_ip, sizeof(client_ip), &client_port);
|
|
|
|
log_printf("[nanodns] query from=%s%s%s:%u id=0x%04x qname=%s qtype=%s(%u) qclass=%u\n",
|
|
client->sa_family == AF_INET6 ? "[" : "", client_ip,
|
|
client->sa_family == AF_INET6 ? "]" : "", client_port,
|
|
question->id, question->qname[0] ? question->qname : ".",
|
|
dns_type_to_string(question->qtype), question->qtype,
|
|
question->qclass);
|
|
}
|
|
|
|
static void
|
|
log_answer_record(const uint8_t *packet, size_t packet_len, size_t *offset,
|
|
size_t index) {
|
|
char name[MAX_DOMAIN_LEN];
|
|
char target[MAX_DOMAIN_LEN];
|
|
uint16_t type;
|
|
uint16_t klass;
|
|
uint16_t rdlength;
|
|
uint32_t ttl;
|
|
size_t rdata_offset;
|
|
char ipbuf[INET6_ADDRSTRLEN];
|
|
|
|
if(dns_expand_name(packet, packet_len, offset, name, sizeof(name)) != 0) {
|
|
log_printf("[nanodns] answer[%zu] <invalid owner name>\n", index);
|
|
*offset = packet_len;
|
|
return;
|
|
}
|
|
|
|
if(*offset + 10 > packet_len) {
|
|
log_printf("[nanodns] answer[%zu] <truncated rr header>\n", index);
|
|
*offset = packet_len;
|
|
return;
|
|
}
|
|
|
|
type = read_u16(&packet[*offset]);
|
|
klass = read_u16(&packet[*offset + 2]);
|
|
ttl = read_u32(&packet[*offset + 4]);
|
|
rdlength = read_u16(&packet[*offset + 8]);
|
|
*offset += 10;
|
|
|
|
if(*offset + rdlength > packet_len) {
|
|
log_printf("[nanodns] answer[%zu] <truncated rr data>\n", index);
|
|
*offset = packet_len;
|
|
return;
|
|
}
|
|
|
|
rdata_offset = *offset;
|
|
|
|
if(type == 1 && rdlength == 4 &&
|
|
inet_ntop(AF_INET, &packet[rdata_offset], ipbuf, sizeof(ipbuf)) != NULL) {
|
|
log_printf("[nanodns] answer[%zu] name=%s type=A ttl=%u class=%u data=%s\n",
|
|
index, name[0] ? name : ".", ttl, klass, ipbuf);
|
|
} else if(type == 28 && rdlength == 16 &&
|
|
inet_ntop(AF_INET6, &packet[rdata_offset], ipbuf,
|
|
sizeof(ipbuf)) != NULL) {
|
|
log_printf("[nanodns] answer[%zu] name=%s type=AAAA ttl=%u class=%u data=%s\n",
|
|
index, name[0] ? name : ".", ttl, klass, ipbuf);
|
|
} else if((type == 2 || type == 5 || type == 12) &&
|
|
dns_expand_name(packet, packet_len, &rdata_offset, target,
|
|
sizeof(target)) == 0) {
|
|
log_printf("[nanodns] answer[%zu] name=%s type=%s ttl=%u class=%u data=%s\n",
|
|
index, name[0] ? name : ".", dns_type_to_string(type), ttl, klass,
|
|
target[0] ? target : ".");
|
|
} else {
|
|
log_printf("[nanodns] answer[%zu] name=%s type=%s(%u) ttl=%u class=%u rdlen=%u\n",
|
|
index, name[0] ? name : ".", dns_type_to_string(type), type, ttl,
|
|
klass, rdlength);
|
|
}
|
|
|
|
*offset += rdlength;
|
|
}
|
|
|
|
static void
|
|
log_dns_response(const uint8_t *packet, size_t packet_len, const char *via) {
|
|
dns_question_t question;
|
|
uint16_t flags;
|
|
uint16_t rcode;
|
|
size_t offset;
|
|
|
|
if(dns_parse_question(packet, packet_len, &question) != 0) {
|
|
log_printf("[nanodns] response via=%s <failed to parse>\n", via);
|
|
return;
|
|
}
|
|
|
|
flags = read_u16(&packet[2]);
|
|
rcode = (uint16_t)(flags & 0x000f);
|
|
|
|
log_printf("[nanodns] response via=%s id=0x%04x qname=%s qtype=%s(%u) rcode=%s(%u) answers=%u authority=%u additional=%u\n",
|
|
via, question.id, question.qname[0] ? question.qname : ".",
|
|
dns_type_to_string(question.qtype), question.qtype,
|
|
dns_rcode_to_string(rcode), rcode, read_u16(&packet[6]),
|
|
read_u16(&packet[8]), read_u16(&packet[10]));
|
|
|
|
offset = question.question_end;
|
|
for(size_t i = 0; i < question.ancount && offset < packet_len; ++i) {
|
|
log_answer_record(packet, packet_len, &offset, i);
|
|
}
|
|
}
|
|
|
|
static int
|
|
open_upstream_socket(const upstream_t *upstream) {
|
|
struct sockaddr_in upstream_addr;
|
|
int fd;
|
|
|
|
memset(&upstream_addr, 0, sizeof(upstream_addr));
|
|
upstream_addr.sin_family = AF_INET;
|
|
upstream_addr.sin_port = htons(DNS_PORT);
|
|
upstream_addr.sin_addr = upstream->addr;
|
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if(fd < 0) {
|
|
log_printf("[nanodns] upstream %s socket failed: %s\n", upstream->text,
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(connect(fd, (struct sockaddr *)&upstream_addr, sizeof(upstream_addr)) !=
|
|
0) {
|
|
log_printf("[nanodns] upstream %s connect failed: %s\n", upstream->text,
|
|
strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
close_upstream_socket(int *fd) {
|
|
if(*fd >= 0) {
|
|
close(*fd);
|
|
*fd = -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
close_upstream_sockets(int *fds, size_t count) {
|
|
for(size_t i = 0; i < count; ++i) {
|
|
close_upstream_socket(&fds[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_server_listener(server_listener_t *listener, int family,
|
|
const char *name, bool retry_enabled) {
|
|
listener->fd = -1;
|
|
listener->family = family;
|
|
listener->name = name;
|
|
listener->retry_enabled = retry_enabled;
|
|
listener->retry_at_ms = 0;
|
|
}
|
|
|
|
static void
|
|
invalidate_server_listener(server_listener_t *listener) {
|
|
if(listener->fd >= 0) {
|
|
close(listener->fd);
|
|
listener->fd = -1;
|
|
}
|
|
listener->retry_at_ms = now_ms() + LISTENER_RETRY_MS;
|
|
}
|
|
|
|
static void
|
|
log_server_listener_addr(const app_config_t *cfg, const server_listener_t *listener,
|
|
const char *prefix) {
|
|
if(listener->family == AF_INET6) {
|
|
log_printf("[nanodns] %s [%s]:%d\n", prefix, cfg->bind6_text, DNS_PORT);
|
|
} else {
|
|
log_printf("[nanodns] %s %s:%d\n", prefix, cfg->bind_text, DNS_PORT);
|
|
}
|
|
}
|
|
|
|
static int
|
|
open_server_socket4(const app_config_t *cfg) {
|
|
struct sockaddr_in listen_addr;
|
|
int fd;
|
|
int reuse = 1;
|
|
|
|
fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if(fd < 0) {
|
|
log_errno("socket(server-ipv4)");
|
|
return -1;
|
|
}
|
|
|
|
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0) {
|
|
log_errno("setsockopt(SO_REUSEADDR)");
|
|
}
|
|
|
|
memset(&listen_addr, 0, sizeof(listen_addr));
|
|
listen_addr.sin_family = AF_INET;
|
|
listen_addr.sin_port = htons(DNS_PORT);
|
|
listen_addr.sin_addr = cfg->bind_addr;
|
|
|
|
if(bind(fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) != 0) {
|
|
log_printf("[nanodns] bind(%s:%d) failed: %s\n", cfg->bind_text, DNS_PORT,
|
|
strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
open_server_socket6(const app_config_t *cfg) {
|
|
struct sockaddr_in6 listen_addr;
|
|
int fd;
|
|
int reuse = 1;
|
|
|
|
if(!cfg->bind6_enabled) {
|
|
return -1;
|
|
}
|
|
|
|
fd = socket(AF_INET6, SOCK_DGRAM, 0);
|
|
if(fd < 0) {
|
|
log_printf("[nanodns] warning: IPv6 listener unavailable: %s\n",
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0) {
|
|
log_printf("[nanodns] warning: setsockopt(SO_REUSEADDR, ipv6): %s\n",
|
|
strerror(errno));
|
|
}
|
|
|
|
#ifdef IPV6_V6ONLY
|
|
if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &reuse, sizeof(reuse)) != 0) {
|
|
log_printf("[nanodns] warning: setsockopt(IPV6_V6ONLY): %s\n",
|
|
strerror(errno));
|
|
}
|
|
#endif
|
|
|
|
memset(&listen_addr, 0, sizeof(listen_addr));
|
|
listen_addr.sin6_family = AF_INET6;
|
|
listen_addr.sin6_port = htons(DNS_PORT);
|
|
listen_addr.sin6_addr = cfg->bind6_addr;
|
|
|
|
if(bind(fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) != 0) {
|
|
log_printf("[nanodns] warning: IPv6 bind([%s]:%d) failed: %s\n",
|
|
cfg->bind6_text, DNS_PORT, strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
open_server_listener(const app_config_t *cfg, server_listener_t *listener) {
|
|
int fd;
|
|
|
|
fd = listener->family == AF_INET6 ? open_server_socket6(cfg) :
|
|
open_server_socket4(cfg);
|
|
if(fd < 0) {
|
|
if(listener->retry_enabled) {
|
|
listener->retry_at_ms = now_ms() + LISTENER_RETRY_MS;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
listener->fd = fd;
|
|
listener->retry_enabled = true;
|
|
listener->retry_at_ms = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
drain_upstream_socket(int fd) {
|
|
uint8_t discard[MAX_DNS_PACKET];
|
|
struct pollfd pfd;
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
|
|
while(true) {
|
|
ssize_t nread;
|
|
|
|
pfd.revents = 0;
|
|
if(poll(&pfd, 1, 0) <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
if((pfd.revents & POLLIN) == 0) {
|
|
return (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) == 0 ? 0 : -1;
|
|
}
|
|
|
|
nread = recv(fd, discard, sizeof(discard), 0);
|
|
if(nread <= 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
server_socket_needs_reopen(int err) {
|
|
switch(err) {
|
|
case EBADF:
|
|
case EINVAL:
|
|
case ENOTSOCK:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
forward_query_to_upstream(const app_config_t *cfg, const uint8_t *request,
|
|
size_t request_len, uint16_t request_id,
|
|
int *upstream_fds,
|
|
uint8_t *response, size_t response_cap,
|
|
size_t *response_len, char *via, size_t via_len) {
|
|
int64_t deadline_ms;
|
|
|
|
deadline_ms = now_ms() + cfg->timeout_ms;
|
|
|
|
for(size_t i = 0; i < cfg->upstream_count; ++i) {
|
|
struct pollfd pfd;
|
|
int fd;
|
|
int poll_rc;
|
|
|
|
fd = upstream_fds[i];
|
|
if(fd < 0) {
|
|
fd = open_upstream_socket(&cfg->upstreams[i]);
|
|
if(fd < 0) {
|
|
continue;
|
|
}
|
|
|
|
upstream_fds[i] = fd;
|
|
}
|
|
|
|
if(drain_upstream_socket(fd) != 0) {
|
|
log_printf("[nanodns] upstream %s drain failed, reconnecting\n",
|
|
cfg->upstreams[i].text);
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
fd = open_upstream_socket(&cfg->upstreams[i]);
|
|
if(fd < 0) {
|
|
continue;
|
|
}
|
|
|
|
upstream_fds[i] = fd;
|
|
}
|
|
|
|
if(send(fd, request, request_len, 0) < 0) {
|
|
log_printf("[nanodns] upstream %s send failed: %s\n",
|
|
cfg->upstreams[i].text, strerror(errno));
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
continue;
|
|
}
|
|
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
pfd.revents = 0;
|
|
|
|
while(true) {
|
|
int64_t timeout_ms = deadline_ms - now_ms();
|
|
|
|
if(timeout_ms <= 0) {
|
|
log_printf("[nanodns] upstream %s timed out\n", cfg->upstreams[i].text);
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
}
|
|
|
|
poll_rc = poll(&pfd, 1, (int)timeout_ms);
|
|
if(poll_rc == 0) {
|
|
log_printf("[nanodns] upstream %s timed out\n",
|
|
cfg->upstreams[i].text);
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
}
|
|
|
|
if(poll_rc < 0) {
|
|
if(errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
log_printf("[nanodns] upstream %s poll failed: %s\n",
|
|
cfg->upstreams[i].text, strerror(errno));
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
}
|
|
|
|
if((pfd.revents & POLLIN) != 0) {
|
|
ssize_t nread = recv(fd, response, response_cap, 0);
|
|
if(nread < 0) {
|
|
log_printf("[nanodns] upstream %s recv failed: %s\n",
|
|
cfg->upstreams[i].text, strerror(errno));
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
} else if((size_t)nread < 12) {
|
|
log_printf("[nanodns] upstream %s returned short packet (%zd bytes)\n",
|
|
cfg->upstreams[i].text, nread);
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
} else if(read_u16(response) != request_id) {
|
|
log_printf("[nanodns] upstream %s returned mismatched id 0x%04x\n",
|
|
cfg->upstreams[i].text, read_u16(response));
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
} else {
|
|
*response_len = (size_t)nread;
|
|
snprintf(via, via_len, "%s", cfg->upstreams[i].text);
|
|
return 0;
|
|
}
|
|
} else if((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
|
|
log_printf("[nanodns] upstream %s poll events=0x%x\n",
|
|
cfg->upstreams[i].text, pfd.revents);
|
|
close_upstream_socket(&upstream_fds[i]);
|
|
break;
|
|
}
|
|
|
|
pfd.revents = 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
send_dns_response_to_client(int server_fd, const uint8_t *response,
|
|
size_t response_len,
|
|
const struct sockaddr *client_addr,
|
|
socklen_t client_len, const char *via,
|
|
const char *error_context) {
|
|
if(sendto(server_fd, response, response_len, 0, client_addr, client_len) <
|
|
0) {
|
|
log_errno(error_context);
|
|
} else {
|
|
log_dns_response(response, response_len, via);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_dns_request(const app_config_t *cfg, int server_fd, int *upstream_fds,
|
|
uint8_t *request, size_t request_len,
|
|
const struct sockaddr *client_addr, socklen_t client_len) {
|
|
uint8_t response[MAX_DNS_PACKET];
|
|
dns_question_t question;
|
|
size_t response_len = 0;
|
|
|
|
if(dns_parse_question(request, request_len, &question) != 0) {
|
|
log_printf("[nanodns] received malformed DNS packet (%zu bytes)\n",
|
|
request_len);
|
|
return;
|
|
}
|
|
|
|
log_dns_query(&question, client_addr);
|
|
|
|
if(question.qdcount == 1 && question.qclass == 1) {
|
|
const override_rule_t *rule = NULL;
|
|
|
|
if(has_matching_exception(cfg, question.qname)) {
|
|
log_printf("[nanodns] exception matched, bypassing override for %s\n",
|
|
question.qname);
|
|
} else {
|
|
rule = find_matching_rule(cfg, question.qname);
|
|
}
|
|
|
|
if(rule != NULL) {
|
|
int build_rc;
|
|
const char *response_via;
|
|
|
|
log_printf("[nanodns] override matched %s -> %s for %s\n", rule->mask,
|
|
rule->text, question.qname);
|
|
|
|
if(question.qtype == 1 || question.qtype == 255) {
|
|
build_rc = build_override_response(request, &question, &rule->addr,
|
|
response, sizeof(response),
|
|
&response_len);
|
|
response_via = "override";
|
|
} else {
|
|
build_rc = build_nodata_response(request, request_len, &question,
|
|
response, sizeof(response),
|
|
&response_len);
|
|
response_via = "override-nodata";
|
|
}
|
|
|
|
if(build_rc == 0) {
|
|
send_dns_response_to_client(
|
|
server_fd, response, response_len, client_addr, client_len,
|
|
response_via, "sendto(client, override-local)");
|
|
return;
|
|
}
|
|
|
|
if(build_error_response(request, request_len, &question, 2, response,
|
|
sizeof(response), &response_len) == 0) {
|
|
send_dns_response_to_client(
|
|
server_fd, response, response_len, client_addr, client_len,
|
|
"override-servfail", "sendto(client, override-servfail)");
|
|
return;
|
|
}
|
|
|
|
log_printf("[nanodns] failed to build override response for %s\n",
|
|
question.qname);
|
|
}
|
|
}
|
|
|
|
{
|
|
char via[INET_ADDRSTRLEN];
|
|
|
|
if(forward_query_to_upstream(cfg, request, request_len, question.id,
|
|
upstream_fds, response, sizeof(response),
|
|
&response_len, via, sizeof(via)) == 0) {
|
|
send_dns_response_to_client(server_fd, response, response_len,
|
|
client_addr, client_len, via,
|
|
"sendto(client, upstream)");
|
|
return;
|
|
}
|
|
}
|
|
|
|
log_printf("[nanodns] all upstreams failed for %s\n", question.qname);
|
|
if(build_error_response(request, request_len, &question, 2, response,
|
|
sizeof(response), &response_len) == 0) {
|
|
send_dns_response_to_client(server_fd, response, response_len, client_addr,
|
|
client_len, "local-servfail",
|
|
"sendto(client, servfail)");
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_server_listener_event(const app_config_t *cfg,
|
|
server_listener_t *listener, int *upstream_fds,
|
|
short revents) {
|
|
uint8_t request[MAX_DNS_PACKET];
|
|
struct sockaddr_storage client_addr;
|
|
socklen_t client_len = sizeof(client_addr);
|
|
ssize_t received;
|
|
|
|
if((revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
|
|
log_printf("[nanodns] %s listening socket poll events=0x%x, recreating\n",
|
|
listener->name, revents);
|
|
invalidate_server_listener(listener);
|
|
return;
|
|
}
|
|
|
|
if((revents & POLLIN) == 0) {
|
|
return;
|
|
}
|
|
|
|
memset(&client_addr, 0, sizeof(client_addr));
|
|
received = recvfrom(listener->fd, request, sizeof(request), 0,
|
|
(struct sockaddr *)&client_addr, &client_len);
|
|
if(received < 0) {
|
|
if(errno == EINTR || errno == EAGAIN) {
|
|
return;
|
|
}
|
|
|
|
log_errno("recvfrom(client)");
|
|
if(server_socket_needs_reopen(errno)) {
|
|
invalidate_server_listener(listener);
|
|
}
|
|
return;
|
|
}
|
|
|
|
handle_dns_request(cfg, listener->fd, upstream_fds, request, (size_t)received,
|
|
(const struct sockaddr *)&client_addr, client_len);
|
|
}
|
|
|
|
static int
|
|
net_init(void) {
|
|
if(sceNetInit() != 0) {
|
|
errno = EIO;
|
|
log_errno("sceNetInit");
|
|
return -1;
|
|
}
|
|
|
|
g_libnet_mem_id = sceNetPoolCreate("nanodns", 64 * 1024, 0);
|
|
if(g_libnet_mem_id < 0) {
|
|
errno = EIO;
|
|
log_errno("sceNetPoolCreate");
|
|
if(sceNetTerm() != 0) {
|
|
errno = EIO;
|
|
log_errno("sceNetTerm");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
net_fini(void) {
|
|
if(g_libnet_mem_id >= 0) {
|
|
if(sceNetPoolDestroy(g_libnet_mem_id) != 0) {
|
|
errno = EIO;
|
|
log_errno("sceNetPoolDestroy");
|
|
}
|
|
g_libnet_mem_id = -1;
|
|
}
|
|
|
|
if(sceNetTerm() != 0) {
|
|
errno = EIO;
|
|
log_errno("sceNetTerm");
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_signal(int signo) {
|
|
(void)signo;
|
|
g_running = 0;
|
|
}
|
|
|
|
int
|
|
main(void) {
|
|
app_config_t cfg;
|
|
int upstream_fds[MAX_UPSTREAMS];
|
|
server_listener_t listeners[SERVER_LISTENER_COUNT];
|
|
struct pollfd pfds[SERVER_LISTENER_COUNT];
|
|
size_t poll_listener_index[SERVER_LISTENER_COUNT];
|
|
int config_state;
|
|
int config_errno = 0;
|
|
int config_loaded = 0;
|
|
int data_dir_state;
|
|
int data_dir_errno = 0;
|
|
|
|
for(size_t i = 0; i < MAX_UPSTREAMS; ++i) {
|
|
upstream_fds[i] = -1;
|
|
}
|
|
|
|
init_server_listener(&listeners[SERVER_LISTENER_IPV4], AF_INET, "IPv4",
|
|
true);
|
|
init_server_listener(&listeners[SERVER_LISTENER_IPV6], AF_INET6, "IPv6",
|
|
false);
|
|
|
|
#if defined(PLATFORM_PS5)
|
|
(void)syscall(SYS_thr_set_name, -1, PAYLOAD_EXEC_NAME);
|
|
#endif
|
|
|
|
signal(SIGINT, on_signal);
|
|
signal(SIGTERM, on_signal);
|
|
|
|
data_dir_state = ensure_runtime_dir_exists(DATA_DIR);
|
|
if(data_dir_state < 0) {
|
|
data_dir_errno = errno;
|
|
}
|
|
|
|
config_state = ensure_default_config_exists(CONFIG_PATH);
|
|
if(config_state < 0) {
|
|
config_errno = errno;
|
|
}
|
|
|
|
if(load_config(CONFIG_PATH, &cfg) != 0) {
|
|
config_set_defaults(&cfg);
|
|
config_apply_builtin_upstreams(&cfg);
|
|
(void)config_apply_builtin_overrides(&cfg);
|
|
config_apply_builtin_exceptions(&cfg);
|
|
} else {
|
|
config_loaded = 1;
|
|
}
|
|
|
|
(void)logger_init(&cfg);
|
|
print_banner();
|
|
|
|
for(size_t i = 0; i < cfg.warning_count; ++i) {
|
|
log_printf("%s", cfg.warnings[i].text);
|
|
}
|
|
|
|
if(data_dir_state == 1) {
|
|
log_printf("[nanodns] created runtime directory %s\n", DATA_DIR);
|
|
} else if(data_dir_state < 0) {
|
|
log_printf("[nanodns] could not create %s: %s\n", DATA_DIR,
|
|
strerror(data_dir_errno));
|
|
}
|
|
|
|
if(config_state == 1) {
|
|
log_printf("[nanodns] created default config at %s\n", CONFIG_PATH);
|
|
} else if(config_state < 0) {
|
|
log_printf("[nanodns] could not create %s: %s\n", CONFIG_PATH,
|
|
strerror(config_errno));
|
|
}
|
|
|
|
if(!config_loaded) {
|
|
log_printf("[nanodns] could not fully load %s, using fallback defaults\n",
|
|
CONFIG_PATH);
|
|
}
|
|
|
|
if(g_log_file != NULL) {
|
|
log_printf("[nanodns] log file: %s\n", cfg.log_path);
|
|
}
|
|
log_printf("[nanodns] debug output: %s\n",
|
|
cfg.debug_enabled ? "enabled" : "disabled");
|
|
log_printf("[nanodns] quiet mode: %s\n",
|
|
cfg.quiet_enabled ? "enabled" : "disabled");
|
|
log_printf("[nanodns] bind IPv4: %s:%d\n", cfg.bind_text, DNS_PORT);
|
|
if(cfg.bind6_enabled) {
|
|
log_printf("[nanodns] bind IPv6: [%s]:%d\n", cfg.bind6_text, DNS_PORT);
|
|
} else {
|
|
log_printf("[nanodns] bind IPv6: disabled\n");
|
|
}
|
|
log_printf("[nanodns] config loaded: %zu upstream(s), %zu rule(s), %zu exception(s), timeout=%dms\n",
|
|
cfg.upstream_count, cfg.rule_count, cfg.exception_count,
|
|
cfg.timeout_ms);
|
|
|
|
for(size_t i = 0; i < cfg.upstream_count; ++i) {
|
|
log_printf("[nanodns] upstream[%zu] = %s\n", i, cfg.upstreams[i].text);
|
|
}
|
|
|
|
for(size_t i = 0; i < cfg.rule_count; ++i) {
|
|
log_printf("[nanodns] rule[%zu] = %s -> %s\n", i, cfg.rules[i].mask,
|
|
cfg.rules[i].text);
|
|
}
|
|
|
|
for(size_t i = 0; i < cfg.exception_count; ++i) {
|
|
log_printf("[nanodns] exception[%zu] = %s\n", i, cfg.exceptions[i].mask);
|
|
}
|
|
|
|
#if defined(PLATFORM_PS5)
|
|
if(terminate_existing_instances(PAYLOAD_EXEC_NAME) != 0) {
|
|
close_upstream_sockets(upstream_fds, MAX_UPSTREAMS);
|
|
logger_fini();
|
|
return EXIT_FAILURE;
|
|
}
|
|
#endif
|
|
|
|
if(elevate_privileges() != 0) {
|
|
close_upstream_sockets(upstream_fds, MAX_UPSTREAMS);
|
|
logger_fini();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if(net_init() != 0) {
|
|
close_upstream_sockets(upstream_fds, MAX_UPSTREAMS);
|
|
logger_fini();
|
|
return 1;
|
|
}
|
|
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
(void)open_server_listener(&cfg, &listeners[i]);
|
|
}
|
|
|
|
if(listeners[SERVER_LISTENER_IPV4].fd < 0 &&
|
|
listeners[SERVER_LISTENER_IPV6].fd < 0) {
|
|
close_upstream_sockets(upstream_fds, MAX_UPSTREAMS);
|
|
net_fini();
|
|
logger_fini();
|
|
return 1;
|
|
}
|
|
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
if(listeners[i].fd >= 0) {
|
|
log_server_listener_addr(&cfg, &listeners[i], "listening on");
|
|
}
|
|
}
|
|
if(!cfg.bind6_enabled) {
|
|
log_printf("[nanodns] IPv6 listener disabled by config\n");
|
|
} else if(listeners[SERVER_LISTENER_IPV6].fd < 0) {
|
|
log_printf("[nanodns] continuing without IPv6 listener\n");
|
|
}
|
|
|
|
if(!cfg.quiet_enabled) {
|
|
(void)send_startup_notification(
|
|
&cfg, listeners[SERVER_LISTENER_IPV4].fd >= 0,
|
|
listeners[SERVER_LISTENER_IPV6].fd >= 0);
|
|
}
|
|
|
|
while(g_running) {
|
|
nfds_t poll_count = 0;
|
|
int64_t current_ms = now_ms();
|
|
int poll_rc;
|
|
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
if(listeners[i].fd < 0 && listeners[i].retry_enabled &&
|
|
current_ms >= listeners[i].retry_at_ms) {
|
|
log_server_listener_addr(&cfg, &listeners[i],
|
|
"attempting to restore listening socket on");
|
|
if(open_server_listener(&cfg, &listeners[i]) == 0) {
|
|
log_server_listener_addr(&cfg, &listeners[i],
|
|
"listening socket restored on");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(listeners[SERVER_LISTENER_IPV4].fd < 0 &&
|
|
listeners[SERVER_LISTENER_IPV6].fd < 0) {
|
|
sleep(1);
|
|
continue;
|
|
}
|
|
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
if(listeners[i].fd < 0) {
|
|
continue;
|
|
}
|
|
|
|
poll_listener_index[poll_count] = i;
|
|
pfds[poll_count].fd = listeners[i].fd;
|
|
pfds[poll_count].events = POLLIN;
|
|
pfds[poll_count].revents = 0;
|
|
++poll_count;
|
|
}
|
|
|
|
poll_rc = poll(pfds, poll_count, 1000);
|
|
if(poll_rc == 0) {
|
|
continue;
|
|
}
|
|
|
|
if(poll_rc < 0) {
|
|
if(errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
log_errno("poll(server)");
|
|
if(server_socket_needs_reopen(errno)) {
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
invalidate_server_listener(&listeners[i]);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
for(nfds_t i = 0; i < poll_count; ++i) {
|
|
handle_server_listener_event(&cfg, &listeners[poll_listener_index[i]],
|
|
upstream_fds, pfds[i].revents);
|
|
}
|
|
}
|
|
|
|
log_printf("[nanodns] shutting down\n");
|
|
for(size_t i = 0; i < SERVER_LISTENER_COUNT; ++i) {
|
|
invalidate_server_listener(&listeners[i]);
|
|
}
|
|
close_upstream_sockets(upstream_fds, MAX_UPSTREAMS);
|
|
net_fini();
|
|
logger_fini();
|
|
return 0;
|
|
}
|