Compare commits
55 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b9a6c1bba | ||
|
|
d964b98e0c | ||
|
|
93f22e6942 | ||
|
|
c41756c2df | ||
|
|
1c91ff091f | ||
|
|
8b6fcf0946 | ||
|
|
3197355258 | ||
|
|
9aab7dde69 | ||
|
|
95663717f4 | ||
|
|
833db1974d | ||
|
|
3efa3308a5 | ||
|
|
d0fe2199c4 | ||
|
|
b7f6912ef0 | ||
|
|
58509bba4d | ||
|
|
b72191f03b | ||
|
|
89b71c9391 | ||
|
|
2a76e5ef0c | ||
|
|
612cf06c06 | ||
|
|
d1e884f2e2 | ||
|
|
d0b3da9494 | ||
|
|
0be2f529a0 | ||
|
|
ff0ab358df | ||
|
|
04c3bd5997 | ||
|
|
da91954490 | ||
|
|
d10a9f9073 | ||
|
|
25d4b85e56 | ||
|
|
5968a4cc50 | ||
|
|
b4b1e5c930 | ||
|
|
b417373f5b | ||
|
|
1810959b89 | ||
|
|
fcdc2c54f9 | ||
|
|
6a69356d51 | ||
|
|
fb7236bf12 | ||
|
|
56be52f7bb | ||
|
|
a78523b085 | ||
|
|
32543b2e12 | ||
|
|
08e0d201e1 | ||
|
|
f0a6c12443 | ||
|
|
5fd82dd342 | ||
|
|
b6f9d50393 | ||
|
|
bcf714d54c | ||
|
|
8807fee752 | ||
|
|
50b2394fdd | ||
|
|
added0ece8 | ||
|
|
3af5018f35 | ||
|
|
edb852ce23 | ||
|
|
f1c0f9cf2b | ||
|
|
33993b9780 | ||
|
|
6e6b2b63c2 | ||
|
|
958a799c45 | ||
|
|
8857ed4e51 | ||
|
|
f6afe97b35 | ||
|
|
5d002f8192 | ||
|
|
5b223e850c | ||
|
|
9b077efe4e |
11 changed files with 2321 additions and 298 deletions
144
action.go
144
action.go
|
|
@ -25,10 +25,19 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
actionArgBind = "bind"
|
||||
actionArgIP = "ip"
|
||||
actionArgName = "name"
|
||||
actionArgPort = "port"
|
||||
actionArgBind = "bind"
|
||||
actionArgBindAddress = "bindAddress"
|
||||
actionArgBindAddressSnake = "bind_address"
|
||||
actionArgIP = "ip"
|
||||
actionArgAddress = "address"
|
||||
actionArgName = "name"
|
||||
actionArgHost = "host"
|
||||
actionArgHostName = "hostname"
|
||||
actionArgPort = "port"
|
||||
actionArgDNSPort = "dnsPort"
|
||||
actionArgDNSPortSnake = "dns_port"
|
||||
actionArgHealthPort = "health_port"
|
||||
actionArgHealthPortCamel = "healthPort"
|
||||
)
|
||||
|
||||
type ActionDefinition struct {
|
||||
|
|
@ -40,7 +49,7 @@ type ActionDefinition struct {
|
|||
// ActionRegistrar publishes DNS actions into another Core surface.
|
||||
//
|
||||
// registrar.RegisterAction(ActionResolve, func(values map[string]any) (any, bool, error) {
|
||||
// return service.ResolveAddress("gateway.charon.lthn")
|
||||
// return service.ResolveAddresses("gateway.charon.lthn")
|
||||
// })
|
||||
type ActionRegistrar interface {
|
||||
RegisterAction(name string, invoke func(map[string]any) (any, bool, error))
|
||||
|
|
@ -75,6 +84,7 @@ type ActionCaller interface {
|
|||
// for _, definition := range definitions {
|
||||
// fmt.Println(definition.Name)
|
||||
// }
|
||||
// // dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
||||
func (service *Service) ActionDefinitions() []ActionDefinition {
|
||||
return []ActionDefinition{
|
||||
{
|
||||
|
|
@ -137,13 +147,13 @@ func (service *Service) ActionDefinitions() []ActionDefinition {
|
|||
if err := service.DiscoverAliases(context.Background()); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return service.Health(), true, nil
|
||||
return nil, true, nil
|
||||
},
|
||||
InvokeContext: func(ctx context.Context, _ map[string]any) (any, bool, error) {
|
||||
if err := service.DiscoverAliases(ctx); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return service.Health(), true, nil
|
||||
return nil, true, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -166,6 +176,7 @@ func (service *Service) ActionNames() []string {
|
|||
//
|
||||
// service.RegisterActions(registrar)
|
||||
// // registrar.RegisterAction("dns.health", ...)
|
||||
// // registrar.RegisterActionContext("dns.discover", func(ctx context.Context, values map[string]any) (any, bool, error) { ... })
|
||||
func (service *Service) RegisterActions(registrar ActionRegistrar) {
|
||||
if registrar == nil {
|
||||
return
|
||||
|
|
@ -191,6 +202,8 @@ func (service *Service) RegisterActions(registrar ActionRegistrar) {
|
|||
|
||||
// NewServiceWithRegistrar builds a DNS service and registers its actions in one step.
|
||||
//
|
||||
// Deprecated: use NewDNSServiceWithRegistrar for a more explicit constructor name.
|
||||
//
|
||||
// service := dns.NewServiceWithRegistrar(dns.ServiceConfiguration{}, registrar)
|
||||
// // registrar now exposes dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
||||
func NewServiceWithRegistrar(options ServiceOptions, registrar ActionRegistrar) *Service {
|
||||
|
|
@ -200,6 +213,14 @@ func NewServiceWithRegistrar(options ServiceOptions, registrar ActionRegistrar)
|
|||
return NewService(options)
|
||||
}
|
||||
|
||||
// NewDNSServiceWithRegistrar builds a DNS service and registers its actions in one step.
|
||||
//
|
||||
// service := dns.NewDNSServiceWithRegistrar(dns.ServiceConfiguration{}, registrar)
|
||||
// // registrar now exposes dns.resolve, dns.resolve.txt, dns.resolve.all, dns.reverse, dns.serve, dns.health, dns.discover
|
||||
func NewDNSServiceWithRegistrar(options ServiceOptions, registrar ActionRegistrar) *Service {
|
||||
return NewServiceWithRegistrar(options, registrar)
|
||||
}
|
||||
|
||||
// HandleAction executes a DNS action by name.
|
||||
//
|
||||
// payload, ok, err := service.HandleAction(ActionResolve, map[string]any{
|
||||
|
|
@ -231,11 +252,11 @@ func (service *Service) HandleActionContext(ctx context.Context, name string, va
|
|||
|
||||
func (service *Service) handleResolveAddress(ctx context.Context, values map[string]any) (any, bool, error) {
|
||||
_ = ctx
|
||||
host, err := stringActionValue(values, actionArgName)
|
||||
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
result, ok := service.ResolveAddress(host)
|
||||
result, ok := service.ResolveAddresses(host)
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
|
@ -244,7 +265,7 @@ func (service *Service) handleResolveAddress(ctx context.Context, values map[str
|
|||
|
||||
func (service *Service) handleResolveTXTRecords(ctx context.Context, values map[string]any) (any, bool, error) {
|
||||
_ = ctx
|
||||
host, err := stringActionValue(values, actionArgName)
|
||||
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
@ -257,7 +278,7 @@ func (service *Service) handleResolveTXTRecords(ctx context.Context, values map[
|
|||
|
||||
func (service *Service) handleResolveAll(ctx context.Context, values map[string]any) (any, bool, error) {
|
||||
_ = ctx
|
||||
host, err := stringActionValue(values, actionArgName)
|
||||
host, err := stringActionValueFromKeys(values, actionArgName, actionArgHost, actionArgHostName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
@ -270,7 +291,7 @@ func (service *Service) handleResolveAll(ctx context.Context, values map[string]
|
|||
|
||||
func (service *Service) handleReverseLookup(ctx context.Context, values map[string]any) (any, bool, error) {
|
||||
_ = ctx
|
||||
ip, err := stringActionValue(values, actionArgIP)
|
||||
ip, err := stringActionValueFromKeys(values, actionArgIP, actionArgAddress, actionArgName, actionArgHost, actionArgHostName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
@ -283,17 +304,37 @@ func (service *Service) handleReverseLookup(ctx context.Context, values map[stri
|
|||
|
||||
func (service *Service) handleServe(ctx context.Context, values map[string]any) (any, bool, error) {
|
||||
_ = ctx
|
||||
bind, err := stringActionValueOptional(values, actionArgBind)
|
||||
bind, _, err := stringActionValueOptionalFromKeys(values, actionArgBind, actionArgBindAddress, actionArgBindAddressSnake)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
port, portProvided, err := intActionValueOptional(values, actionArgPort)
|
||||
port, portProvided, err := intActionValueOptionalFromKeys(values, actionArgPort, actionArgDNSPort, actionArgDNSPortSnake)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !portProvided {
|
||||
port = service.ResolveDNSPort()
|
||||
}
|
||||
|
||||
healthPort, healthPortProvided, err := intActionValueOptionalFromKeys(values, actionArgHealthPort, actionArgHealthPortCamel)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !healthPortProvided && service.httpPort > 0 {
|
||||
runtime, err := service.ServeAll(bind, port, service.httpPort)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return runtime, true, nil
|
||||
}
|
||||
if healthPortProvided {
|
||||
runtime, err := service.ServeAll(bind, port, healthPort)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return runtime, true, nil
|
||||
}
|
||||
|
||||
result, err := service.Serve(bind, port)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
@ -319,6 +360,46 @@ func stringActionValue(values map[string]any, key string) (string, error) {
|
|||
return "", errActionMissingValue
|
||||
}
|
||||
|
||||
func lookupActionValue(values map[string]any, keys ...string) (any, string, bool) {
|
||||
if values == nil {
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
if value, exists := values[key]; exists {
|
||||
return value, key, true
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
for alias, value := range values {
|
||||
if strings.EqualFold(alias, key) {
|
||||
return value, alias, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
func stringActionValueFromKeys(values map[string]any, keys ...string) (string, error) {
|
||||
value, key, found := lookupActionValue(values, keys...)
|
||||
if !found {
|
||||
return "", errActionMissingValue
|
||||
}
|
||||
text, ok := value.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w: %s", errActionMissingValue, key)
|
||||
}
|
||||
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return "", errActionMissingValue
|
||||
}
|
||||
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func stringActionValueOptional(values map[string]any, key string) (string, error) {
|
||||
if values == nil {
|
||||
return "", nil
|
||||
|
|
@ -334,6 +415,18 @@ func stringActionValueOptional(values map[string]any, key string) (string, error
|
|||
return strings.TrimSpace(value), nil
|
||||
}
|
||||
|
||||
func stringActionValueOptionalFromKeys(values map[string]any, keys ...string) (string, bool, error) {
|
||||
value, key, found := lookupActionValue(values, keys...)
|
||||
if !found {
|
||||
return "", false, nil
|
||||
}
|
||||
text, ok := value.(string)
|
||||
if !ok {
|
||||
return "", false, fmt.Errorf("%w: %s", errActionMissingValue, key)
|
||||
}
|
||||
return strings.TrimSpace(text), true, nil
|
||||
}
|
||||
|
||||
func intActionValue(values map[string]any, key string) (int, error) {
|
||||
if values == nil {
|
||||
return 0, errActionMissingValue
|
||||
|
|
@ -420,3 +513,26 @@ func intActionValueOptional(values map[string]any, key string) (int, bool, error
|
|||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func intActionValueOptionalFromKeys(values map[string]any, keys ...string) (int, bool, error) {
|
||||
for _, key := range keys {
|
||||
value, valueProvided, err := intActionValueOptional(values, key)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if valueProvided {
|
||||
return value, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
value, key, found := lookupActionValue(values, keys...)
|
||||
if !found {
|
||||
return 0, false, nil
|
||||
}
|
||||
intValue, err := intActionValue(map[string]any{key: value}, key)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
return intValue, true, nil
|
||||
}
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -2,7 +2,10 @@ module dappco.re/go/dns
|
|||
|
||||
go 1.22
|
||||
|
||||
require github.com/miekg/dns v1.1.62
|
||||
require (
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,5 +1,7 @@
|
|||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
|
|
|
|||
166
hsd.go
166
hsd.go
|
|
@ -28,6 +28,20 @@ type HSDClientOptions struct {
|
|||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// HSDClientConfiguration is the explicit long-form alias for HSDClientOptions.
|
||||
//
|
||||
// client := dns.NewHSDClientFromConfiguration(dns.HSDClientConfiguration{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
type HSDClientConfiguration = HSDClientOptions
|
||||
|
||||
// HSDClientConfig is kept for compatibility with older call sites.
|
||||
//
|
||||
// Deprecated: use HSDClientConfiguration instead.
|
||||
type HSDClientConfig = HSDClientOptions
|
||||
|
||||
type HSDClient struct {
|
||||
baseURL string
|
||||
username string
|
||||
|
|
@ -93,6 +107,28 @@ func NewHSDClient(options HSDClientOptions) *HSDClient {
|
|||
}
|
||||
}
|
||||
|
||||
// NewHSDClientFromOptions is the explicit long-form constructor name.
|
||||
//
|
||||
// client := dns.NewHSDClientFromOptions(dns.HSDClientOptions{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
func NewHSDClientFromOptions(options HSDClientOptions) *HSDClient {
|
||||
return NewHSDClient(options)
|
||||
}
|
||||
|
||||
// NewHSDClientFromConfiguration is the explicit constructor name for the HSD configuration alias.
|
||||
//
|
||||
// client := dns.NewHSDClientFromConfiguration(dns.HSDClientConfiguration{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
func NewHSDClientFromConfiguration(options HSDClientConfiguration) *HSDClient {
|
||||
return NewHSDClient(options)
|
||||
}
|
||||
|
||||
func (client *HSDClient) GetNameResource(ctx context.Context, name string) (NameRecords, error) {
|
||||
normalized := strings.TrimSpace(name)
|
||||
if normalized == "" {
|
||||
|
|
@ -185,51 +221,127 @@ func (client *HSDClient) call(ctx context.Context, request HSDRPCRequest) (json.
|
|||
}
|
||||
|
||||
func parseHSDNameResource(raw json.RawMessage) (NameRecords, error) {
|
||||
var result NameRecords
|
||||
var wrapper map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &wrapper); err != nil {
|
||||
return result, err
|
||||
return NameRecords{}, err
|
||||
}
|
||||
|
||||
if recordsRaw, ok := wrapper["records"]; ok {
|
||||
if err := json.Unmarshal(recordsRaw, &result); err != nil {
|
||||
result, err := parseHSDNameResourceRecords(recordsRaw)
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if _, ok := wrapper["a"]; ok {
|
||||
if err := json.Unmarshal(raw, &result); err != nil {
|
||||
if hasHSDNameResourceField(wrapper) {
|
||||
result, err := parseHSDNameResourceRecords(raw)
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var wrapped struct {
|
||||
A []string `json:"a"`
|
||||
AAAA []string `json:"aaaa"`
|
||||
TXT []string `json:"txt"`
|
||||
NS []string `json:"ns"`
|
||||
DS []string `json:"ds"`
|
||||
DNSKEY []string `json:"dnskey"`
|
||||
RRSIG []string `json:"rrsig"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &wrapped); err == nil {
|
||||
result = NameRecords{
|
||||
A: wrapped.A,
|
||||
AAAA: wrapped.AAAA,
|
||||
TXT: wrapped.TXT,
|
||||
NS: wrapped.NS,
|
||||
DS: wrapped.DS,
|
||||
DNSKEY: wrapped.DNSKEY,
|
||||
RRSIG: wrapped.RRSIG,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return NameRecords{}, errors.New("unable to parse getnameresource result")
|
||||
}
|
||||
|
||||
func getCaseInsensitiveRecordField(fields map[string]json.RawMessage, key string) json.RawMessage {
|
||||
if fields == nil {
|
||||
return nil
|
||||
}
|
||||
for candidate, value := range fields {
|
||||
if strings.EqualFold(candidate, key) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseHSDNameResourceRecords(raw json.RawMessage) (NameRecords, error) {
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &fields); err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
|
||||
a, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "a"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
aaaa, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "aaaa"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
txt, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "txt"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
ns, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "ns"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
ds, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "ds"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
dnsKey, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "dnskey"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
rrSig, err := parseHSDRecordValue(getCaseInsensitiveRecordField(fields, "rrsig"))
|
||||
if err != nil {
|
||||
return NameRecords{}, err
|
||||
}
|
||||
|
||||
return NameRecords{
|
||||
A: a,
|
||||
AAAA: aaaa,
|
||||
TXT: txt,
|
||||
NS: ns,
|
||||
DS: ds,
|
||||
DNSKEY: dnsKey,
|
||||
RRSIG: rrSig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func hasHSDNameResourceField(fields map[string]json.RawMessage) bool {
|
||||
if len(fields) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return getCaseInsensitiveRecordField(fields, "a") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "aaaa") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "txt") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "ns") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "ds") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "dnskey") != nil ||
|
||||
getCaseInsensitiveRecordField(fields, "rrsig") != nil
|
||||
}
|
||||
|
||||
func parseHSDRecordValue(raw json.RawMessage) ([]string, error) {
|
||||
if len(bytes.TrimSpace(raw)) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if trimmed := bytes.TrimSpace(raw); bytes.Equal(trimmed, []byte("null")) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var values []string
|
||||
if err := json.Unmarshal(raw, &values); err == nil {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
var value string
|
||||
if err := json.Unmarshal(raw, &value); err == nil {
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{value}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to parse DNS record field")
|
||||
}
|
||||
|
||||
func parseHSDBlockchainInfo(raw json.RawMessage) (HSDBlockchainInfo, error) {
|
||||
var info HSDBlockchainInfo
|
||||
var wrapper map[string]json.RawMessage
|
||||
|
|
|
|||
98
hsd_test.go
98
hsd_test.go
|
|
@ -52,6 +52,18 @@ func TestHSDClientGetNameResourceCallsRPCAndParsesResult(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewHSDClientFromOptionsMatchesNewHSDClient(t *testing.T) {
|
||||
clientFromOptions := NewHSDClientFromOptions(HSDClientOptions{URL: "http://127.0.0.1:14037"})
|
||||
clientFromConfiguration := NewHSDClientFromConfiguration(HSDClientConfiguration{URL: "http://127.0.0.1:14037"})
|
||||
|
||||
if clientFromOptions == nil || clientFromConfiguration == nil {
|
||||
t.Fatal("expected explicit HSD constructors to return clients")
|
||||
}
|
||||
if clientFromOptions.baseURL != clientFromConfiguration.baseURL {
|
||||
t.Fatalf("expected explicit HSD constructors to normalize the same base URL, got %q and %q", clientFromOptions.baseURL, clientFromConfiguration.baseURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSDClientGetNameResourceParsesWrappedRecords(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
var payload struct {
|
||||
|
|
@ -92,6 +104,50 @@ func TestHSDClientGetNameResourceParsesWrappedRecords(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHSDClientGetNameResourceParsesCaseInsensitiveFields(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
var payload struct {
|
||||
Method string `json:"method"`
|
||||
}
|
||||
if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("unexpected request payload: %v", err)
|
||||
}
|
||||
if payload.Method != "getnameresource" {
|
||||
t.Fatalf("expected method getnameresource, got %s", payload.Method)
|
||||
}
|
||||
|
||||
responseWriter.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(responseWriter).Encode(map[string]any{
|
||||
"result": map[string]any{
|
||||
"records": map[string]any{
|
||||
"A": []string{"10.10.10.10"},
|
||||
"Txt": []string{"v=lthn1 type=case"},
|
||||
"NS": []string{"ns.example.lthn"},
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewHSDClient(HSDClientOptions{
|
||||
URL: server.URL,
|
||||
})
|
||||
|
||||
record, err := client.GetNameResource(context.Background(), "case.lthn")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected getnameresource error: %v", err)
|
||||
}
|
||||
if len(record.A) != 1 || record.A[0] != "10.10.10.10" {
|
||||
t.Fatalf("unexpected wrapped A result with mixed-case key: %#v", record.A)
|
||||
}
|
||||
if len(record.TXT) != 1 || record.TXT[0] != "v=lthn1 type=case" {
|
||||
t.Fatalf("unexpected wrapped TXT result with mixed-case key: %#v", record.TXT)
|
||||
}
|
||||
if len(record.NS) != 1 || record.NS[0] != "ns.example.lthn" {
|
||||
t.Fatalf("unexpected wrapped NS result with mixed-case key: %#v", record.NS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSDClientGetNameResourceParsesDSRecords(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
var payload struct {
|
||||
|
|
@ -168,6 +224,48 @@ func TestHSDClientGetNameResourceParsesDNSSECRecords(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHSDClientGetNameResourceParsesSingleValueDNSSECRecords(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
var payload struct {
|
||||
Method string `json:"method"`
|
||||
}
|
||||
if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("unexpected request payload: %v", err)
|
||||
}
|
||||
if payload.Method != "getnameresource" {
|
||||
t.Fatalf("expected method getnameresource, got %s", payload.Method)
|
||||
}
|
||||
|
||||
responseWriter.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(responseWriter).Encode(map[string]any{
|
||||
"result": map[string]any{
|
||||
"dnskey": "257 3 13 AA==",
|
||||
"rrsig": "A 8 2 3600 20260101000000 20250101000000 12345 gateway.lthn. AA==",
|
||||
"ds": "60485 8 2 A1B2C3D4E5F60718293A4B5C6D7E8F9012345678",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewHSDClient(HSDClientOptions{
|
||||
URL: server.URL,
|
||||
})
|
||||
|
||||
record, err := client.GetNameResource(context.Background(), "gateway.lthn")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected getnameresource error: %v", err)
|
||||
}
|
||||
if len(record.DNSKEY) != 1 || record.DNSKEY[0] != "257 3 13 AA==" {
|
||||
t.Fatalf("unexpected DNSKEY result: %#v", record.DNSKEY)
|
||||
}
|
||||
if len(record.RRSIG) != 1 || record.RRSIG[0] != "A 8 2 3600 20260101000000 20250101000000 12345 gateway.lthn. AA==" {
|
||||
t.Fatalf("unexpected RRSIG result: %#v", record.RRSIG)
|
||||
}
|
||||
if len(record.DS) != 1 || record.DS[0] != "60485 8 2 A1B2C3D4E5F60718293A4B5C6D7E8F9012345678" {
|
||||
t.Fatalf("unexpected DS result: %#v", record.DS)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHSDClientGetBlockchainInfo(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
|
||||
var payload struct {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ func (server *HealthServer) Address() string {
|
|||
return server.HealthAddress()
|
||||
}
|
||||
|
||||
// StartHealthServer is an explicit convenience wrapper around ServeHTTPHealth.
|
||||
//
|
||||
// health, err := service.StartHealthServer("127.0.0.1", 5554)
|
||||
func (service *Service) StartHealthServer(bind string, port int) (*HealthServer, error) {
|
||||
return service.ServeHTTPHealth(bind, port)
|
||||
}
|
||||
|
||||
func (server *HealthServer) Close() error {
|
||||
if server == nil {
|
||||
return nil
|
||||
|
|
@ -65,7 +72,7 @@ func (service *Service) ServeHTTPHealth(bind string, port int) (*HealthServer, e
|
|||
bind = "127.0.0.1"
|
||||
}
|
||||
if port <= 0 {
|
||||
port = service.resolveHTTPPort()
|
||||
port = service.resolveHealthPort()
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(bind, strconv.Itoa(port))
|
||||
|
|
|
|||
70
mainchain.go
70
mainchain.go
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -25,6 +26,20 @@ type MainchainClientOptions struct {
|
|||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// MainchainAliasClientConfiguration is the explicit long-form alias for MainchainClientOptions.
|
||||
//
|
||||
// client := dns.NewMainchainAliasClientFromConfiguration(dns.MainchainAliasClientConfiguration{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
type MainchainAliasClientConfiguration = MainchainClientOptions
|
||||
|
||||
// MainchainAliasClientConfig is kept for compatibility with older call sites.
|
||||
//
|
||||
// Deprecated: use MainchainAliasClientConfiguration instead.
|
||||
type MainchainAliasClientConfig = MainchainClientOptions
|
||||
|
||||
type MainchainAliasClient struct {
|
||||
baseURL string
|
||||
username string
|
||||
|
|
@ -70,6 +85,28 @@ func NewMainchainAliasClient(options MainchainClientOptions) *MainchainAliasClie
|
|||
}
|
||||
}
|
||||
|
||||
// NewMainchainAliasClientFromOptions is the explicit long-form constructor name.
|
||||
//
|
||||
// client := dns.NewMainchainAliasClientFromOptions(dns.MainchainClientOptions{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
func NewMainchainAliasClientFromOptions(options MainchainClientOptions) *MainchainAliasClient {
|
||||
return NewMainchainAliasClient(options)
|
||||
}
|
||||
|
||||
// NewMainchainAliasClientFromConfiguration is the explicit constructor name for the main-chain configuration alias.
|
||||
//
|
||||
// client := dns.NewMainchainAliasClientFromConfiguration(dns.MainchainAliasClientConfiguration{
|
||||
// URL: "http://127.0.0.1:14037",
|
||||
// Username: "user",
|
||||
// Password: "pass",
|
||||
// })
|
||||
func NewMainchainAliasClientFromConfiguration(options MainchainAliasClientConfiguration) *MainchainAliasClient {
|
||||
return NewMainchainAliasClient(options)
|
||||
}
|
||||
|
||||
// GetAllAliasDetails returns alias names mapped to HNS DNS names.
|
||||
//
|
||||
// client := dns.NewMainchainAliasClient(dns.MainchainClientOptions{
|
||||
|
|
@ -222,19 +259,39 @@ func decodeString(raw json.RawMessage) (string, bool) {
|
|||
}
|
||||
|
||||
func extractAliasFromComment(comment string) string {
|
||||
for _, token := range strings.Fields(comment) {
|
||||
if strings.HasPrefix(token, "hns=") {
|
||||
return normalizeName(strings.TrimSuffix(strings.TrimPrefix(token, "hns="), ";"))
|
||||
fields := strings.Fields(comment)
|
||||
for index, token := range fields {
|
||||
normalizedToken := strings.ToLower(strings.TrimSpace(token))
|
||||
switch {
|
||||
case strings.HasPrefix(normalizedToken, "hns="):
|
||||
alias := strings.TrimSuffix(strings.TrimPrefix(normalizedToken, "hns="), ";")
|
||||
if alias != "" {
|
||||
return normalizeName(alias)
|
||||
}
|
||||
case normalizedToken == "hns" && index+2 < len(fields):
|
||||
nextToken := strings.TrimSpace(strings.ToLower(fields[index+1]))
|
||||
if nextToken != "=" && nextToken != ":" && nextToken != ":=" {
|
||||
continue
|
||||
}
|
||||
alias := strings.TrimSpace(fields[index+2])
|
||||
alias = strings.TrimSuffix(alias, ";")
|
||||
alias = strings.TrimSuffix(alias, ",")
|
||||
if alias != "" {
|
||||
return normalizeName(alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if marker := strings.Index(comment, "hns="); marker >= 0 {
|
||||
alias := comment[marker+4:]
|
||||
if trim := strings.IndexAny(alias, " ;,"); trim >= 0 {
|
||||
commentLower := strings.ToLower(comment)
|
||||
if marker := strings.Index(commentLower, "hns"); marker >= 0 {
|
||||
alias := comment[marker+3:]
|
||||
alias = strings.TrimLeft(alias, " \t:=;")
|
||||
if trim := strings.IndexAny(alias, " ;,\t\r\n"); trim >= 0 {
|
||||
alias = alias[:trim]
|
||||
}
|
||||
alias = strings.TrimSpace(alias)
|
||||
alias = strings.TrimSuffix(alias, ";")
|
||||
alias = strings.TrimSuffix(alias, ",")
|
||||
return normalizeName(alias)
|
||||
}
|
||||
|
||||
|
|
@ -255,5 +312,6 @@ func normalizeAliasList(raw []string) []string {
|
|||
seen[next] = true
|
||||
normalized = append(normalized, next)
|
||||
}
|
||||
slices.Sort(normalized)
|
||||
return normalized
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,18 @@ func TestMainchainClientGetsAliasDetailsAndParsesHNSComments(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewMainchainAliasClientFromOptionsMatchesNewMainchainAliasClient(t *testing.T) {
|
||||
clientFromOptions := NewMainchainAliasClientFromOptions(MainchainClientOptions{URL: "http://127.0.0.1:14037"})
|
||||
clientFromConfiguration := NewMainchainAliasClientFromConfiguration(MainchainAliasClientConfiguration{URL: "http://127.0.0.1:14037"})
|
||||
|
||||
if clientFromOptions == nil || clientFromConfiguration == nil {
|
||||
t.Fatal("expected explicit main-chain constructors to return clients")
|
||||
}
|
||||
if clientFromOptions.baseURL != clientFromConfiguration.baseURL {
|
||||
t.Fatalf("expected explicit main-chain constructors to normalize the same base URL, got %q and %q", clientFromOptions.baseURL, clientFromConfiguration.baseURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDiscoverFromMainchainAliasesUsesMainchainThenHSD(t *testing.T) {
|
||||
var chainCalls int32
|
||||
var hsdTreeRootCalls int32
|
||||
|
|
@ -559,3 +571,48 @@ func TestServiceDiscoverFromMainchainAliasesFallsBackToChainClientWhenPrimaryDis
|
|||
t.Fatalf("expected one tree-root and two name-resource RPC calls, got treeRoot=%d nameResource=%d", atomic.LoadInt32(&hsdTreeRootCalls), atomic.LoadInt32(&hsdAliasCalls))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractAliasFromCommentParsesCaseInsensitiveHNSPrefix(t *testing.T) {
|
||||
got := extractAliasFromComment("gateway alias HNS=gateway.charon.lthn")
|
||||
if got != "gateway.charon.lthn" {
|
||||
t.Fatalf("expected gateway.charon.lthn, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractAliasFromCommentParsesSpacedHNSAssignment(t *testing.T) {
|
||||
got := extractAliasFromComment("gateway alias hns = gateway.charon.lthn;")
|
||||
if got != "gateway.charon.lthn" {
|
||||
t.Fatalf("expected gateway.charon.lthn, got %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServiceBuildsMainchainAliasClientWithHSDFallbackCredentials(t *testing.T) {
|
||||
service := NewService(ServiceOptions{
|
||||
MainchainURL: "http://127.0.0.1:14037",
|
||||
HSDUsername: "mainchain-user",
|
||||
HSDPassword: "mainchain-pass",
|
||||
HSDApiKey: "ignored-token",
|
||||
})
|
||||
|
||||
if service.mainchainAliasClient == nil {
|
||||
t.Fatal("expected default mainchain alias client with fallback credentials")
|
||||
}
|
||||
if got := service.mainchainAliasClient.username; got != "mainchain-user" {
|
||||
t.Fatalf("expected mainchain username to fall back to hsd username, got %q", got)
|
||||
}
|
||||
if got := service.mainchainAliasClient.password; got != "mainchain-pass" {
|
||||
t.Fatalf("expected mainchain password to fall back to hsd password, got %q", got)
|
||||
}
|
||||
|
||||
serviceFromToken := NewService(ServiceOptions{
|
||||
MainchainURL: "http://127.0.0.1:14037",
|
||||
HSDUsername: "token-user",
|
||||
HSDApiKey: "token-pass",
|
||||
})
|
||||
if serviceFromToken.mainchainAliasClient == nil {
|
||||
t.Fatal("expected default mainchain alias client with fallback token credentials")
|
||||
}
|
||||
if got := serviceFromToken.mainchainAliasClient.password; got != "token-pass" {
|
||||
t.Fatalf("expected mainchain password to fall back to hsd api key token, got %q", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
248
serve.go
248
serve.go
|
|
@ -30,23 +30,60 @@ type DNSServer struct {
|
|||
// defer func() { _ = runtime.Close() }()
|
||||
// fmt.Println(runtime.DNSAddress(), runtime.HealthAddress())
|
||||
type ServiceRuntime struct {
|
||||
DNS *DNSServer
|
||||
// DNSServer is the explicit name for the DNS listener.
|
||||
DNSServer *DNSServer
|
||||
// DNS is retained for compatibility with older call sites.
|
||||
DNS *DNSServer
|
||||
// HealthServer is the explicit name for the `/health` listener.
|
||||
HealthServer *HealthServer
|
||||
// Health is retained for compatibility with older call sites.
|
||||
Health *HealthServer
|
||||
// HTTP is retained for compatibility with older call sites.
|
||||
HTTP *HealthServer
|
||||
// HTTPServer is the explicit name for the `/health` listener when the
|
||||
// HTTP surface is addressed directly.
|
||||
HTTPServer *HealthServer
|
||||
}
|
||||
|
||||
// DNSServiceRuntime is the explicit long-form alias for ServiceRuntime.
|
||||
//
|
||||
// runtime, err := service.ServeAll("127.0.0.1", 53, 5554)
|
||||
// var dnsRuntime dns.DNSServiceRuntime = *runtime
|
||||
// fmt.Println(dnsRuntime.DNSAddress(), dnsRuntime.HealthAddress())
|
||||
type DNSServiceRuntime = ServiceRuntime
|
||||
|
||||
// Address is the compatibility alias for HealthAddress.
|
||||
//
|
||||
// runtime, err := service.ServeAll("127.0.0.1", 53, 5554)
|
||||
// defer func() { _ = runtime.Close() }()
|
||||
// fmt.Println(runtime.Address())
|
||||
func (runtime *ServiceRuntime) Address() string {
|
||||
return runtime.HealthAddress()
|
||||
}
|
||||
|
||||
func (runtime *ServiceRuntime) DNSAddress() string {
|
||||
if runtime == nil || runtime.DNS == nil {
|
||||
if runtime == nil {
|
||||
return ""
|
||||
}
|
||||
return runtime.DNS.DNSAddress()
|
||||
if runtime.DNSServer != nil {
|
||||
return runtime.DNSServer.DNSAddress()
|
||||
}
|
||||
if runtime.DNS != nil {
|
||||
return runtime.DNS.DNSAddress()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (runtime *ServiceRuntime) HealthAddress() string {
|
||||
if runtime == nil {
|
||||
return ""
|
||||
}
|
||||
if runtime.HealthServer != nil {
|
||||
return runtime.HealthServer.HealthAddress()
|
||||
}
|
||||
if runtime.HTTPServer != nil {
|
||||
return runtime.HTTPServer.HealthAddress()
|
||||
}
|
||||
if runtime.Health != nil {
|
||||
return runtime.Health.HealthAddress()
|
||||
}
|
||||
|
|
@ -67,17 +104,32 @@ func (runtime *ServiceRuntime) Close() error {
|
|||
}
|
||||
|
||||
var firstError error
|
||||
if runtime.DNS != nil {
|
||||
if runtime.DNSServer != nil {
|
||||
if err := runtime.DNSServer.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if runtime.DNS != nil && runtime.DNS != runtime.DNSServer {
|
||||
if err := runtime.DNS.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if runtime.Health != nil {
|
||||
if runtime.HealthServer != nil {
|
||||
if err := runtime.HealthServer.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if runtime.HTTPServer != nil && runtime.HTTPServer != runtime.HealthServer {
|
||||
if err := runtime.HTTPServer.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if runtime.Health != nil && runtime.Health != runtime.HealthServer {
|
||||
if err := runtime.Health.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if runtime.HTTP != nil && runtime.HTTP != runtime.Health {
|
||||
if runtime.HTTP != nil && runtime.HTTP != runtime.HealthServer && runtime.HTTP != runtime.Health {
|
||||
if err := runtime.HTTP.Close(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
|
|
@ -104,14 +156,18 @@ func (server *DNSServer) Close() error {
|
|||
if server.tcpListener != nil {
|
||||
_ = server.tcpListener.Close()
|
||||
}
|
||||
var err error
|
||||
var firstError error
|
||||
if server.udpServer != nil {
|
||||
err = server.udpServer.Shutdown()
|
||||
if err := server.udpServer.Shutdown(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
if server.tcpServer != nil {
|
||||
err = server.tcpServer.Shutdown()
|
||||
if err := server.tcpServer.Shutdown(); err != nil && firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
return err
|
||||
return firstError
|
||||
}
|
||||
|
||||
// ResolveDNSPort returns the DNS port used for `dns.serve` and `Serve`.
|
||||
|
|
@ -119,22 +175,50 @@ func (server *DNSServer) Close() error {
|
|||
// port := service.ResolveDNSPort()
|
||||
// server, err := service.Serve("127.0.0.1", port)
|
||||
func (service *Service) ResolveDNSPort() int {
|
||||
if service == nil || service.dnsPort <= 0 {
|
||||
if service == nil {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
if service.dnsPort <= 0 {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
return service.dnsPort
|
||||
}
|
||||
|
||||
// DNSListenPort resolves the configured DNS listen port for `dns.serve`.
|
||||
//
|
||||
// port := service.DNSListenPort()
|
||||
// server, err := service.Serve("127.0.0.1", port)
|
||||
func (service *Service) DNSListenPort() int {
|
||||
if service == nil {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
return service.ResolveDNSPort()
|
||||
}
|
||||
|
||||
// DNSPort is an explicit alias for ResolveDNSPort.
|
||||
//
|
||||
// port := service.DNSPort()
|
||||
// server, err := service.Serve("127.0.0.1", port)
|
||||
func (service *Service) DNSPort() int {
|
||||
if service == nil {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
return service.ResolveDNSPort()
|
||||
}
|
||||
|
||||
// resolveServePort keeps internal callers aligned with existing behavior.
|
||||
// resolveDNSListenPort keeps internal callers aligned with explicit naming.
|
||||
func (service *Service) resolveDNSListenPort() int {
|
||||
if service == nil {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
return service.DNSListenPort()
|
||||
}
|
||||
|
||||
// resolveServePort is a legacy compatibility helper.
|
||||
func (service *Service) resolveServePort() int {
|
||||
if service == nil {
|
||||
return DefaultDNSPort
|
||||
}
|
||||
return service.ResolveDNSPort()
|
||||
}
|
||||
|
||||
|
|
@ -143,22 +227,73 @@ func (service *Service) resolveServePort() int {
|
|||
// port := service.ResolveHTTPPort()
|
||||
// healthServer, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) ResolveHTTPPort() int {
|
||||
if service == nil || service.httpPort <= 0 {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// ResolveHealthPort returns the health listener port used by `ServeHTTPHealth`.
|
||||
//
|
||||
// port := service.ResolveHealthPort()
|
||||
// healthServer, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) ResolveHealthPort() int {
|
||||
if service == nil {
|
||||
return DefaultHTTPPort
|
||||
}
|
||||
if service.httpPort <= 0 {
|
||||
return DefaultHTTPPort
|
||||
}
|
||||
return service.httpPort
|
||||
}
|
||||
|
||||
// HTTPListenPort resolves the configured health/listener port for `dns.serve`.
|
||||
//
|
||||
// port := service.HTTPListenPort()
|
||||
// server, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) HTTPListenPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// HealthListenPort is the explicit alias for ResolveHealthPort.
|
||||
//
|
||||
// port := service.HealthListenPort()
|
||||
// server, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) HealthListenPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// HealthPort returns the health listener port used by `ServeHTTPHealth`.
|
||||
//
|
||||
// port := service.HealthPort()
|
||||
// healthServer, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) HealthPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// HTTPPort is an explicit alias for ResolveHTTPPort.
|
||||
//
|
||||
// port := service.HTTPPort()
|
||||
// healthServer, err := service.ServeHTTPHealth("127.0.0.1", port)
|
||||
func (service *Service) HTTPPort() int {
|
||||
return service.ResolveHTTPPort()
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// resolveHTTPListenPort keeps internal callers aligned with explicit naming.
|
||||
func (service *Service) resolveHTTPListenPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// resolveHealthListenPort keeps internal callers aligned with explicit naming.
|
||||
func (service *Service) resolveHealthListenPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// resolveHTTPPort is a legacy compatibility helper.
|
||||
func (service *Service) resolveHTTPPort() int {
|
||||
return service.ResolveHTTPPort()
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// resolveHealthPort is a legacy compatibility helper.
|
||||
func (service *Service) resolveHealthPort() int {
|
||||
return service.ResolveHealthPort()
|
||||
}
|
||||
|
||||
// Serve starts DNS over UDP and TCP.
|
||||
|
|
@ -168,9 +303,15 @@ func (service *Service) resolveHTTPPort() int {
|
|||
// lookup := new(dnsprotocol.Msg)
|
||||
// lookup.SetQuestion("gateway.charon.lthn.", dnsprotocol.TypeA)
|
||||
func (service *Service) Serve(bind string, port int) (*DNSServer, error) {
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("service is required")
|
||||
}
|
||||
if bind == "" {
|
||||
bind = "127.0.0.1"
|
||||
}
|
||||
if port <= 0 {
|
||||
port = service.ResolveDNSPort()
|
||||
}
|
||||
addr := net.JoinHostPort(bind, strconv.Itoa(port))
|
||||
|
||||
udpListener, err := net.ListenPacket("udp", addr)
|
||||
|
|
@ -207,17 +348,41 @@ func (service *Service) Serve(bind string, port int) (*DNSServer, error) {
|
|||
return dnsServer, nil
|
||||
}
|
||||
|
||||
// StartDNSServer is an explicit convenience wrapper around Serve.
|
||||
//
|
||||
// dnsServer, err := service.StartDNSServer("127.0.0.1", 53)
|
||||
func (service *Service) StartDNSServer(bind string, port int) (*DNSServer, error) {
|
||||
return service.Serve(bind, port)
|
||||
}
|
||||
|
||||
// StartDNSAndHealth is an explicit convenience wrapper around ServeAll.
|
||||
//
|
||||
// runtime, err := service.StartDNSAndHealth("127.0.0.1", 53, 5554)
|
||||
func (service *Service) StartDNSAndHealth(bind string, dnsPort int, healthPort int) (*ServiceRuntime, error) {
|
||||
return service.ServeAll(bind, dnsPort, healthPort)
|
||||
}
|
||||
|
||||
// ServeDNSAndHealth is the explicit alias for ServeAll.
|
||||
//
|
||||
// runtime, err := service.ServeDNSAndHealth("127.0.0.1", 53, 5554)
|
||||
func (service *Service) ServeDNSAndHealth(bind string, dnsPort int, httpPort int) (*ServiceRuntime, error) {
|
||||
return service.ServeAll(bind, dnsPort, httpPort)
|
||||
}
|
||||
|
||||
// ServeAll starts DNS and health together.
|
||||
//
|
||||
// runtime, err := service.ServeAll("127.0.0.1", 53, 5554)
|
||||
// defer func() { _ = runtime.Close() }()
|
||||
// fmt.Println("dns:", runtime.DNSAddress(), "health:", runtime.HealthAddress())
|
||||
func (service *Service) ServeAll(bind string, dnsPort int, httpPort int) (*ServiceRuntime, error) {
|
||||
if dnsPort == 0 {
|
||||
dnsPort = service.dnsPort
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("service is required")
|
||||
}
|
||||
if dnsPort <= 0 {
|
||||
dnsPort = service.resolveDNSListenPort()
|
||||
}
|
||||
if httpPort <= 0 {
|
||||
httpPort = service.resolveHTTPPort()
|
||||
httpPort = service.resolveHealthListenPort()
|
||||
}
|
||||
|
||||
dnsServer, err := service.Serve(bind, dnsPort)
|
||||
|
|
@ -232,9 +397,12 @@ func (service *Service) ServeAll(bind string, dnsPort int, httpPort int) (*Servi
|
|||
}
|
||||
|
||||
return &ServiceRuntime{
|
||||
DNS: dnsServer,
|
||||
Health: httpServer,
|
||||
HTTP: httpServer,
|
||||
DNSServer: dnsServer,
|
||||
DNS: dnsServer,
|
||||
HealthServer: httpServer,
|
||||
Health: httpServer,
|
||||
HTTP: httpServer,
|
||||
HTTPServer: httpServer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -246,9 +414,19 @@ func (service *Service) ServeAll(bind string, dnsPort int, httpPort int) (*Servi
|
|||
// })
|
||||
// runtime, err := service.ServeConfigured("127.0.0.1")
|
||||
func (service *Service) ServeConfigured(bind string) (*ServiceRuntime, error) {
|
||||
if service == nil {
|
||||
return nil, fmt.Errorf("service is required")
|
||||
}
|
||||
return service.ServeAll(bind, service.dnsPort, service.httpPort)
|
||||
}
|
||||
|
||||
// StartConfigured is an explicit convenience wrapper around ServeConfigured.
|
||||
//
|
||||
// runtime, err := service.StartConfigured("127.0.0.1")
|
||||
func (service *Service) StartConfigured(bind string) (*ServiceRuntime, error) {
|
||||
return service.ServeConfigured(bind)
|
||||
}
|
||||
|
||||
type dnsRequestHandler struct {
|
||||
service *Service
|
||||
}
|
||||
|
|
@ -285,7 +463,7 @@ func (handler *dnsRequestHandler) ServeDNS(responseWriter dnsprotocol.ResponseWr
|
|||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
for _, value := range record.A {
|
||||
for _, value := range normalizeRecordValues(record.A) {
|
||||
parsedIP := net.ParseIP(value)
|
||||
if parsedIP == nil || parsedIP.To4() == nil {
|
||||
continue
|
||||
|
|
@ -299,7 +477,7 @@ func (handler *dnsRequestHandler) ServeDNS(responseWriter dnsprotocol.ResponseWr
|
|||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
for _, value := range record.AAAA {
|
||||
for _, value := range normalizeRecordValues(record.AAAA) {
|
||||
parsedIP := net.ParseIP(value)
|
||||
if parsedIP == nil || parsedIP.To4() != nil {
|
||||
continue
|
||||
|
|
@ -313,7 +491,7 @@ func (handler *dnsRequestHandler) ServeDNS(responseWriter dnsprotocol.ResponseWr
|
|||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
for _, value := range record.TXT {
|
||||
for _, value := range normalizeRecordValues(record.TXT) {
|
||||
reply.Answer = append(reply.Answer, &dnsprotocol.TXT{
|
||||
Hdr: dnsprotocol.RR_Header{Name: question.Name, Rrtype: dnsprotocol.TypeTXT, Class: dnsprotocol.ClassINET, Ttl: defaultDNSTTL},
|
||||
Txt: []string{value},
|
||||
|
|
@ -321,7 +499,7 @@ func (handler *dnsRequestHandler) ServeDNS(responseWriter dnsprotocol.ResponseWr
|
|||
}
|
||||
case dnsprotocol.TypeNS:
|
||||
if found {
|
||||
for _, value := range record.NS {
|
||||
for _, value := range normalizeRecordValues(record.NS) {
|
||||
reply.Answer = append(reply.Answer, &dnsprotocol.NS{
|
||||
Hdr: dnsprotocol.RR_Header{Name: question.Name, Rrtype: dnsprotocol.TypeNS, Class: dnsprotocol.ClassINET, Ttl: defaultDNSTTL},
|
||||
Ns: normalizeName(value) + ".",
|
||||
|
|
@ -383,17 +561,17 @@ func (handler *dnsRequestHandler) ServeDNS(responseWriter dnsprotocol.ResponseWr
|
|||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeDS, record.DS)
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeDS, normalizeRecordValues(record.DS))
|
||||
case dnsprotocol.TypeDNSKEY:
|
||||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeDNSKEY, record.DNSKEY)
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeDNSKEY, normalizeRecordValues(record.DNSKEY))
|
||||
case dnsprotocol.TypeRRSIG:
|
||||
if !found {
|
||||
goto noRecord
|
||||
}
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeRRSIG, record.RRSIG)
|
||||
appendDNSSECResourceRecords(reply, question.Name, dnsprotocol.TypeRRSIG, normalizeRecordValues(record.RRSIG))
|
||||
default:
|
||||
reply.SetRcode(request, dnsprotocol.RcodeNotImplemented)
|
||||
_ = responseWriter.WriteMsg(reply)
|
||||
|
|
@ -466,7 +644,7 @@ func parsePTRIP(name string) (string, bool) {
|
|||
}
|
||||
|
||||
func appendAnyAnswers(reply *dnsprotocol.Msg, questionName string, lookupName string, record NameRecords, zoneApex string) {
|
||||
for _, value := range record.A {
|
||||
for _, value := range normalizeRecordValues(record.A) {
|
||||
parsedIP := net.ParseIP(value)
|
||||
if parsedIP == nil || parsedIP.To4() == nil {
|
||||
continue
|
||||
|
|
@ -477,7 +655,7 @@ func appendAnyAnswers(reply *dnsprotocol.Msg, questionName string, lookupName st
|
|||
})
|
||||
}
|
||||
|
||||
for _, value := range record.AAAA {
|
||||
for _, value := range normalizeRecordValues(record.AAAA) {
|
||||
parsedIP := net.ParseIP(value)
|
||||
if parsedIP == nil || parsedIP.To4() != nil {
|
||||
continue
|
||||
|
|
@ -488,7 +666,7 @@ func appendAnyAnswers(reply *dnsprotocol.Msg, questionName string, lookupName st
|
|||
})
|
||||
}
|
||||
|
||||
for _, value := range record.TXT {
|
||||
for _, value := range normalizeRecordValues(record.TXT) {
|
||||
reply.Answer = append(reply.Answer, &dnsprotocol.TXT{
|
||||
Hdr: dnsprotocol.RR_Header{Name: questionName, Rrtype: dnsprotocol.TypeTXT, Class: dnsprotocol.ClassINET, Ttl: defaultDNSTTL},
|
||||
Txt: []string{value},
|
||||
|
|
@ -496,7 +674,7 @@ func appendAnyAnswers(reply *dnsprotocol.Msg, questionName string, lookupName st
|
|||
}
|
||||
|
||||
if len(record.NS) > 0 {
|
||||
for _, value := range record.NS {
|
||||
for _, value := range normalizeRecordValues(record.NS) {
|
||||
reply.Answer = append(reply.Answer, &dnsprotocol.NS{
|
||||
Hdr: dnsprotocol.RR_Header{Name: questionName, Rrtype: dnsprotocol.TypeNS, Class: dnsprotocol.ClassINET, Ttl: defaultDNSTTL},
|
||||
Ns: normalizeName(value) + ".",
|
||||
|
|
@ -504,15 +682,15 @@ func appendAnyAnswers(reply *dnsprotocol.Msg, questionName string, lookupName st
|
|||
}
|
||||
}
|
||||
|
||||
for _, value := range record.DNSKEY {
|
||||
for _, value := range normalizeRecordValues(record.DNSKEY) {
|
||||
appendDNSSECResourceRecords(reply, questionName, dnsprotocol.TypeDNSKEY, []string{value})
|
||||
}
|
||||
|
||||
for _, value := range record.DS {
|
||||
for _, value := range normalizeRecordValues(record.DS) {
|
||||
appendDNSSECResourceRecords(reply, questionName, dnsprotocol.TypeDS, []string{value})
|
||||
}
|
||||
|
||||
for _, value := range record.RRSIG {
|
||||
for _, value := range normalizeRecordValues(record.RRSIG) {
|
||||
appendDNSSECResourceRecords(reply, questionName, dnsprotocol.TypeRRSIG, []string{value})
|
||||
}
|
||||
|
||||
|
|
|
|||
755
service.go
755
service.go
File diff suppressed because it is too large
Load diff
1065
service_test.go
1065
service_test.go
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue