gnunet-go

GNUnet Bindings for Go
Log | Files | Refs | README | LICENSE

commit 65f1f9c357307b92a7ee3fc1327f34eb24135667
parent caf624535c2089e01ecc6d4c74f056472b3e54e0
Author: Bernd Fix <brf@hoi-polloi.org>
Date:   Wed, 18 Dec 2019 22:18:28 +0100

MS2-RC2: Handle CNAME records with custom block handler.

Diffstat:
Msrc/gnunet/service/gns/block_handler.go | 172++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/gnunet/service/gns/dns.go | 49+++++++++++++++++++------------------------------
Msrc/gnunet/service/gns/module.go | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Asrc/gnunet/util/misc.go | 33+++++++++++++++++++++++++++++++++
4 files changed, 255 insertions(+), 124 deletions(-)

diff --git a/src/gnunet/service/gns/block_handler.go b/src/gnunet/service/gns/block_handler.go @@ -6,6 +6,7 @@ import ( "gnunet/enums" "gnunet/message" + "gnunet/util" "github.com/bfix/gospel/crypto/ed25519" "github.com/bfix/gospel/logger" @@ -16,17 +17,22 @@ type HdlrInst func(*message.GNSResourceRecord, []string) (BlockHandler, error) // Error codes var ( - ErrInvalidRecordMix = fmt.Errorf("Invalid mix of RR types in block") - ErrBlockHandler = fmt.Errorf("Internal block handler failure") + ErrInvalidRecordType = fmt.Errorf("Invalid resource record type") + ErrInvalidRecordBody = fmt.Errorf("Invalid resource record body") + ErrInvalidPKEY = fmt.Errorf("Invalid PKEY resource record") + ErrInvalidCNAME = fmt.Errorf("Invalid CNAME resource record") + ErrInvalidRecordMix = fmt.Errorf("Invalid mix of RR types in block") + ErrBlockHandler = fmt.Errorf("Internal block handler failure") ) // Mapping of RR types to BlockHandler instanciation functions var ( customHandler = map[int]HdlrInst{ - enums.GNS_TYPE_PKEY: NewPkeyHandler, - enums.GNS_TYPE_GNS2DNS: NewGns2DnsHandler, - enums.GNS_TYPE_BOX: NewBoxHandler, - enums.GNS_TYPE_LEHO: NewLehoHandler, + enums.GNS_TYPE_PKEY: NewPkeyHandler, + enums.GNS_TYPE_GNS2DNS: NewGns2DnsHandler, + enums.GNS_TYPE_BOX: NewBoxHandler, + enums.GNS_TYPE_LEHO: NewLehoHandler, + enums.GNS_TYPE_DNS_CNAME: NewCnameHandler, } ) @@ -39,17 +45,17 @@ var ( // BlockHandler interface. type BlockHandler interface { - // AddRecord inserts a RR into the BlockHandler for (later) processing. - // The handler can inspect the remaining labels in a path if required. - // It returns an error if a record is not accepted by the block handler. + // AddRecord inserts an associated RR into the BlockHandler for (later) + // processing. The handler can inspect the remaining labels in a path + // if required. The method returns an error if a record is not accepted + // by the block handler (RR not of required type). AddRecord(rr *message.GNSResourceRecord, labels []string) error - // TypeAction returns a flag indicating how a resource record of a - // given type is to be treated by a custom block handler: - // = -1: Record is not allowed - // = 0: Record is allowed but will be ignored - // = 1: Record is allowed and will be processed - TypeAction(t int) int + // Coexist checks if a custom block handler can co-exist with other + // resource records in the same block. 'cm' maps the resource type + // to an integer count (how many records of a type are present in the + // GNS block). + Coexist(cm util.CounterMap) bool // Records returns a list of RR of the given types associated with // the custom handler @@ -69,7 +75,8 @@ type BlockHandler interface { // BlockHandlerList is a list of block handlers instantiated. type BlockHandlerList struct { - list map[int]BlockHandler // list of handler instances + list map[int]BlockHandler // list of handler instances + counts util.CounterMap // count number of RRs by type } // NewBlockHandlerList instantiates an a list of active block handlers @@ -77,24 +84,21 @@ type BlockHandlerList struct { func NewBlockHandlerList(records []*message.GNSResourceRecord, labels []string) (*BlockHandlerList, error) { // initialize block handler list hl := &BlockHandlerList{ - list: make(map[int]BlockHandler), + list: make(map[int]BlockHandler), + counts: make(util.CounterMap), } - // build a list of record types that are handled by a custom handler. - rrList := NewRRTypeList( - enums.GNS_TYPE_PKEY, - enums.GNS_TYPE_GNS2DNS, - enums.GNS_TYPE_BOX, - enums.GNS_TYPE_LEHO) // Traverse record list and build list of handler instances for _, rec := range records { - // check for custom handler type + // update counter map rrType := int(rec.Type) - if rrList.HasType(rrType) { + hl.counts.Add(rrType) + + // check for custom handler type + if creat, ok := customHandler[rrType]; ok { // check if a handler for given type already exists var ( hdlr BlockHandler - ok bool err error ) if hdlr, ok = hl.list[rrType]; ok { @@ -105,23 +109,22 @@ func NewBlockHandlerList(records []*message.GNSResourceRecord, labels []string) continue } // create a new handler instance - switch rrType { - case enums.GNS_TYPE_PKEY: - hdlr, err = NewPkeyHandler(rec, labels) - case enums.GNS_TYPE_GNS2DNS: - hdlr, err = NewGns2DnsHandler(rec, labels) - case enums.GNS_TYPE_BOX: - hdlr, err = NewBoxHandler(rec, labels) - case enums.GNS_TYPE_LEHO: - hdlr, err = NewLehoHandler(rec, labels) - } - if err != nil { + if hdlr, err = creat(rec, labels); err != nil { return nil, err } // store handler in list hl.list[rrType] = hdlr } } + + // Check if all registered handlers in list can co-exist with + // all the other records of varying type + for _, hdlr := range hl.list { + if !hdlr.Coexist(hl.counts) { + return nil, ErrInvalidRecordMix + } + } + // return assembled handler list return hl, nil } @@ -179,14 +182,11 @@ func (h *PkeyHandler) AddRecord(rec *message.GNSResourceRecord, labels []string) return nil } -// TypeAction return a flag indicating how a resource record of a given type +// Coexist return a flag indicating how a resource record of a given type // is to be treated (see BlockHandler interface) -func (h *PkeyHandler) TypeAction(t int) int { - // no other resource record type is not allowed - if t == enums.GNS_TYPE_PKEY { - return 1 - } - return -1 +func (h *PkeyHandler) Coexist(cm util.CounterMap) bool { + // only one type (GNS_TYPE_PKEY) is present + return len(cm) == 1 && cm.Num(enums.GNS_TYPE_PKEY) == 1 } // Records returns a list of RR of the given type associated with this handler @@ -252,11 +252,11 @@ func (h *Gns2DnsHandler) AddRecord(rec *message.GNSResourceRecord, labels []stri return nil } -// TypeAction return a flag indicating how a resource record of a given type +// Coexist return a flag indicating how a resource record of a given type // is to be treated (see BlockHandler interface) -func (h *Gns2DnsHandler) TypeAction(t int) int { - // anything goes... - return 1 +func (h *Gns2DnsHandler) Coexist(cm util.CounterMap) bool { + // only one type (GNS_TYPE_GNS2DNS) is present + return len(cm) == 1 && cm.Num(enums.GNS_TYPE_GNS2DNS) > 0 } // Records returns a list of RR of the given type associated with this handler @@ -317,11 +317,11 @@ func (h *BoxHandler) AddRecord(rec *message.GNSResourceRecord, labels []string) return nil } -// TypeAction return a flag indicating how a resource record of a given type +// Coexist return a flag indicating how a resource record of a given type // is to be treated (see BlockHandler interface) -func (h *BoxHandler) TypeAction(t int) int { +func (h *BoxHandler) Coexist(cm util.CounterMap) bool { // anything goes... - return 1 + return true } // Records returns a list of RR of the given type associated with this handler @@ -376,16 +376,17 @@ func (h *LehoHandler) AddRecord(rec *message.GNSResourceRecord, labels []string) return nil } -// TypeAction return a flag indicating how a resource record of a given type +// Coexist return a flag indicating how a resource record of a given type // is to be treated (see BlockHandler interface) -func (h *LehoHandler) TypeAction(t int) int { - // only A and AAAA records allowed beside LEHO - switch t { - case enums.GNS_TYPE_LEHO, enums.GNS_TYPE_DNS_A, enums.GNS_TYPE_DNS_AAAA: - return 1 - default: - return -1 +func (h *LehoHandler) Coexist(cm util.CounterMap) bool { + // requires exactly one A/AAAA record alongside single LEHO + if len(cm) != 2 { + return false } + if cm.Num(enums.GNS_TYPE_LEHO) != 1 { + return false + } + return cm.Num(enums.GNS_TYPE_DNS_A) == 1 || cm.Num(enums.GNS_TYPE_DNS_AAAA) == 1 } // Records returns a list of RR of the given type associated with this handler @@ -396,3 +397,56 @@ func (h *LehoHandler) Records(kind RRTypeList) *GNSRecordSet { } return rs } + +//---------------------------------------------------------------------- +// CNAME handler +//---------------------------------------------------------------------- + +// CnameHandler implementing the BlockHandler interface +type CnameHandler struct { + name string + rec *message.GNSResourceRecord +} + +// NewCnameHandler returns a new BlockHandler instance +func NewCnameHandler(rec *message.GNSResourceRecord, labels []string) (BlockHandler, error) { + if int(rec.Type) != enums.GNS_TYPE_DNS_CNAME { + return nil, ErrInvalidRecordType + } + h := &CnameHandler{ + name: "", + } + if err := h.AddRecord(rec, labels); err != nil { + return nil, err + } + return h, nil +} + +// AddRecord inserts a CNAME record into the handler. +func (h *CnameHandler) AddRecord(rec *message.GNSResourceRecord, labels []string) error { + if int(rec.Type) != enums.GNS_TYPE_DNS_CNAME { + return ErrInvalidRecordType + } + if h.rec != nil { + return ErrInvalidCNAME + } + _, h.name = DNSNameFromBytes(rec.Data, 0) + h.rec = rec + return nil +} + +// Coexist return a flag indicating how a resource record of a given type +// is to be treated (see BlockHandler interface) +func (h *CnameHandler) Coexist(cm util.CounterMap) bool { + // anything goes + return true +} + +// Records returns a list of RR of the given type associated with this handler +func (h *CnameHandler) Records(kind RRTypeList) *GNSRecordSet { + rs := NewGNSRecordSet() + if kind.HasType(enums.GNS_TYPE_DNS_CNAME) { + rs.AddRecord(h.rec) + } + return rs +} diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go @@ -45,9 +45,11 @@ func DNSNameFromBytes(b []byte, offset int) (int, string) { return pos + 1, str } -// queryDNS resolves a name on a given nameserver and delivers all matching -// resource record (of type 'kind') to the result channel. -func queryDNS(id int, name string, server net.IP, kind RRTypeList, res chan *GNSRecordSet) { +func QueryDNS(id int, name string, server net.IP, kind RRTypeList) *GNSRecordSet { + // get default nameserver if not defined. + if server == nil { + server = net.IPv4(8, 8, 8, 8) + } logger.Printf(logger.DBG, "[dns][%d] Starting query for '%s' on '%s'...\n", id, name, server.String()) // assemble query @@ -80,14 +82,13 @@ func queryDNS(id int, name string, server net.IP, kind RRTypeList, res chan *GNS continue } logger.Printf(logger.ERROR, "[dns][%d] Error: %s\n", id, errMsg) - res <- nil + return nil } // process results logger.Printf(logger.WARN, "[dns][%d] Response from DNS server received (%d/5).\n", id, retry+1) if in == nil { logger.Printf(logger.ERROR, "[dns][%d] No results\n", id) - res <- nil - return + return nil } set := NewGNSRecordSet() for _, record := range in.Answer { @@ -118,12 +119,11 @@ func queryDNS(id int, name string, server net.IP, kind RRTypeList, res chan *GNS set.AddRecord(rr) } } - logger.Printf(logger.WARN, "[dns][%d] %d resource records extracted from response (%d/5).\n", id, set.Count, retry+1) - res <- set - return + logger.Printf(logger.INFO, "[dns][%d] %d resource records extracted from response (%d/5).\n", id, set.Count, retry+1) + return set } logger.Printf(logger.WARN, "[dns][%d] Resolution failed -- giving up...\n", id) - res <- nil + return nil } // ResolveDNS resolves a name in DNS. Multiple DNS servers are queried in @@ -135,29 +135,16 @@ func (gns *GNSModule) ResolveDNS(name string, servers []string, kind RRTypeList, // start DNS queries concurrently res := make(chan *GNSRecordSet) running := 0 - for idx, srv := range servers { + for _, srv := range servers { // check if srv is an IPv4/IPv6 address addr := net.ParseIP(srv) logger.Printf(logger.DBG, "ParseIP('%s', len=%d) --> %v\n", srv, len(srv), addr) if addr == nil { + // no, it is a name... try to resolve an IP address from the name query := NewRRTypeList(enums.GNS_TYPE_DNS_A, enums.GNS_TYPE_DNS_AAAA) - // no; resolve server name in GNS - if strings.HasSuffix(srv, ".+") { - // resolve server name relative to current zone - zone := util.EncodeBinaryToString(pkey.Bytes()) - srv = strings.TrimSuffix(srv, ".+") - set, err = gns.Resolve(srv, pkey, query, enums.GNS_LO_DEFAULT) - if err != nil { - logger.Printf(logger.ERROR, "[dns] Can't resolve NS server '%s' in '%s'\n", srv, zone) - continue - } - } else { - // resolve absolute GNS name (name MUST end in a PKEY) - set, err = gns.Resolve(srv, nil, query, enums.GNS_LO_DEFAULT) - if err != nil { - logger.Printf(logger.ERROR, "[dns] Can't resolve NS server '%s'\n", srv) - continue - } + if set, err = gns.ResolveUnknown(srv, pkey, query); err != nil { + logger.Printf(logger.ERROR, "[dns] Can't resolve NS server '%s': %s\n", srv, err.Error()) + continue } // traverse resource records for 'A' and 'AAAA' records. rec_loop: @@ -173,12 +160,14 @@ func (gns *GNSModule) ResolveDNS(name string, servers []string, kind RRTypeList, } // check if we have an IP address available if addr == nil { - logger.Printf(logger.WARN, "[dns] No IP address for nameserver in GNS") + logger.Printf(logger.WARN, "[dns] No IP address for nameserver '%s'\n", srv) continue } } // query DNS concurrently - go queryDNS(idx, name, addr, kind, res) + go func() { + res <- QueryDNS(util.NextID(), name, addr, kind) + }() running++ } // check if we started some queries at all. diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go @@ -20,10 +20,7 @@ import ( // Error codes var ( - ErrUnknownTLD = fmt.Errorf("Unknown TLD in name") - ErrInvalidRecordType = fmt.Errorf("Invalid resource record type") - ErrInvalidRecordBody = fmt.Errorf("Invalid resource record body") - ErrInvalidPKEY = fmt.Errorf("Invalid PKEY resource record") + ErrUnknownTLD = fmt.Errorf("Unknown TLD in name") ) //---------------------------------------------------------------------- @@ -83,7 +80,7 @@ func NewQuery(pkey *ed25519.PublicKey, label string) *Query { // (d) if rec is CNAME record: // if no remaining labels: // if requested types include CNAME -> (5) -// if +// -> Resolve(CNAME) // resolution failed: name not completely processed and no zone available // // (5) return records: it is the responsibility of the caller to assemble @@ -119,30 +116,23 @@ func (gns *GNSModule) Resolve(path string, pkey *ed25519.PublicKey, kind RRTypeL // Resolve a fully qualified GNS absolute name (with multiple labels). func (gns *GNSModule) ResolveAbsolute(labels []string, kind RRTypeList, mode int) (set *GNSRecordSet, err error) { - // get the root zone key for the TLD - var ( - pkey *ed25519.PublicKey - data []byte - ) - for { - // (1) check if TLD is a public key string - if len(labels[0]) == 52 { - if data, err = util.DecodeStringToBinary(labels[0], 32); err == nil { - if pkey = ed25519.NewPublicKeyFromBytes(data); pkey != nil { - break - } - } - } + // get the zone key for the TLD + // (1) check if TLD is a PKEY + pkey := gns.GetZoneKey(labels[0]) + if pkey == nil { // (2) check if TLD is in our local config - if pkey = config.Cfg.GNS.GetRootZoneKey(labels[0]); pkey != nil { - break - } + pkey = config.Cfg.GNS.GetRootZoneKey(labels[0]) + } + if pkey == nil { // (3) check if TLD is one of our identities - if pkey, err = gns.GetLocalZone(labels[0]); err == nil { - break + pkey, err = gns.GetLocalZone(labels[0]) + } + if pkey == nil { + if err == nil { + err = ErrUnknownTLD } // (4) we can't resolve this TLD - return nil, ErrUnknownTLD + return } // continue with resolution relative to a zone. return gns.ResolveRelative(labels[1:], pkey, kind, mode) @@ -156,7 +146,6 @@ func (gns *GNSModule) ResolveRelative(labels []string, pkey *ed25519.PublicKey, records []*message.GNSResourceRecord // final resource records from resolution hdlrs *BlockHandlerList // list of block handlers in final step ) -name_loop: for ; len(labels) > 0; labels = labels[1:] { logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in '%s'\n", labels[0], util.EncodeBinaryToString(pkey.Bytes())) @@ -192,16 +181,24 @@ name_loop: if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_PKEY); hdlr != nil { // (1) PKEY record: inst := hdlr.(*PkeyHandler) - // if labels are pending, set new zone and continue resolution - if len(labels) > 1 { - pkey = inst.pkey - continue name_loop + // if labels are pending, set new zone and continue resolution; + // otherwise resolve "@" label for the zone if no PKEY record + // was requested. + pkey = inst.pkey + if len(labels) == 1 && !kind.HasType(enums.GNS_TYPE_PKEY) { + labels = append(labels, "@") } } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); hdlr != nil { - // (2) GNS2DNS records: delegate resolution to DNS + // (2) GNS2DNS records inst := hdlr.(*Gns2DnsHandler) - // we need to handle delegation to DNS: returns a list of found - // resource records in DNS (filter by 'kind') + // if we are at the end of the path and the requested type + // includes GNS_TYPE_GNS2DNS, the GNS2DNS records are returned... + if len(labels) == 1 && kind.HasType(enums.GNS_TYPE_GNS2DNS) { + records = inst.recs + break + } + // ... otherwise we need to handle delegation to DNS: returns a + // list of found resource records in DNS (filter by 'kind') lbls := strings.Join(util.ReverseStringList(labels[1:]), ".") if len(lbls) > 0 { lbls += "." @@ -213,15 +210,30 @@ name_loop: } // we are done with resolution; pass on records to caller records = set.Records - break name_loop + break } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_BOX); hdlr != nil { // (3) BOX records: inst := hdlr.(*BoxHandler) new_records := inst.Records(kind).Records if len(new_records) > 0 { records = new_records - break name_loop + break + } + } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_DNS_CNAME); hdlr != nil { + // (4) CNAME records: + inst := hdlr.(*CnameHandler) + // if we are at the end of the path and the requested type + // includes GNS_TYPE_DNS_CNAME, the records are returned... + if len(labels) == 1 && kind.HasType(enums.GNS_TYPE_DNS_CNAME) { + break } + if set, err = gns.ResolveUnknown(inst.name, pkey, kind); err != nil { + logger.Println(logger.ERROR, "[gns] CNAME resolution failed.") + return + } + // we are done with resolution; pass on records to caller + records = set.Records + break } } // Assemble resulting resource record set by filtering for requested types. @@ -237,6 +249,49 @@ name_loop: return } +// ResolveUnknown resolves a name either in GNS (if applicable) or DNS: +// If the name is a relative GNS path (ending in ".+"), it is resolved in GNS +// relative to the zone PKEY. If the name is an absolute GNS name (ending in +// a PKEY TLD), it is also resolved with GNS. All other names are resolved +// via DNS queries. +func (gns *GNSModule) ResolveUnknown(name string, pkey *ed25519.PublicKey, kind RRTypeList) (set *GNSRecordSet, err error) { + // relative GNS-based server name? + if strings.HasSuffix(name, ".+") { + // resolve server name relative to current zone + name = strings.TrimSuffix(name, ".+") + if set, err = gns.Resolve(name, pkey, kind, enums.GNS_LO_DEFAULT); err != nil { + return + } + } else { + // check for absolute GNS name (with PKEY as TLD) + if zk := gns.GetZoneKey(name); zk != nil { + // resolve absolute GNS name (name ends in a PKEY) + if set, err = gns.Resolve(util.StripPathRight(name), zk, kind, enums.GNS_LO_DEFAULT); err != nil { + return + } + } else { + // resolve the server name via DNS + if set = QueryDNS(util.NextID(), name, nil, kind); set == nil { + err = ErrNoDNSResults + } + } + } + return +} + +// GetZoneKey returns the PKEY (or nil) from an absolute GNS path. +func (gns *GNSModule) GetZoneKey(path string) *ed25519.PublicKey { + labels := util.ReverseStringList(strings.Split(path, ".")) + if len(labels[0]) == 52 { + if data, err := util.DecodeStringToBinary(labels[0], 32); err == nil { + if pkey := ed25519.NewPublicKeyFromBytes(data); pkey != nil { + return pkey + } + } + } + return nil +} + // Lookup name in GNS. func (gns *GNSModule) Lookup(pkey *ed25519.PublicKey, label string, remote bool) (block *GNSBlock, err error) { diff --git a/src/gnunet/util/misc.go b/src/gnunet/util/misc.go @@ -0,0 +1,33 @@ +package util + +import ( + "strings" +) + +type CounterMap map[interface{}]int + +func (cm CounterMap) Add(i interface{}) int { + count, ok := cm[i] + if !ok { + count = 1 + } else { + count++ + } + cm[i] = count + return count +} + +func (cm CounterMap) Num(i interface{}) int { + count, ok := cm[i] + if !ok { + count = 0 + } + return count +} + +func StripPathRight(s string) string { + if idx := strings.LastIndex(s, "."); idx != -1 { + return s[:idx] + } + return s +}