-
Notifications
You must be signed in to change notification settings - Fork 483
new provider: Add Gidinet DNS provider and registrar #4004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add support for Gidinet (Italian domain registrar) as both DNS provider and registrar. Features: - DNS Provider: Full CRUD operations for A, AAAA, CNAME, MX, NS, TXT, SRV records - Registrar: Nameserver delegation management at registry level - Zone listing via get-zones command - Dual host support for migration scenarios Technical details: - Uses SOAP API at api.quickservicebox.com - Record-based updates (diff2.ByRecord) - Apex NS records filtered (managed by registrar only) - TTL values automatically rounded to API-supported values Documentation and CI/CD configuration included.
tlimoncelli
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far! Just cosmetic changes. Thanks for contributing this!
providers/gidinet/gidinetProvider.go
Outdated
| // The default for unlisted capabilities is 'Cannot'. | ||
| // See providers/capabilities.go for the entire list of capabilities. | ||
| providers.CanAutoDNSSEC: providers.Cannot(), | ||
| providers.CanConcur: providers.Cannot(), // SOAP API, safer to serialize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All new providers must have providers.CanConcur: providers.Can(). The Cannot() setting is for legacy providers.
I don't see anything in the code that indicates Cannot() is justified. This setting means that the code in providers/gidinet/ can be used in a goroutine. This usually means that any caches in gidinetProvider{} are protected by mutexes. Since there aren't any caches, this should run fine.
| providers.CanConcur: providers.Cannot(), // SOAP API, safer to serialize | |
| providers.CanConcur: providers.Can(), |
providers/gidinet/gidinetProvider.go
Outdated
| providers.CanConcur: providers.Cannot(), // SOAP API, safer to serialize | ||
| providers.CanGetZones: providers.Can(), | ||
| providers.CanUseAlias: providers.Cannot(), | ||
| providers.CanUseCAA: providers.Cannot(), // Only premium service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| providers.CanUseCAA: providers.Cannot(), // Only premium service | |
| providers.CanUseCAA: providers.Cannot("Only premium service"), |
providers/gidinet/gidinetProvider.go
Outdated
| providers.CanUseSSHFP: providers.Cannot(), | ||
| providers.CanUseSVCB: providers.Cannot(), | ||
| providers.CanUseTLSA: providers.Cannot(), | ||
| providers.DocCreateDomains: providers.Cannot(), // Must be created via web UI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| providers.DocCreateDomains: providers.Cannot(), // Must be created via web UI | |
| providers.DocCreateDomains: providers.Cannot("Must be created via web UI"), |
providers/gidinet/api.go
Outdated
| soapEnvelope := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?> | ||
| <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | ||
| <soap:Body> | ||
| %s | ||
| </soap:Body> | ||
| </soap:Envelope>`, string(bodyXML)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concatenation is faster in this situation:
| soapEnvelope := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?> | |
| <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
| <soap:Body> | |
| %s | |
| </soap:Body> | |
| </soap:Envelope>`, string(bodyXML)) | |
| soapEnvelope := (`<?xml version="1.0" encoding="utf-8"?> | |
| <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
| <soap:Body> | |
| ' + string(bodyXML) + ' | |
| </soap:Body> | |
| </soap:Envelope>`) |
I did a small benchmark:
$ go test -bench=.
goos: darwin
goarch: arm64
pkg: github.com/StackExchange/dnscontrol/v4/bench
cpu: Apple M3 Max
BenchmarkSprintf-16 4205144 267.2 ns/op
BenchmarkStringConcatenation-16 12940010 92.44 ns/op
BenchmarkStringsBuilder-16 6614196 179.9 ns/op
PASS
ok github.com/StackExchange/dnscontrol/v4/bench 4.432s
providers/gidinet/types.go
Outdated
| ) | ||
|
|
||
| // AllowedTTLValues lists the TTL values supported by the Gidinet API | ||
| var AllowedTTLValues = []uint32{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unexport AllowedTTLValues (i.e. rename it allowedTTLValues)
providers/gidinet/api.go
Outdated
| } | ||
|
|
||
| // parseSOAPResponse extracts the response from a SOAP envelope | ||
| func parseSOAPResponse(data []byte, response interface{}) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| func parseSOAPResponse(data []byte, response interface{}) error { | |
| func parseSOAPResponse(data []byte, response any) error { |
providers/gidinet/types.go
Outdated
| // SOAPBody represents the SOAP body | ||
| type SOAPBody struct { | ||
| XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` | ||
| Content interface{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Content interface{} | |
| Content any |
providers/gidinet/api.go
Outdated
| if strings.HasSuffix(hostname, ".") { | ||
| return strings.TrimSuffix(hostname, ".") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if strings.HasSuffix(hostname, ".") { | |
| return strings.TrimSuffix(hostname, ".") | |
| } | |
| if before, ok := strings.CutSuffix(hostname, "."); ok { | |
| return before | |
| } |
providers/gidinet/api.go
Outdated
| suffix := "." + domain | ||
| if strings.HasSuffix(fqdn, suffix) { | ||
| return strings.TrimSuffix(fqdn, suffix) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| suffix := "." + domain | |
| if strings.HasSuffix(fqdn, suffix) { | |
| return strings.TrimSuffix(fqdn, suffix) | |
| } | |
| if before, ok := strings.CutSuffix(fqdn, "." + domain); ok { | |
| return before | |
| } |
providers/gidinet/auditrecords.go
Outdated
| a.Add("MX", rejectif.MxNull) // MX priority 0 is allowed (means highest priority) | ||
|
|
||
| a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Gidinet doesn't support quotes in TXT | ||
| a.Add("TXT", rejectif.TxtIsEmpty) // Empty TXT records not allowed | ||
| a.Add("TXT", rejectif.TxtHasBackticks) // Backticks not supported | ||
|
|
||
| a.Add("SRV", rejectif.SrvHasNullTarget) // SRV must have a target |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include "last verified" dates for all these comments.
| a.Add("MX", rejectif.MxNull) // MX priority 0 is allowed (means highest priority) | |
| a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Gidinet doesn't support quotes in TXT | |
| a.Add("TXT", rejectif.TxtIsEmpty) // Empty TXT records not allowed | |
| a.Add("TXT", rejectif.TxtHasBackticks) // Backticks not supported | |
| a.Add("SRV", rejectif.SrvHasNullTarget) // SRV must have a target | |
| a.Add("MX", rejectif.MxNull) // Last verified 2026-01-24 | |
| a.Add("TXT", rejectif.TxtHasDoubleQuotes) // Last verified 2026-01-24 | |
| a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2026-01-24 | |
| a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2026-01-24 | |
| a.Add("SRV", rejectif.SrvHasNullTarget) // Last verified 2026-01-24 |
|
Let me introduce you to @fm, our "liaison to maintainers". He'll reach out to you with our "welcome kit". |
providers/gidinet/api.go
Outdated
| } | ||
|
|
||
| // Find the smallest allowed value that is >= ttl | ||
| for _, v := range AllowedTTLValues { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(low priority) Consider using https://pkg.go.dev/slices#example-BinarySearch instead of a loop.
…pes, fix SRV/TXT audits
|
Thank you @tlimoncelli 💪 Great review. I've addressed all your suggestions. I'm going to reach out to the Gidinet owner to see if we can get a fix for long TXT records (more than 254 chars), SRV records and a test account with a test domain (that I can pay) to have a proper integration flow that can be tested automatically. |
The GIDINET API rejects single TXT strings >250 chars but accepts multiple quoted segments like: "chunk1" "chunk2" - Add chunkTXT() to split long values into 250-char quoted chunks - Add unchunkTXT() to parse chunked format back to single string - Update toGidinetRecord to chunk TXT on create/update - Update toRecordConfig to unchunk TXT when reading - Remove TxtLongerThan(254) audit since long TXT now works - Add comprehensive unit tests for chunking functions
|
I found a way to support long TXT records (like DKIM keys >250 chars)! The GIDINET API rejects single TXT strings longer than ~250 characters, but it accepts multiple quoted segments in the format: I've added automatic chunking:
This enables full DKIM support. Tested end-to-end with a 400+ char DKIM record - works perfectly with no drift on re-preview. Unit tests included for the chunking functions. |
Summary
Hey! This adds support for Gidinet, an Italian domain registrar. I needed it for managing my domains so figured I'd contribute it back.
It works as both DNS provider and registrar:
The API is SOAP-based which was fun to work with... Their docs are a bit sparse but I got it working after some trial and error.
Notes
Tested with my own domains and it's been working fine. Happy to address any feedback!
Please create the GitHub label "provider-gidinet"