Add IPv6 support for porkbun
This commit is contained in:
parent
b2412fe85d
commit
191377c08e
1 changed files with 198 additions and 76 deletions
274
ddclient.in
274
ddclient.in
|
|
@ -863,11 +863,14 @@ my %services = (
|
||||||
'update' => \&nic_porkbun_update,
|
'update' => \&nic_porkbun_update,
|
||||||
'examples' => \&nic_porkbun_examples,
|
'examples' => \&nic_porkbun_examples,
|
||||||
'variables' => {
|
'variables' => {
|
||||||
'apikey' => setv(T_PASSWD, 1, 0, '', undef),
|
'apikey' => setv(T_PASSWD, 1, 0, '', undef),
|
||||||
'secretapikey' => setv(T_PASSWD, 1, 0, '', undef),
|
'secretapikey' => setv(T_PASSWD, 1, 0, '', undef),
|
||||||
'on-root-domain' => setv(T_BOOL, 0, 0, 0, undef),
|
'on-root-domain' => setv(T_BOOL, 0, 0, 0, undef),
|
||||||
'login' => setv(T_LOGIN, 0, 0, 'unused', undef),
|
'login' => setv(T_LOGIN, 0, 0, 'unused', undef),
|
||||||
'password' => setv(T_PASSWD, 0, 0, 'unused', undef),
|
'password' => setv(T_PASSWD, 0, 0, 'unused', undef),
|
||||||
|
'use' => setv(T_USE, 0, 0, 'disabled', undef),
|
||||||
|
'usev4' => setv(T_USEV4, 0, 0, 'disabled', undef),
|
||||||
|
'usev6' => setv(T_USEV6, 0, 0, 'disabled', undef),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'sitelutions' => {
|
'sitelutions' => {
|
||||||
|
|
@ -7127,9 +7130,11 @@ Available configuration variables:
|
||||||
* on-root-domain=yes or no (default: no): Indicates whether the specified domain name (FQDN) is
|
* on-root-domain=yes or no (default: no): Indicates whether the specified domain name (FQDN) is
|
||||||
an unnamed record (Zone APEX) in a zone.
|
an unnamed record (Zone APEX) in a zone.
|
||||||
It is useful to specify it as a local variable as shown in the example.
|
It is useful to specify it as a local variable as shown in the example.
|
||||||
|
* usev4, usev6 : These configuration variables can be specified as local variables to override
|
||||||
|
the global settings. It is useful to finely control IPv4 or IPv6 as shown in the example.
|
||||||
|
* use (deprecated) : This parameter is deprecated but can be overridden like the above parameters.
|
||||||
|
|
||||||
Limitations:
|
Limitations:
|
||||||
* Only support IPv4 (A record). Not support IPv6.
|
|
||||||
* Multiple same name records (for round robin) are not supported.
|
* Multiple same name records (for round robin) are not supported.
|
||||||
The same IP address is set for all, creating meaningless extra records.
|
The same IP address is set for all, creating meaningless extra records.
|
||||||
|
|
||||||
|
|
@ -7139,6 +7144,25 @@ Example ${program}.conf file entry:
|
||||||
secretapikey=SecretAPIKey
|
secretapikey=SecretAPIKey
|
||||||
host.example.com,host2.sub.example.com
|
host.example.com,host2.sub.example.com
|
||||||
on-root-domain=yes example.com,sub.example.com
|
on-root-domain=yes example.com,sub.example.com
|
||||||
|
|
||||||
|
Additional example to finely control IPv4 or IPv6 :
|
||||||
|
# Example 01 : Global enable both IPv4 and IPv6, and update both records.
|
||||||
|
usev4=webv4
|
||||||
|
usev6=ifv6, ifv6=enp1s0
|
||||||
|
|
||||||
|
protocol=porkbun
|
||||||
|
apikey=APIKey
|
||||||
|
secretapikey=SecretAPIKey
|
||||||
|
host.example.com,host2.sub.example.com
|
||||||
|
|
||||||
|
# Example 02 : Global enable only IPv4, and update only IPv6 record.
|
||||||
|
usev4=webv4
|
||||||
|
|
||||||
|
protocol=porkbun
|
||||||
|
apikey=APIKey
|
||||||
|
secretapikey=SecretAPIKey
|
||||||
|
usev6=ifv6, ifv6=enp1s0, usev4=disabled ipv6.example.com
|
||||||
|
|
||||||
EoEXAMPLE
|
EoEXAMPLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7151,10 +7175,6 @@ sub nic_porkbun_update {
|
||||||
## update each configured host
|
## update each configured host
|
||||||
## should improve to update in one pass
|
## should improve to update in one pass
|
||||||
foreach my $host (@_) {
|
foreach my $host (@_) {
|
||||||
my $ipv4 = delete $config{$host}{'wantipv4'};
|
|
||||||
info("setting IP address to %s for %s", $ipv4, $host);
|
|
||||||
verbose("UPDATE:","updating %s", $host);
|
|
||||||
|
|
||||||
my ($sub_domain, $domain);
|
my ($sub_domain, $domain);
|
||||||
if ($config{$host}{'on-root-domain'}) {
|
if ($config{$host}{'on-root-domain'}) {
|
||||||
$sub_domain = '';
|
$sub_domain = '';
|
||||||
|
|
@ -7162,69 +7182,19 @@ sub nic_porkbun_update {
|
||||||
} else {
|
} else {
|
||||||
($sub_domain, $domain) = split(/\./, $host, 2);
|
($sub_domain, $domain) = split(/\./, $host, 2);
|
||||||
}
|
}
|
||||||
my $url = "https://porkbun.com/api/json/v3/dns/retrieveByNameType/$domain/A/$sub_domain";
|
my $ipv4 = delete $config{$host}{'wantipv4'};
|
||||||
my $data = encode_json({
|
my $ipv6 = delete $config{$host}{'wantipv6'};
|
||||||
secretapikey => $config{$host}{'secretapikey'},
|
if (is_ipv4($ipv4)) {
|
||||||
apikey => $config{$host}{'apikey'},
|
info("setting IPv4 address to %s for %s", $ipv4, $host);
|
||||||
});
|
verbose("UPDATE:","updating %s", $host);
|
||||||
my $header = "Content-Type: application/json\n";
|
|
||||||
my $reply = geturl(
|
my $url = "https://porkbun.com/api/json/v3/dns/retrieveByNameType/$domain/A/$sub_domain";
|
||||||
proxy => opt('proxy'),
|
my $data = encode_json({
|
||||||
url => $url,
|
|
||||||
headers => $header,
|
|
||||||
method => 'POST',
|
|
||||||
data => $data,
|
|
||||||
);
|
|
||||||
# No response, declare as failed
|
|
||||||
if (!defined($reply) || !$reply) {
|
|
||||||
$config{$host}{'status'} = "bad";
|
|
||||||
failed("updating %s: Could not connect to porkbun.com.", $host);
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
if (!header_ok($host, $reply)) {
|
|
||||||
$config{$host}{'status'} = "bad";
|
|
||||||
failed("updating %s: failed (%s)", $host, $reply);
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
# Strip header
|
|
||||||
# Porkbun sends data in chunks, so it is assumed to be one chunk and parsed forcibly.
|
|
||||||
$reply =~ qr/{(?:[^{}]*|(?R))*}/mp;
|
|
||||||
my $response = eval { decode_json(${^MATCH}) };
|
|
||||||
if (!defined($response)) {
|
|
||||||
$config{$host}{'status'} = "bad";
|
|
||||||
failed("%s -- Unexpected service response.", $host);
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
if ($response->{status} ne 'SUCCESS') {
|
|
||||||
$config{$host}{'status'} = "bad";
|
|
||||||
failed("%s -- Unexpected status. (status = %s)", $host, $response->{status});
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
my $records = $response->{records};
|
|
||||||
if (ref($records) eq 'ARRAY' && defined $records->[0]->{'id'}) {
|
|
||||||
my $count = scalar(@{$records});
|
|
||||||
if ($count > 1) {
|
|
||||||
warning("updating %s: There are multiple applicable records. Only first record is used. Overwrite all with the same content.");
|
|
||||||
}
|
|
||||||
my $current_content = $records->[0]->{'content'};
|
|
||||||
if ($current_content eq $ipv4) {
|
|
||||||
$config{$host}{'status'} = "good";
|
|
||||||
success("updating %s: skipped: IP address was already set to %s.", $host, $ipv4);
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
my $ttl = $records->[0]->{'ttl'};
|
|
||||||
my $notes = $records->[0]->{'notes'};
|
|
||||||
debug("ttl = %s", $ttl);
|
|
||||||
debug("notes = %s", $notes);
|
|
||||||
$url = "https://porkbun.com/api/json/v3/dns/editByNameType/$domain/A/$sub_domain";
|
|
||||||
$data = encode_json({
|
|
||||||
secretapikey => $config{$host}{'secretapikey'},
|
secretapikey => $config{$host}{'secretapikey'},
|
||||||
apikey => $config{$host}{'apikey'},
|
apikey => $config{$host}{'apikey'},
|
||||||
content => $ipv4,
|
|
||||||
ttl => $ttl,
|
|
||||||
notes => $notes,
|
|
||||||
});
|
});
|
||||||
$reply = geturl(
|
my $header = "Content-Type: application/json\n";
|
||||||
|
my $reply = geturl(
|
||||||
proxy => opt('proxy'),
|
proxy => opt('proxy'),
|
||||||
url => $url,
|
url => $url,
|
||||||
headers => $header,
|
headers => $header,
|
||||||
|
|
@ -7233,20 +7203,172 @@ sub nic_porkbun_update {
|
||||||
);
|
);
|
||||||
# No response, declare as failed
|
# No response, declare as failed
|
||||||
if (!defined($reply) || !$reply) {
|
if (!defined($reply) || !$reply) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
failed("updating %s: Could not connect to porkbun.com.", $host);
|
failed("updating %s: Could not connect to porkbun.com.", $host);
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
if (!header_ok($host, $reply)) {
|
if (!header_ok($host, $reply)) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
failed("updating %s: failed (%s)", $host, $reply);
|
failed("updating %s: failed (%s)", $host, $reply);
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
$config{$host}{'status'} = "good";
|
# Strip header
|
||||||
success("updating %s: good: IP address set to %s", $host, $ipv4);
|
# Porkbun sends data in chunks, so it is assumed to be one chunk and parsed forcibly.
|
||||||
next;
|
$reply =~ qr/{(?:[^{}]*|(?R))*}/mp;
|
||||||
|
my $response = eval { decode_json(${^MATCH}) };
|
||||||
|
if (!defined($response)) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("%s -- Unexpected service response.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if ($response->{status} ne 'SUCCESS') {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("%s -- Unexpected status. (status = %s)", $host, $response->{status});
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $records = $response->{records};
|
||||||
|
if (ref($records) eq 'ARRAY' && defined $records->[0]->{'id'}) {
|
||||||
|
my $count = scalar(@{$records});
|
||||||
|
if ($count > 1) {
|
||||||
|
warning("updating %s: There are multiple applicable records. Only first record is used. Overwrite all with the same content.");
|
||||||
|
}
|
||||||
|
my $current_content = $records->[0]->{'content'};
|
||||||
|
if ($current_content eq $ipv4) {
|
||||||
|
$config{$host}{'status'} = "good";
|
||||||
|
success("updating %s: skipped: IPv4 address was already set to %s.", $host, $ipv4);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $ttl = $records->[0]->{'ttl'};
|
||||||
|
my $notes = $records->[0]->{'notes'};
|
||||||
|
debug("ttl = %s", $ttl);
|
||||||
|
debug("notes = %s", $notes);
|
||||||
|
$url = "https://porkbun.com/api/json/v3/dns/editByNameType/$domain/A/$sub_domain";
|
||||||
|
$data = encode_json({
|
||||||
|
secretapikey => $config{$host}{'secretapikey'},
|
||||||
|
apikey => $config{$host}{'apikey'},
|
||||||
|
content => $ipv4,
|
||||||
|
ttl => $ttl,
|
||||||
|
notes => $notes,
|
||||||
|
});
|
||||||
|
$reply = geturl(
|
||||||
|
proxy => opt('proxy'),
|
||||||
|
url => $url,
|
||||||
|
headers => $header,
|
||||||
|
method => 'POST',
|
||||||
|
data => $data,
|
||||||
|
);
|
||||||
|
# No response, declare as failed
|
||||||
|
if (!defined($reply) || !$reply) {
|
||||||
|
failed("updating %s: Could not connect to porkbun.com.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if (!header_ok($host, $reply)) {
|
||||||
|
failed("updating %s: failed (%s)", $host, $reply);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
$config{$host}{'status'} = "good";
|
||||||
|
success("updating %s: good: IPv4 address set to %s", $host, $ipv4);
|
||||||
|
next;
|
||||||
|
} else {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("updating %s: No applicable existing records.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$config{$host}{'status'} = "bad";
|
info("No IPv4 address for %s", $host);
|
||||||
failed("updating %s: No applicable existing records.", $host);
|
}
|
||||||
next;
|
if (is_ipv6($ipv6)) {
|
||||||
|
info("setting IPv6 address to %s for %s", $ipv6, $host);
|
||||||
|
verbose("UPDATE:","updating %s", $host);
|
||||||
|
|
||||||
|
my $url = "https://porkbun.com/api/json/v3/dns/retrieveByNameType/$domain/AAAA/$sub_domain";
|
||||||
|
my $data = encode_json({
|
||||||
|
secretapikey => $config{$host}{'secretapikey'},
|
||||||
|
apikey => $config{$host}{'apikey'},
|
||||||
|
});
|
||||||
|
my $header = "Content-Type: application/json\n";
|
||||||
|
my $reply = geturl(
|
||||||
|
proxy => opt('proxy'),
|
||||||
|
url => $url,
|
||||||
|
headers => $header,
|
||||||
|
method => 'POST',
|
||||||
|
data => $data,
|
||||||
|
);
|
||||||
|
# No response, declare as failed
|
||||||
|
if (!defined($reply) || !$reply) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("updating %s: Could not connect to porkbun.com.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if (!header_ok($host, $reply)) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("updating %s: failed (%s)", $host, $reply);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
# Strip header
|
||||||
|
# Porkbun sends data in chunks, so it is assumed to be one chunk and parsed forcibly.
|
||||||
|
$reply =~ qr/{(?:[^{}]*|(?R))*}/mp;
|
||||||
|
my $response = eval { decode_json(${^MATCH}) };
|
||||||
|
if (!defined($response)) {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("%s -- Unexpected service response.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if ($response->{status} ne 'SUCCESS') {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("%s -- Unexpected status. (status = %s)", $host, $response->{status});
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $records = $response->{records};
|
||||||
|
if (ref($records) eq 'ARRAY' && defined $records->[0]->{'id'}) {
|
||||||
|
my $count = scalar(@{$records});
|
||||||
|
if ($count > 1) {
|
||||||
|
warning("updating %s: There are multiple applicable records. Only first record is used. Overwrite all with the same content.");
|
||||||
|
}
|
||||||
|
my $current_content = $records->[0]->{'content'};
|
||||||
|
if ($current_content eq $ipv6) {
|
||||||
|
$config{$host}{'status'} = "good";
|
||||||
|
success("updating %s: skipped: IPv6 address was already set to %s.", $host, $ipv6);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $ttl = $records->[0]->{'ttl'};
|
||||||
|
my $notes = $records->[0]->{'notes'};
|
||||||
|
debug("ttl = %s", $ttl);
|
||||||
|
debug("notes = %s", $notes);
|
||||||
|
$url = "https://porkbun.com/api/json/v3/dns/editByNameType/$domain/AAAA/$sub_domain";
|
||||||
|
$data = encode_json({
|
||||||
|
secretapikey => $config{$host}{'secretapikey'},
|
||||||
|
apikey => $config{$host}{'apikey'},
|
||||||
|
content => $ipv6,
|
||||||
|
ttl => $ttl,
|
||||||
|
notes => $notes,
|
||||||
|
});
|
||||||
|
$reply = geturl(
|
||||||
|
proxy => opt('proxy'),
|
||||||
|
url => $url,
|
||||||
|
headers => $header,
|
||||||
|
method => 'POST',
|
||||||
|
data => $data,
|
||||||
|
);
|
||||||
|
# No response, declare as failed
|
||||||
|
if (!defined($reply) || !$reply) {
|
||||||
|
failed("updating %s: Could not connect to porkbun.com.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
if (!header_ok($host, $reply)) {
|
||||||
|
failed("updating %s: failed (%s)", $host, $reply);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
$config{$host}{'status'} = "good";
|
||||||
|
success("updating %s: good: IPv6 address set to %s", $host, $ipv4);
|
||||||
|
next;
|
||||||
|
} else {
|
||||||
|
$config{$host}{'status'} = "bad";
|
||||||
|
failed("updating %s: No applicable existing records.", $host);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info("No IPv6 address for %s", $host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue