Add IPv6 support for porkbun

This commit is contained in:
NIWA Naoya 2023-02-03 19:56:59 +09:00
parent b2412fe85d
commit 191377c08e

View file

@ -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);
} }
} }
} }