Add "myip6" URL parameter to provide an IPv6 address in addition to an IPv4 one as "myip"

This commit is contained in:
cn 2016-12-07 14:16:57 +01:00
parent ae095c22b7
commit 4894015325
6 changed files with 71 additions and 22 deletions

View File

@ -120,6 +120,8 @@ The following rules apply:
* use any IP address provided via the X-Real-IP header e.g. when used behind HTTP reverse proxy such as nginx, or * use any IP address provided via the X-Real-IP header e.g. when used behind HTTP reverse proxy such as nginx, or
* use any IP address used by the connecting HTTP client * use any IP address used by the connecting HTTP client
If you want to provide an additional IPv6 address as myip6 parameter the myip parameter containing an IPv4 address has to be present, too! No automatism is applied then.
### SSL, multiple listen ports ### SSL, multiple listen ports
Use a webserver as a proxy to handle SSL and/or multiple listen addresses and ports. DynDNS.com provides HTTP on port 80 and 8245 and HTTPS on port 443. Use a webserver as a proxy to handle SSL and/or multiple listen addresses and ports. DynDNS.com provides HTTP on port 80 and 8245 and HTTPS on port 443.

View File

@ -78,24 +78,40 @@ module Dyndnsd
return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname
end end
# fallback value, always present myip = nil
myip = env["REMOTE_ADDR"]
# check whether X-Real-IP header has valid IPAddr if params.has_key?("myip6")
if env.has_key?("HTTP_X_REAL_IP") # require presence of myip parameter as valid IPAddr (v4) and valid myip6
return @responder.response_for_error(:host_forbidden) if not params["myip"]
begin begin
IPAddr.new(env["HTTP_X_REAL_IP"]) IPAddr.new(params["myip"], Socket::AF_INET)
myip = env["HTTP_X_REAL_IP"] IPAddr.new(params["myip6"], Socket::AF_INET6)
# myip will be an array
myip = [params["myip"], params["myip6"]]
rescue ArgumentError rescue ArgumentError
return @responder.response_for_error(:host_forbidden)
end end
end else
# fallback value, always present
myip = env["REMOTE_ADDR"]
# check whether myip parameter has valid IPAddr # check whether X-Real-IP header has valid IPAddr
if params.has_key?("myip") if env.has_key?("HTTP_X_REAL_IP")
begin begin
IPAddr.new(params["myip"]) IPAddr.new(env["HTTP_X_REAL_IP"])
myip = params["myip"] myip = env["HTTP_X_REAL_IP"]
rescue ArgumentError rescue ArgumentError
end
end
# check whether myip parameter has valid IPAddr
if params.has_key?("myip")
begin
IPAddr.new(params["myip"])
myip = params["myip"]
rescue ArgumentError
end
end end
end end

View File

@ -18,11 +18,13 @@ module Dyndnsd
out << "@ IN SOA #{@dns} #{@email_addr} ( #{zone['serial']} 3h 5m 1w 1h )" out << "@ IN SOA #{@dns} #{@email_addr} ( #{zone['serial']} 3h 5m 1w 1h )"
out << "@ IN NS #{@dns}" out << "@ IN NS #{@dns}"
out << "" out << ""
zone['hosts'].each do |hostname,ip| zone['hosts'].each do |hostname,ips|
ip = IPAddr.new(ip).native (ips.is_a?(Array) ? ips : [ips]).each do |ip|
type = ip.ipv6? ? "AAAA" : "A" ip = IPAddr.new(ip).native
name = hostname.chomp('.' + @domain) type = ip.ipv6? ? "AAAA" : "A"
out << "#{name} IN #{type} #{ip}" name = hostname.chomp('.' + @domain)
out << "#{name} IN #{type} #{ip}"
end
end end
out << "" out << ""
out << @additional_zone_content out << @additional_zone_content

View File

@ -11,9 +11,9 @@ module Dyndnsd
return [200, {"Content-Type" => "text/plain"}, ["nohost"]] if state == :host_forbidden return [200, {"Content-Type" => "text/plain"}, ["nohost"]] if state == :host_forbidden
return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_malformed return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_malformed
end end
def response_for_changes(states, ip) def response_for_changes(states, ip)
body = states.map { |state| "#{state} #{ip}" }.join("\n") body = states.map { |state| "#{state} #{ip.is_a?(Array) ? ip.join(' ') : ip}" }.join("\n")
return [200, {"Content-Type" => "text/plain"}, [body]] return [200, {"Content-Type" => "text/plain"}, [body]]
end end
end end

View File

@ -11,9 +11,9 @@ module Dyndnsd
return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] if state == :host_forbidden return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] if state == :host_forbidden
return [422, {"Content-Type" => "text/plain"}, ["Hostname malformed"]] if state == :hostname_malformed return [422, {"Content-Type" => "text/plain"}, ["Hostname malformed"]] if state == :hostname_malformed
end end
def response_for_changes(states, ip) def response_for_changes(states, ip)
body = states.map { |state| state == :good ? "Changed to #{ip}" : "No change needed for #{ip}" }.join("\n") body = states.map { |state| state == :good ? "Changed to #{ip.is_a?(Array) ? ip.join(' ') : ip}" : "No change needed for #{ip.is_a?(Array) ? ip.join(' ') : ip}" }.join("\n")
return [200, {"Content-Type" => "text/plain"}, [body]] return [200, {"Content-Type" => "text/plain"}, [body]]
end end
end end

View File

@ -170,8 +170,37 @@ describe Dyndnsd::Daemon do
it 'uses clients remote IP address from X-Real-IP header if behind proxy' do it 'uses clients remote IP address from X-Real-IP header if behind proxy' do
authorize 'test', 'secret' authorize 'test', 'secret'
get '/nic/update?hostname=foo.example.org', '', 'HTTP_X_REAL_IP' => '10.0.0.1' get '/nic/update?hostname=foo.example.org', '', 'HTTP_X_REAL_IP' => '10.0.0.1'
expect(last_response).to be_ok expect(last_response).to be_ok
expect(last_response.body).to eq('good 10.0.0.1') expect(last_response.body).to eq('good 10.0.0.1')
get '/nic/update?hostname=foo.example.org', '', 'HTTP_X_REAL_IP' => '2001:db8::1'
expect(last_response).to be_ok
expect(last_response.body).to eq('good 2001:db8::1')
end
it 'supports an IPv4 and an IPv6 address in one request' do
authorize 'test', 'secret'
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4&myip6=2001:db8::1'
expect(last_response).to be_ok
expect(last_response.body).to eq("good 1.2.3.4 2001:db8::1")
get '/nic/update?hostname=foo.example.org&myip=BROKENIP&myip6=2001:db8::1'
expect(last_response).to be_ok
expect(last_response.body).to eq('nohost')
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4&myip6=BROKENIP'
expect(last_response).to be_ok
expect(last_response.body).to eq('nohost')
get '/nic/update?hostname=foo.example.org&myip6=2001:db8::10'
expect(last_response).to be_ok
expect(last_response.body).to eq('nohost')
get '/nic/update?hostname=foo.example.org&myip=1.2.3.40'
expect(last_response).to be_ok
expect(last_response.body).to eq('good 1.2.3.40')
end end
end end