[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnunet-go] branch master updated: Started recursive resolution; GNS2DNS
From: |
gnunet |
Subject: |
[gnunet-go] branch master updated: Started recursive resolution; GNS2DNS implemented. |
Date: |
Tue, 12 Nov 2019 12:35:32 +0100 |
This is an automated email from the git hooks/post-receive script.
bernd-fix pushed a commit to branch master
in repository gnunet-go.
The following commit(s) were added to refs/heads/master by this push:
new fef7570 Started recursive resolution; GNS2DNS implemented.
fef7570 is described below
commit fef7570713f8ec064b7689f28f201deb0b5030d4
Author: Bernd Fix <address@hidden>
AuthorDate: Tue Nov 12 12:31:44 2019 +0100
Started recursive resolution; GNS2DNS implemented.
---
README.md | 34 +++-
src/cmd/vanityid/main.go | 11 +-
src/gnunet/config/config.go | 19 +-
src/gnunet/config/gnunet-config.json | 5 +-
src/gnunet/crypto/symmetric.go | 20 ++
src/gnunet/modules.go | 40 ++++
src/gnunet/service/gns/crypto.go | 27 +--
src/gnunet/service/gns/dns.go | 198 +++++++++++++++++++
src/gnunet/service/gns/module.go | 342 +++++++++++++++++++++++++++++++++
src/gnunet/service/gns/record.go | 5 +
src/gnunet/service/gns/service.go | 257 +++++++++++++++++++++++++
src/gnunet/service/namecache/module.go | 25 +++
src/gnunet/util/array.go | 39 ++++
src/gnunet/util/time.go | 17 ++
test.sh | 3 +
15 files changed, 1015 insertions(+), 27 deletions(-)
diff --git a/README.md b/README.md
index d0f1b9c..1875501 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ DOCUMENTATION OR COMPILABLE, RUNNABLE OR EVEN OPERATIONAL
SOURCE CODE.
## Source code
-All source code is written in Golang (version 1.11+).
+All source code is written in Golang (version 1.13+).
### Dependencies
@@ -38,19 +38,43 @@ $ go get -u github.com/bfix/gospel/...
### ./src/cmd folder
-* `vanityid`: Compute GNUnet vanity peer id for a given regexp pattern.
+
+#### `gnunet-service-gns-go`: Implementation of the GNS service.
+
+#### `peer_mockup`: test message exchange on the lowest level (transport).
+
+#### `vanityid`: Compute GNUnet vanity peer and ego id for a given regexp
pattern.
+
+N.B.: Key generation is slow at the moment, so be patient! To generate a single
+matching key some 1,000,000 keys need to be generated for a four letter prefix;
+this can take more than 30 minutes on average (depending on your CPU).
```bash
$ vanityid "^TST[0-9]"
```
-* `gnunet-service-gns-go`: Implementation of the GNS service.
+Keys matching the pattern are printed to the console in the following format:
-* `peer_mockup`: test message exchange on the lowest level (transport).
+```bash
+<vanity_id> [<hex.seed>][<hex.scalar>] (<count> tries, <time> elapsed)
+```
+The value of `count` tells how many key had been generated before a match was
+found; `time` is the time needed to find a match.
+
+To generate the key files, make sure GNUnet **is not running** and do:
+```bash
+$ # use a vanity peer id:
+$ echo "<hex.seed>" | xxd -r -p >
/var/lib/gnunet/.local/share/gnunet/private_key.ecc
+$ sudo chown gnunet:gnunet /var/lib/gnunet/.local/share/gnunet/private_key.ecc
+$ sudo chmod 600 /var/lib/gnunet/.local/share/gnunet/private_key.ecc
+$ # use a vanity ego id:
+$ echo "<hex.scalar>" | xxd -r -p >
~/.local/share/gnunet/identity/egos/<vanity_ego>
+$ chmod 600 ~/.local/share/gnunet/identity/egos/<vanity_ego>
+```
### ./src/gnunet folder
-Packages used to implement GNUnet protocols (currently only TRANSPORT
+Packages used to implement GNUnet protocols (currently only some of TRANSPORT
and GNS).
## Documentation
diff --git a/src/cmd/vanityid/main.go b/src/cmd/vanityid/main.go
index 0b9fb18..938df61 100644
--- a/src/cmd/vanityid/main.go
+++ b/src/cmd/vanityid/main.go
@@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"regexp"
+ "time"
"github.com/bfix/gospel/crypto/ed25519"
"gnunet/util"
@@ -29,7 +30,8 @@ func main() {
// generate new keys in a loop
seed := make([]byte, 32)
- for {
+ start := time.Now()
+ for i := 0; ; i++ {
n, err := rand.Read(seed)
if err != nil || n != 32 {
panic(err)
@@ -39,7 +41,12 @@ func main() {
id := util.EncodeBinaryToString(pub)
for _, r := range reg {
if r.MatchString(id) {
- fmt.Printf("%s [%s]\n", id,
hex.EncodeToString(seed))
+ elapsed := time.Now().Sub(start)
+ s1 := hex.EncodeToString(seed)
+ s2 := hex.EncodeToString(prv.D.Bytes())
+ fmt.Printf("%s [%s][%s] (%d tries, %s
elapsed)\n", id, s1, s2, i, elapsed)
+ i = 0
+ start = time.Now()
}
}
}
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go
index 74732ab..1a53941 100644
--- a/src/gnunet/config/config.go
+++ b/src/gnunet/config/config.go
@@ -7,7 +7,9 @@ import (
"regexp"
"strings"
+ "github.com/bfix/gospel/crypto/ed25519"
"github.com/bfix/gospel/logger"
+ "gnunet/util"
)
///////////////////////////////////////////////////////////////////////
@@ -15,8 +17,21 @@ import (
// GNSConfig
type GNSConfig struct {
- Endpoint string `json:"endpoint"` // end-point of GNS service
- DHTReplLevel int `json:"dhtReplLevel"` // DHT replication level
+ Endpoint string `json:"endpoint"` // end-point of
GNS service
+ DHTReplLevel int `json:"dhtReplLevel"` // DHT replication
level
+ RootZones map[string]string `json:"rootZones"` // pre-configured
root zones
+}
+
+// GetRootZoneKey returns the zone key (PKEY) for a pre-configured root with
given name.
+func (gns *GNSConfig) GetRootZoneKey(name string) *ed25519.PublicKey {
+ // lookup key in the dictionary
+ if dStr, ok := gns.RootZones[name]; ok {
+ if data, err := util.DecodeStringToBinary(dStr, 32); err == nil
{
+ return ed25519.NewPublicKeyFromBytes(data)
+ }
+ }
+ // no pkey found.
+ return nil
}
///////////////////////////////////////////////////////////////////////
diff --git a/src/gnunet/config/gnunet-config.json
b/src/gnunet/config/gnunet-config.json
index 7ea854c..13bbe7c 100644
--- a/src/gnunet/config/gnunet-config.json
+++ b/src/gnunet/config/gnunet-config.json
@@ -8,7 +8,10 @@
},
"gns": {
"endpoint":
"unix+${RT_SYS}/gnunet-service-gns-go.sock+perm=0770",
- "dhtReplLevel": 10
+ "dhtReplLevel": 10,
+ "rootZones": {
+ "home":
"ACAB23DC3SEECJORPHQNVRH965A6N74B1M37S721IG4RBQ15PJLL"
+ }
},
"namecache": {
"endpoint": "unix+${RT_SYS}/gnunet-service-namecache.sock"
diff --git a/src/gnunet/crypto/symmetric.go b/src/gnunet/crypto/symmetric.go
index 5fb2c72..82116a4 100644
--- a/src/gnunet/crypto/symmetric.go
+++ b/src/gnunet/crypto/symmetric.go
@@ -57,3 +57,23 @@ func SymmetricDecrypt(data []byte, skey *SymmetricKey, iv
*SymmetricIV) ([]byte,
stream.XORKeyStream(out, out)
return out, nil
}
+
+func SymmetricEncrypt(data []byte, skey *SymmetricKey, iv *SymmetricIV)
([]byte, error) {
+ // Encrypt with AES CFB stream cipher
+ aes, err := aes.NewCipher(skey.AESKey)
+ if err != nil {
+ return nil, err
+ }
+ stream := cipher.NewCFBEncrypter(aes, iv.AESIv)
+ out := make([]byte, len(data))
+ stream.XORKeyStream(out, data)
+
+ // Encrypt with Twofish CFB stream cipher
+ tf, err := twofish.NewCipher(skey.TwofishKey)
+ if err != nil {
+ return nil, err
+ }
+ stream = cipher.NewCFBEncrypter(tf, iv.TwofishIv)
+ stream.XORKeyStream(out, out)
+ return out, nil
+}
diff --git a/src/gnunet/modules.go b/src/gnunet/modules.go
new file mode 100644
index 0000000..bbb2563
--- /dev/null
+++ b/src/gnunet/modules.go
@@ -0,0 +1,40 @@
+//======================================================================
+// Standalone (all-in-one) implementation of GNUnet:
+// -------------------------------------------------
+// Instead of running GNUnet services like GNS or DHT in separate
+// processes communicating (exchanging messages) with each other over
+// Unix Domain Sockets, the standalone implementation combines all
+// service modules into a single binary running go-routines to
+// concurrently performing their tasks.
+//======================================================================
+
+package gnunet
+
+import (
+ "gnunet/service/gns"
+ "gnunet/service/namecache"
+)
+
+// List of all GNUnet service module instances
+type Instances struct {
+ GNS *gns.GNSModule
+ Namecache *namecache.NamecacheModule
+}
+
+// Local reference to instance list
+var (
+ Modules Instances
+)
+
+// Initialize instance list and link module functions as required.
+func init() {
+
+ // Namecache (no calls to other modules)
+ Modules.Namecache = new(namecache.NamecacheModule)
+
+ // GNS (calls Namecache, DHT and Identity)
+ Modules.GNS = &gns.GNSModule{
+ LookupLocal: Modules.Namecache.Get,
+ StoreLocal: Modules.Namecache.Put,
+ }
+}
diff --git a/src/gnunet/service/gns/crypto.go b/src/gnunet/service/gns/crypto.go
index f7ef111..38f25fb 100644
--- a/src/gnunet/service/gns/crypto.go
+++ b/src/gnunet/service/gns/crypto.go
@@ -9,23 +9,8 @@ import (
"golang.org/x/crypto/hkdf"
)
-// QueryFromPublickeyDerive calculates the DHT query for a given label in a
-// given zone (identified by PKEY).
-func QueryFromPublickeyDerive(pkey *ed25519.PublicKey, label string)
*crypto.HashCode {
- pd := crypto.DerivePublicKey(pkey, label, "gns")
- return crypto.Hash(pd.Bytes())
-}
-
-// DecryptBlock
-func DecryptBlock(data []byte, zoneKey *ed25519.PublicKey, label string) (out
[]byte, err error) {
- // derive key material for decryption
- iv, skey := deriveBlockKey(label, zoneKey)
- // perform decryption
- return crypto.SymmetricDecrypt(data, skey, iv)
-}
-
-// Derive a symmetric key to decipher a GNS block
-func deriveBlockKey(label string, pub *ed25519.PublicKey) (iv
*crypto.SymmetricIV, skey *crypto.SymmetricKey) {
+// DeriveBlockKey returns a symmetric key to decipher a GNS block
+func DeriveBlockKey(label string, pub *ed25519.PublicKey) (iv
*crypto.SymmetricIV, skey *crypto.SymmetricKey) {
// generate symmetric key
prk := hkdf.Extract(sha512.New, []byte(label), pub.Bytes())
rdr := hkdf.Expand(sha256.New, prk, []byte("gns-aes-ctx-key"))
@@ -39,3 +24,11 @@ func deriveBlockKey(label string, pub *ed25519.PublicKey)
(iv *crypto.SymmetricI
rdr.Read(iv.TwofishIv)
return
}
+
+// DecryptBlock for a given zone and label.
+func DecryptBlock(data []byte, zoneKey *ed25519.PublicKey, label string) (out
[]byte, err error) {
+ // derive key material for decryption
+ iv, skey := DeriveBlockKey(label, zoneKey)
+ // perform decryption
+ return crypto.SymmetricDecrypt(data, skey, iv)
+}
diff --git a/src/gnunet/service/gns/dns.go b/src/gnunet/service/gns/dns.go
new file mode 100644
index 0000000..2ebe331
--- /dev/null
+++ b/src/gnunet/service/gns/dns.go
@@ -0,0 +1,198 @@
+package gns
+
+import (
+ "fmt"
+ "net"
+ "strings"
+ "time"
+
+ "gnunet/enums"
+ "gnunet/message"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/crypto/ed25519"
+ "github.com/bfix/gospel/logger"
+ "github.com/miekg/dns"
+)
+
+// Error codes
+var (
+ ErrDNSTimedOut = fmt.Errorf("DNS query timed out")
+ ErrNoDNSQueries = fmt.Errorf("No valid DNS queries")
+ ErrNoDNSResults = fmt.Errorf("No valid DNS results")
+)
+
+// Convert DNS name from its binary representation [RFC1034]:
+// A string is a sequence of a (len,chars...) tupels terminated by a (len=0,).
+// The name parts are concatenated with "." as separator.
+// The parsing starts at offset in the byte array; the function returns the
+// offset after the parsed name as well as the name itself.
+func DNSNameFromBytes(b []byte, offset int) (int, string) {
+ if offset >= len(b) {
+ return offset, ""
+ }
+ str := ""
+ pos := offset
+ for b[pos] != 0 {
+ if len(str) > 0 {
+ str += "."
+ }
+ count := int(b[pos])
+ pos++
+ str += string(b[pos : pos+count])
+ pos += count
+ }
+ 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 int, res chan
*GNSRecordSet) {
+ logger.Printf(logger.DBG, "[dns][%d] Starting query for '%s' on
'%s'...\n", id, name, server.String())
+
+ // assemble query
+ m := &dns.Msg{
+ MsgHdr: dns.MsgHdr{
+ Authoritative: true,
+ AuthenticatedData: false,
+ CheckingDisabled: false,
+ RecursionDesired: true,
+ Opcode: dns.OpcodeQuery,
+ },
+ Question: make([]dns.Question, 1),
+ }
+ m.Question[0] = dns.Question{
+ dns.Fqdn(name),
+ dns.TypeANY,
+ dns.ClassINET,
+ }
+
+ // perform query in retry-loop
+ for retry := 0; retry < 5; retry++ {
+ // send query with new ID when retrying
+ m.Id = dns.Id()
+ in, err := dns.Exchange(m, net.JoinHostPort(server.String(),
"53"))
+ // handle DNS fails
+ if err != nil {
+ errMsg := err.Error()
+ if strings.HasSuffix(errMsg, "i/o timeout") {
+ logger.Printf(logger.WARN, "[dns][%d] Query
timed-out -- retrying (%d/5)\n", id, retry+1)
+ continue
+ }
+ logger.Printf(logger.ERROR, "[dns][%d] Error: %s\n",
id, errMsg)
+ res <- 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
+ }
+ set := NewGNSRecordSet()
+ for _, record := range in.Answer {
+ // create a new GNS resource record
+ rr := new(message.GNSResourceRecord)
+ rr.Expires = util.AbsoluteTimeNever()
+ rr.Flags = 0
+ rr.Type = uint32(record.Header().Rrtype)
+ rr.Size = uint32(record.Header().Rdlength)
+ rr.Data = make([]byte, rr.Size)
+
+ // get wire-format of resource record
+ buf := make([]byte, 2048)
+ n, err := dns.PackRR(record, buf, 0, nil, false)
+ if err != nil {
+ logger.Printf(logger.WARN, "[dns][%d] Failed to
get RR data for %s\n", id, err.Error())
+ continue
+ }
+ if n < int(rr.Size) {
+ logger.Printf(logger.WARN, "[dns][%d] Nit
enough data in RR (%d != %d)\n", id, n, rr.Size)
+ continue
+ }
+ copy(rr.Data, buf[n-int(rr.Size):])
+ 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.WARN, "[dns][%d] Resolution failed -- giving
up...\n", id)
+ res <- nil
+}
+
+// ResolveDNS resolves a name in DNS. Multiple DNS servers are queried in
+// parallel; the first result delivered by any of the servers is returned
+// as the result list of matching resource records.
+func (gns *GNSModule) ResolveDNS(name string, servers []string, kind int, pkey
*ed25519.PublicKey) (set *GNSRecordSet, err error) {
+ logger.Printf(logger.DBG, "[dns] Resolution of '%s' starting...\n",
name)
+
+ // start DNS queries concurrently
+ res := make(chan *GNSRecordSet)
+ running := 0
+ for idx, srv := range servers {
+ // check if srv is an IPv4/IPv6 address
+ addr := net.ParseIP(srv)
+ if addr == nil {
+ // 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,
enums.GNS_TYPE_ANY, 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 (MUST end in a
PKEY)
+ set, err = gns.Resolve(srv, nil,
enums.GNS_TYPE_ANY, enums.GNS_LO_DEFAULT)
+ if err != nil {
+ logger.Printf(logger.ERROR, "[dns]
Can't resolve NS server '%s'\n", srv)
+ continue
+ }
+ }
+ // traverse resource records for 'A' and 'AAAA' records.
+ rec_loop:
+ for _, rec := range set.Records {
+ switch int(rec.Type) {
+ case enums.GNS_TYPE_DNS_AAAA:
+ addr = net.IP(rec.Data)
+ break rec_loop
+ case enums.GNS_TYPE_DNS_A:
+ addr = net.IP(rec.Data)
+ }
+ }
+ }
+ // query DNS concurrently
+ go queryDNS(idx, name, addr, kind, res)
+ running++
+ }
+ // check if we started some queries at all.
+ if running == 0 {
+ return nil, ErrNoDNSQueries
+ }
+ // wait for query results
+ timeout := time.Tick(10 * time.Second)
+ for {
+ select {
+ case set = <-res:
+ running--
+ if set != nil {
+ // we have a result.
+ logger.Println(logger.DBG, "[dns] Query result
available.")
+ return
+ }
+ if running == 0 {
+ // no results
+ logger.Println(logger.WARN, "[dns] No results
received from queries.")
+ return nil, ErrNoDNSResults
+ }
+
+ case <-timeout:
+ // no results
+ logger.Println(logger.WARN, "[dns] Queries timed out.")
+ return nil, ErrNoDNSResults
+ }
+ }
+}
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go
new file mode 100644
index 0000000..e4dc5fb
--- /dev/null
+++ b/src/gnunet/service/gns/module.go
@@ -0,0 +1,342 @@
+package gns
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "gnunet/config"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/message"
+ "gnunet/util"
+
+ "github.com/bfix/gospel/crypto/ed25519"
+ "github.com/bfix/gospel/logger"
+)
+
+//======================================================================
+// "GNUnet Name System" implementation
+//======================================================================
+
+// 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")
+)
+
+//----------------------------------------------------------------------
+// Query for simple GNS lookups
+//----------------------------------------------------------------------
+
+// Query specifies the context for a basic GNS name lookup of an (atomic)
+// label in a given zone identified by its public key.
+type Query struct {
+ Zone *ed25519.PublicKey // Public zone key
+ Label string // Atomic label
+ Derived *ed25519.PublicKey // Derived key from (pkey,label)
+ Key *crypto.HashCode // Key for repository queries (local/remote)
+}
+
+// NewQuery assembles a new Query object for the given zone and label.
+func NewQuery(pkey *ed25519.PublicKey, label string) *Query {
+ // derive a public key from (pkey,label) and set the repository
+ // key as the SHA512 hash of the binary key representation.
+ pd := crypto.DerivePublicKey(pkey, label, "gns")
+ key := crypto.Hash(pd.Bytes())
+ return &Query{
+ Zone: pkey,
+ Label: label,
+ Derived: pd,
+ Key: key,
+ }
+}
+
+//----------------------------------------------------------------------
+// GNS blocks with special types (PKEY, GNS2DNS) require special
+// treatment with respect to other resource records with different types
+// in the same block. Usually only certain other types (or not at all)
+// are allowed and the allowed ones are required to deliver a consistent
+// list of resulting resource records passed back to the caller.
+//----------------------------------------------------------------------
+
+// BlockHandler interface.
+type BlockHandler interface {
+ // TypeAction returns a flag indicating how a resource record of a
+ // given type is to be treated:
+ // = -1: Record is not allowed (terminates lookup with an error)
+ // = 0: Record is allowed but will be ignored
+ // = 1: Record is allowed and will be processed
+ TypeAction(int) int
+}
+
+// Gns2DnsHandler implementing the BlockHandler interface
+type Gns2DnsHandler struct {
+ Name string
+ Servers []string
+}
+
+// NewGns2DnsHandler returns a new BlockHandler instance
+func NewGns2DnsHandler() *Gns2DnsHandler {
+ return &Gns2DnsHandler{
+ Name: "",
+ Servers: make([]string, 0),
+ }
+}
+
+// TypeAction return a flag indicating how a resource record of a given type
+// is to be treated (see RecordMaster interface)
+func (m *Gns2DnsHandler) TypeAction(t int) int {
+ // only process other GNS2DNS records
+ if t == enums.GNS_TYPE_GNS2DNS {
+ return 1
+ }
+ // skip everything else
+ return 0
+}
+
+// AddRequest adds the DNS request for "name" at "server" to the list
+// of requests. All GNS2DNS records must query for the same name
+func (m *Gns2DnsHandler) AddRequest(name, server string) bool {
+ if len(m.Servers) == 0 {
+ m.Name = name
+ }
+ if name != m.Name {
+ return false
+ }
+ m.Servers = append(m.Servers, server)
+ return true
+}
+
+//----------------------------------------------------------------------
+// The GNS module (recursively) resolves GNS names:
+// Resolves DNS-like names (e.g. "minecraft.servers.bob.games") to the
+// requested resource records (RRs). In short, the resolution process
+// works as follows:
+//
+// Resolve(name):
+// --------------
+// (1) split the full name into elements in reverse order: names[]
+// (2) Resolve first element (root zone, right-most name part, name[0]) to
+// a zone public key PKEY:
+// (a) the name is a string representation of a public key -> (3)
+// (b) the zone key for the name is stored in the config file -> (3)
+// (c) a local zone with that given name -> (3)
+// (d) ERROR: "Unknown root zone"
+// (3) names = names[1:] // remove first element
+// block = Lookup (PKEY, names[0]):
+// (a) If last element of namess: -> (4)
+// (b) block is PKEY record:
+// PKEY <- block, --> (3)
+// (4) return block: it is the responsibility of the caller to assemble
+// the desired result from block data (e.g. filter for requested
+// resource record types).
+//----------------------------------------------------------------------
+
+// GNSModule handles the resolution of GNS names to RRs bundled in a block.
+type GNSModule struct {
+ // Use function references for calls to methods in other modules:
+ //
+ LookupLocal func(query *Query) (*GNSBlock, error)
+ StoreLocal func(query *Query, block *GNSBlock) error
+ LookupRemote func(query *Query) (*GNSBlock, error)
+ GetLocalZone func(name string) (*ed25519.PublicKey, error)
+}
+
+// Resolve a GNS name with multiple elements, If pkey is not nil, the name
+// is interpreted as "relative to current zone".
+func (gns *GNSModule) Resolve(path string, pkey *ed25519.PublicKey, kind int,
mode int) (set *GNSRecordSet, err error) {
+ // get the name elements in reverse order
+ names := util.ReverseStringList(strings.Split(path, "."))
+ logger.Printf(logger.DBG, "[gns] Resolver called for %v\n", names)
+
+ // check for relative path
+ if pkey != nil {
+ //resolve relative path
+ return gns.ResolveRelative(names, pkey, kind, mode)
+ }
+ // resolve absolute path
+ return gns.ResolveAbsolute(names, kind, mode)
+}
+
+// Resolve a fully qualified GNS absolute name (with multiple levels).
+func (gns *GNSModule) ResolveAbsolute(names []string, kind int, 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(names[0]) == 52 {
+ if data, err = util.DecodeStringToBinary(names[0], 32);
err == nil {
+ if pkey = ed25519.NewPublicKeyFromBytes(data);
pkey != nil {
+ break
+ }
+ }
+ }
+ // (2) check if TLD is in our local config
+ if pkey = config.Cfg.GNS.GetRootZoneKey(names[0]); pkey != nil {
+ break
+ }
+ // (3) check if TLD is one of our identities
+ if pkey, err = gns.GetLocalZone(names[0]); err == nil {
+ break
+ }
+ // (4) we can't resolve this TLD
+ return nil, ErrUnknownTLD
+ }
+ // continue with resolution relative to a zone.
+ return gns.ResolveRelative(names[1:], pkey, kind, mode)
+}
+
+// Resolve relative path (to a given zone) recursively by processing simple
+// (PKEY,Label) lookups in sequence and handle intermediate GNS record types
+func (gns *GNSModule) ResolveRelative(names []string, pkey *ed25519.PublicKey,
kind int, mode int) (set *GNSRecordSet, err error) {
+ // Process all names in sequence
+ var records []*message.GNSResourceRecord
+name_loop:
+ for ; len(names) > 0; names = names[1:] {
+ logger.Printf(logger.DBG, "[gns] ResolveRelative '%s' in
'%s'\n", names[0], util.EncodeBinaryToString(pkey.Bytes()))
+
+ // resolve next level
+ var block *GNSBlock
+ if block, err = gns.Lookup(pkey, names[0], mode ==
enums.GNS_LO_DEFAULT); err != nil {
+ // failed to resolve name
+ return
+ }
+ // set new mode after processing right-most label in
LOCAL_MASTER mode
+ if mode == enums.GNS_LO_LOCAL_MASTER {
+ mode = enums.GNS_LO_DEFAULT
+ }
+ // post-process block by inspecting contained resource records
for
+ // special GNS types
+ var hdlr BlockHandler
+ if records, err = block.Records(); err != nil {
+ return
+ }
+ for _, rec := range records {
+ // let a block handler decide how to handle records
+ if hdlr != nil {
+ switch hdlr.TypeAction(int(rec.Type)) {
+ case -1:
+ // No records of this type allowed in
block
+ err = ErrInvalidRecordType
+ return
+ case 0:
+ // records of this type are simply
ignored
+ continue
+ case 1:
+ // process record of this type
+ }
+ }
+ switch int(rec.Type) {
+
//----------------------------------------------------------
+ case enums.GNS_TYPE_PKEY:
+ // check for single RR and sane key data
+ if len(rec.Data) != 32 || len(records) > 1 {
+ err = ErrInvalidPKEY
+ return
+ }
+ // set new PKEY and continue resolution
+ pkey = ed25519.NewPublicKeyFromBytes(rec.Data)
+ continue name_loop
+
+
//----------------------------------------------------------
+ case enums.GNS_TYPE_GNS2DNS:
+ // get the master controlling this block;
create a new
+ // one if necessary
+ var inst *Gns2DnsHandler
+ if hdlr == nil {
+ inst = NewGns2DnsHandler()
+ hdlr = inst
+ } else {
+ inst = hdlr.(*Gns2DnsHandler)
+ }
+ // extract list of names in DATA block:
+ logger.Printf(logger.DBG, "[gns] GNS2DNS data:
%s\n", hex.EncodeToString(rec.Data))
+ var dnsNames []string
+ for pos := 0; ; {
+ next, name :=
DNSNameFromBytes(rec.Data, pos)
+ if len(name) == 0 {
+ break
+ }
+ dnsNames = append(dnsNames, name)
+ pos = next
+ }
+ logger.Printf(logger.DBG, "[gns] GNS2DNS
params: %v\n", dnsNames)
+ if len(dnsNames) != 2 {
+ err = ErrInvalidRecordBody
+ return
+ }
+ // Add to collection of requests
+ logger.Printf(logger.DBG, "[gns] GNS2DNS: query
for '%s' on '%s'\n", dnsNames[0], dnsNames[1])
+ if !inst.AddRequest(dnsNames[0], dnsNames[1]) {
+ err = ErrInvalidRecordBody
+ return
+ }
+ }
+ }
+ // handle special block cases
+ if hdlr != nil {
+ switch inst := hdlr.(type) {
+ case *Gns2DnsHandler:
+ // we need to handle delegation to DNS: returns
a list of found
+ // resource records in DNS (filter by 'kind')
+ fqdn :=
strings.Join(util.ReverseStringList(names[1:]), ".") + "." + inst.Name
+ if set, err = gns.ResolveDNS(fqdn,
inst.Servers, kind, pkey); err != nil {
+ logger.Println(logger.ERROR, "[gns]
GNS2DNS resilution failed.")
+ return
+ }
+ // we are done with resolution; pass on records
to caller
+ records = set.Records
+ break name_loop
+ }
+ }
+ }
+ // Assemble resulting resource record set
+ set = NewGNSRecordSet()
+ for _, rec := range records {
+ // is this the record type we are looking for?
+ if kind == enums.GNS_TYPE_ANY || int(rec.Type) == kind {
+ // add it to the result
+ set.AddRecord(rec)
+ }
+ }
+ return
+}
+
+// Lookup name in GNS.
+func (gns *GNSModule) Lookup(pkey *ed25519.PublicKey, label string, remote
bool) (block *GNSBlock, err error) {
+
+ // create query (lookup key)
+ query := NewQuery(pkey, label)
+
+ // try local lookup first
+ if block, err = gns.LookupLocal(query); err != nil {
+ logger.Printf(logger.ERROR, "[gns] local Lookup: %s\n",
err.Error())
+ block = nil
+ return
+ }
+ if block == nil {
+ logger.Println(logger.DBG, "[gns] local Lookup: no block found")
+ if remote {
+ // get the block from a remote lookup
+ if block, err = gns.LookupRemote(query); err != nil ||
block == nil {
+ if err != nil {
+ logger.Printf(logger.ERROR, "[gns]
remote Lookup: %s\n", err.Error())
+ block = nil
+ } else {
+ logger.Println(logger.DBG, "[gns]
remote Lookup: no block found")
+ }
+ // lookup fails completely -- no result
+ return
+ }
+ // store RRs from remote locally.
+ gns.StoreLocal(query, block)
+ }
+ }
+ return
+}
diff --git a/src/gnunet/service/gns/record.go b/src/gnunet/service/gns/record.go
index 0e6315b..68ce4a6 100644
--- a/src/gnunet/service/gns/record.go
+++ b/src/gnunet/service/gns/record.go
@@ -28,6 +28,11 @@ func NewGNSRecordSet() *GNSRecordSet {
}
}
+func (rs *GNSRecordSet) AddRecord(rec *message.GNSResourceRecord) {
+ rs.Count++
+ rs.Records = append(rs.Records, rec)
+}
+
type SignedBlockData struct {
Purpose *crypto.SignaturePurpose // Size and purpose of signature (8
bytes)
Expire util.AbsoluteTime // Expiration time of the block.
diff --git a/src/gnunet/service/gns/service.go
b/src/gnunet/service/gns/service.go
new file mode 100644
index 0000000..852f513
--- /dev/null
+++ b/src/gnunet/service/gns/service.go
@@ -0,0 +1,257 @@
+package gns
+
+import (
+ "encoding/hex"
+ "io"
+
+ "github.com/bfix/gospel/crypto/ed25519"
+ "github.com/bfix/gospel/data"
+ "github.com/bfix/gospel/logger"
+ "gnunet/config"
+ "gnunet/crypto"
+ "gnunet/enums"
+ "gnunet/message"
+ "gnunet/service"
+ "gnunet/transport"
+ "gnunet/util"
+)
+
+//----------------------------------------------------------------------
+// "GNUnet Name System" service implementation
+//----------------------------------------------------------------------
+
+// GNSService
+type GNSService struct {
+ GNSModule
+}
+
+// NewGNSService
+func NewGNSService() service.Service {
+ // instantiate service and assemble a new GNS handler.
+ inst := new(GNSService)
+ inst.LookupLocal = inst.LookupNamecache
+ inst.StoreLocal = inst.StoreNamecache
+ inst.LookupRemote = inst.LookupDHT
+ inst.GetLocalZone = inst.GetPrivateZone
+ return inst
+}
+
+// Start the GNS service
+func (s *GNSService) Start(spec string) error {
+ return nil
+}
+
+// Stop the GNS service
+func (s *GNSService) Stop() error {
+ return nil
+}
+
+// Serve a client channel.
+func (s *GNSService) ServeClient(mc *transport.MsgChannel) {
+ for {
+ // receive next message from client
+ msg, err := mc.Receive()
+ if err != nil {
+ if err == io.EOF {
+ logger.Println(logger.INFO, "[gns] Client
channel closed.")
+ } else {
+ logger.Printf(logger.ERROR, "[gns]
Message-receive failed: %s\n", err.Error())
+ }
+ break
+ }
+ logger.Printf(logger.INFO, "[gns] Received msg: %v\n", msg)
+
+ // perform lookup
+ var resp message.Message
+ switch m := msg.(type) {
+ case *message.GNSLookupMsg:
+
//----------------------------------------------------------
+ // GNS_LOOKUP
+
//----------------------------------------------------------
+ logger.Println(logger.INFO, "[gns] Lookup request
received.")
+ respX := message.NewGNSLookupResultMsg(m.Id)
+ resp = respX
+
+ // perform lookup on block (locally and remote)
+ // TODO: run code in a go routine concurrently (would
need
+ // access to the message channel to send
responses)
+ pkey := ed25519.NewPublicKeyFromBytes(m.Zone)
+ label := m.GetName()
+ recset, err := s.Resolve(label, pkey, int(m.Type),
int(m.Options))
+ if err != nil {
+ logger.Printf(logger.ERROR, "[gns] Failed to
lookup block: %s\n", err.Error())
+ break
+ }
+ // handle records
+ if recset != nil {
+ logger.Printf(logger.DBG, "[gns] Received
record set with %d entries\n", recset.Count)
+
+ // get records from block
+ if recset.Count == 0 {
+ logger.Println(logger.WARN, "[gns] No
records in block")
+ break
+ }
+ // process records
+ for i, rec := range recset.Records {
+ logger.Printf(logger.DBG, "[gns] Record
#%d: %v\n", i, rec)
+
+ // is this the record type we are
looking for?
+ if rec.Type == m.Type || int(m.Type) ==
enums.GNS_TYPE_ANY {
+ // add it to the response
message
+ respX.AddRecord(rec)
+ }
+ }
+ }
+
+ default:
+
//----------------------------------------------------------
+ // UNKNOWN message type received
+
//----------------------------------------------------------
+ logger.Printf(logger.ERROR, "[gns] Unhandled message of
type (%d)\n", msg.Header().MsgType)
+ continue
+ }
+
+ // send response
+ if err := mc.Send(resp); err != nil {
+ logger.Printf(logger.ERROR, "[gns] Failed to send
response: %s\n", err.Error())
+ }
+
+ }
+ // close client connection
+ mc.Close()
+}
+
+// LookupNamecache
+func (s *GNSService) LookupNamecache(query *Query) (block *GNSBlock, err
error) {
+ logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n",
hex.EncodeToString(query.Key.Bits))
+
+ // assemble Namecache request
+ req := message.NewNamecacheLookupMsg(query.Key)
+ req.Id = uint32(util.NextID())
+ block = nil
+
+ // get response from Namecache service
+ var resp message.Message
+ if resp, err = service.ServiceRequestResponse("gns", "Namecache",
config.Cfg.Namecache.Endpoint, req); err != nil {
+ return
+ }
+
+ // handle message depending on its type
+ logger.Println(logger.DBG, "[gns] Handling response from Namecache
service")
+ switch m := resp.(type) {
+ case *message.NamecacheLookupResultMsg:
+ // check for matching IDs
+ if m.Id != req.Id {
+ logger.Println(logger.ERROR, "[gns] Got response for
unknown ID")
+ break
+ }
+ // check if block was found
+ if len(m.EncData) == 0 {
+ logger.Println(logger.DBG, "[gns] block not found in
namecache")
+ break
+ }
+ // check if record has expired
+ if m.Expire.Expired() {
+ logger.Printf(logger.ERROR, "[gns] block expired at
%s\n", m.Expire)
+ break
+ }
+
+ // assemble GNSBlock from message
+ block = new(GNSBlock)
+ block.Signature = m.Signature
+ block.DerivedKey = m.DerivedKey
+ sb := new(SignedBlockData)
+ sb.Purpose = new(crypto.SignaturePurpose)
+ sb.Purpose.Purpose = enums.SIG_GNS_RECORD_SIGN
+ sb.Purpose.Size = uint32(16 + len(m.EncData))
+ sb.Expire = m.Expire
+ sb.EncData = m.EncData
+ block.Block = sb
+
+ // verify and decrypt block
+ if err = block.Verify(query.Zone, query.Label); err != nil {
+ break
+ }
+ if err = block.Decrypt(query.Zone, query.Label); err != nil {
+ break
+ }
+ }
+ return
+}
+
+// StoreNamecache
+func (s *GNSService) StoreNamecache(query *Query, block *GNSBlock) error {
+ logger.Println(logger.WARN, "[gns] StoreNamecache() not implemented
yet!")
+ return nil
+}
+
+// LookupDHT
+func (s *GNSService) LookupDHT(query *Query) (block *GNSBlock, err error) {
+ logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n",
hex.EncodeToString(query.Key.Bits))
+
+ // assemble DHT request
+ req := message.NewDHTClientGetMsg(query.Key)
+ req.Id = uint64(util.NextID())
+ req.ReplLevel = uint32(enums.DHT_GNS_REPLICATION_LEVEL)
+ req.Type = uint32(enums.BLOCK_TYPE_GNS_NAMERECORD)
+ req.Options = uint32(enums.DHT_RO_DEMULTIPLEX_EVERYWHERE)
+ block = nil
+
+ // get response from DHT service
+ var resp message.Message
+ if resp, err = service.ServiceRequestResponse("gns", "DHT",
config.Cfg.DHT.Endpoint, req); err != nil {
+ return
+ }
+
+ // handle message depending on its type
+ logger.Println(logger.DBG, "[gns] Handling response from DHT service")
+ switch m := resp.(type) {
+ case *message.DHTClientResultMsg:
+ // check for matching IDs
+ if m.Id != req.Id {
+ logger.Println(logger.ERROR, "[gns] Got response for
unknown ID")
+ break
+ }
+ // check if block was found
+ if len(m.Data) == 0 {
+ logger.Println(logger.DBG, "[gns] block not found in
DHT")
+ break
+ }
+ // check if record has expired
+ if m.Expire.Expired() {
+ logger.Printf(logger.ERROR, "[gns] block expired at
%s\n", m.Expire)
+ break
+ }
+ // check if result is of requested type
+ if int(m.Type) != enums.BLOCK_TYPE_GNS_NAMERECORD {
+ logger.Println(logger.ERROR, "[gns] DHT response has
wrong type")
+ break
+ }
+
+ // get GNSBlock from message
+ block = NewGNSBlock()
+ if err = data.Unmarshal(block, m.Data); err != nil {
+ logger.Printf(logger.ERROR, "[gns] can't read GNS
block: %s\n", err.Error())
+ break
+ }
+ // verify and decrypt block
+ if err = block.Verify(query.Zone, query.Label); err != nil {
+ break
+ }
+ if err = block.Decrypt(query.Zone, query.Label); err != nil {
+ break
+ }
+
+ // we got a result from DHT that was not in the namecache,
+ // so store it there now.
+ if err = s.StoreNamecache(query, block); err != nil {
+ logger.Printf(logger.ERROR, "[gns] can't store block in
Namecache: %s\n", err.Error())
+ }
+ }
+ return
+}
+
+// GetPrivateZone
+func (s *GNSService) GetPrivateZone(name string) (*ed25519.PublicKey, error) {
+ return nil, nil
+}
diff --git a/src/gnunet/service/namecache/module.go
b/src/gnunet/service/namecache/module.go
new file mode 100644
index 0000000..85f7601
--- /dev/null
+++ b/src/gnunet/service/namecache/module.go
@@ -0,0 +1,25 @@
+package namecache
+
+import (
+ "gnunet/service/gns"
+)
+
+//======================================================================
+// "GNS name cache" implementation
+//======================================================================
+
+//----------------------------------------------------------------------
+// Put and get GNS blocks into/from a cache (transient storage)
+//----------------------------------------------------------------------
+
+// Namecache handles the transient storage of GNS blocks under the query key.
+type NamecacheModule struct {
+}
+
+func (nc *NamecacheModule) Get(query *gns.Query) (*gns.GNSBlock, error) {
+ return nil, nil
+}
+
+func (nc *NamecacheModule) Put(query *gns.Query, block *gns.GNSBlock) error {
+ return nil
+}
diff --git a/src/gnunet/util/array.go b/src/gnunet/util/array.go
index 2ffb618..9076516 100644
--- a/src/gnunet/util/array.go
+++ b/src/gnunet/util/array.go
@@ -4,16 +4,23 @@ import (
"fmt"
)
+// Error variables
var (
ErrUtilArrayTooSmall = fmt.Errorf("Array to small")
)
+//----------------------------------------------------------------------
+// Byte array helpers
+//----------------------------------------------------------------------
+
+// Clone creates a new array of same content as the argument.
func Clone(d []byte) []byte {
r := make([]byte, len(d))
copy(r, d)
return r
}
+// Reverse the content of a byte array
func Reverse(b []byte) []byte {
bl := len(b)
r := make([]byte, bl)
@@ -41,8 +48,40 @@ func CopyBlock(out, in []byte) {
copy(out[to:], in[from:])
}
+// Fill an array with a value
func Fill(b []byte, val byte) {
for i := 0; i < len(b); i++ {
b[i] = val
}
}
+
+//----------------------------------------------------------------------
+// String list helpers
+//----------------------------------------------------------------------
+
+// Reverse StringList reverse an array of strings
+func ReverseStringList(s []string) []string {
+ sl := len(s)
+ r := make([]string, sl)
+ for i := 0; i < sl; i++ {
+ r[sl-i-1] = s[i]
+ }
+ return r
+}
+
+// Convert a binary representation of a string list. Each string is '\0'-
+// terminated. The whole byte array is parsed; if the final string is not
+// terminated, it is skipped.
+func StringList(b []byte) []string {
+ res := make([]string, 0)
+ str := ""
+ for _, ch := range b {
+ if ch == 0 {
+ res = append(res, str)
+ str = ""
+ continue
+ }
+ str += string(ch)
+ }
+ return res
+}
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go
index b513d5e..0dd51a0 100644
--- a/src/gnunet/util/time.go
+++ b/src/gnunet/util/time.go
@@ -9,10 +9,14 @@ import (
// Absolute time
//----------------------------------------------------------------------
+// AbsoluteTime refers to a unique point in time.
+// The value is the elapsed time in milliseconds (Unix epoch), so no timestamp
+// before January 1st, 1970 is possible (not a restriction for GNUnet).
type AbsoluteTime struct {
Val uint64 `order:"big"`
}
+// NewAbsoluteTime set the point in time to the given time value
func NewAbsoluteTime(t time.Time) AbsoluteTime {
secs := t.Unix()
usecs := t.Nanosecond() / 1000
@@ -21,10 +25,17 @@ func NewAbsoluteTime(t time.Time) AbsoluteTime {
}
}
+// AbsoluteTimeNow returns the current point in time.
func AbsoluteTimeNow() AbsoluteTime {
return NewAbsoluteTime(time.Now())
}
+// AbsoluteTimeNever returns the time defined as "never"
+func AbsoluteTimeNever() AbsoluteTime {
+ return AbsoluteTime{math.MaxUint64}
+}
+
+// String returns a human-readable notation of an absolute time.
func (t AbsoluteTime) String() string {
if t.Val == math.MaxUint64 {
return "Never"
@@ -33,12 +44,14 @@ func (t AbsoluteTime) String() string {
return ts.Format(time.RFC3339Nano)
}
+// Add a duration to an absolute time yielding a new absolute time.
func (t AbsoluteTime) Add(d time.Duration) AbsoluteTime {
return AbsoluteTime{
Val: t.Val + uint64(d.Milliseconds()),
}
}
+// Expired returns true if the timestamp is in the past.
func (t AbsoluteTime) Expired() bool {
// check for "never"
if t.Val == math.MaxUint64 {
@@ -51,16 +64,20 @@ func (t AbsoluteTime) Expired() bool {
// Relative time
//----------------------------------------------------------------------
+// Relative time is a timestamp defined relative to the current time.
+// It actually is more like a duration than a time...
type RelativeTime struct {
Val uint64 `order:"big"`
}
+// NewRelativeTime is initialized with a given duration.
func NewRelativeTime(d time.Duration) RelativeTime {
return RelativeTime{
Val: uint64(d.Milliseconds()),
}
}
+// String returns a human-readble representation of a relative time (duration).
func (t RelativeTime) String() string {
if t.Val == math.MaxUint64 {
return "Forever"
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..5761694
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+GOPATH=$(pwd):${GOPATH} go test -gcflags "-N -l" ./...
--
To stop receiving notification emails like this one, please contact
address@hidden.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnunet-go] branch master updated: Started recursive resolution; GNS2DNS implemented.,
gnunet <=