diff --git a/lib/bootstrap/bootstrap.go b/lib/bootstrap/bootstrap.go new file mode 100644 index 0000000..e4b6172 --- /dev/null +++ b/lib/bootstrap/bootstrap.go @@ -0,0 +1,13 @@ +package bootstrap + +import "github.com/hkparker/go-i2p/lib/common" + +// interface defining a way to bootstrap into the i2p network +type Bootstrap interface { + // get more peers for bootstrap + // try obtaining at most n router infos + // if n is 0 then try obtaining as many router infos as possible + // returns nil and error if we cannot fetch ANY router infos + // returns a channel that yields 1 slice of router infos containing n or fewer router infos, caller must close channel after use + GetPeers(n int) (chan []common.RouterInfo, error) +} diff --git a/lib/bootstrap/doc.go b/lib/bootstrap/doc.go new file mode 100644 index 0000000..c85b2ad --- /dev/null +++ b/lib/bootstrap/doc.go @@ -0,0 +1,4 @@ +// +// provides generic interfaces for initial bootstrap into network and network reseeding +// +package bootstrap diff --git a/lib/common/base64/base64.go b/lib/common/base64/base64.go index 9b3ea29..f056796 100644 --- a/lib/common/base64/base64.go +++ b/lib/common/base64/base64.go @@ -7,7 +7,11 @@ import ( b64 "encoding/base64" ) -var I2PEncoding *b64.Encoding = b64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~") +// i2p base64 alphabet +const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~" + +// i2p base64 encoding +var I2PEncoding *b64.Encoding = b64.NewEncoding(Alphabet) // // Return a go string of the I2P base64 @@ -16,3 +20,11 @@ var I2PEncoding *b64.Encoding = b64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde func EncodeToString(data []byte) string { return I2PEncoding.EncodeToString(data) } + +// +// decode string using i2p base64 encoding +// returns error if data is malfromed +// +func DecodeFromString(str string) (d []byte, err error) { + return I2PEncoding.DecodeString(str) +} diff --git a/lib/common/hash.go b/lib/common/hash.go index 14fcb9f..553eba4 100644 --- a/lib/common/hash.go +++ b/lib/common/hash.go @@ -1,3 +1,27 @@ package common +import ( + "crypto/sha256" + "io" +) + +// sha256 hash of some data type Hash [32]byte + +// calculate sha256 of a byte slice +func HashData(data []byte) (h Hash) { + h = sha256.Sum256(data) + return +} + +// calulate sha256 of all data being read from an io.Reader +// return error if one occurs while reading from reader +func HashReader(r io.Reader) (h Hash, err error) { + sha := sha256.New() + _, err = io.Copy(sha, r) + if err == nil { + d := sha.Sum(nil) + copy(h[:], d) + } + return +} diff --git a/lib/common/router_info.go b/lib/common/router_info.go index 2e3b7d1..67c9f89 100644 --- a/lib/common/router_info.go +++ b/lib/common/router_info.go @@ -89,6 +89,19 @@ func (router_info RouterInfo) RouterIdentity() (router_identity RouterIdentity, return } +// +// Calculate this RouterInfo's Identity Hash (the sha256 of the RouterIdentity) +// returns error if the RouterIdentity is malformed +// +func (router_info RouterInfo) IdentHash() (h Hash, err error) { + var ri RouterIdentity + ri, err = router_info.RouterIdentity() + if err == nil { + h = HashData(ri) + } + return +} + // // Return the Date the RouterInfo was published and any errors encountered parsing the RouterInfo. // @@ -196,6 +209,7 @@ func (router_info RouterInfo) Options() (mapping Mapping) { func (router_info RouterInfo) Signature() (signature Signature) { head := router_info.optionsLocation() size := head + router_info.optionsSize() + // TODO: signature is not always 40 bytes, is 40 bytes for DSA only signature = Signature(router_info[size : size+40]) return } diff --git a/lib/config/bootstrap.go b/lib/config/bootstrap.go index 0d7b1b1..e8cee6f 100644 --- a/lib/config/bootstrap.go +++ b/lib/config/bootstrap.go @@ -1,5 +1,23 @@ package config -type BootstrapConfig struct { - LowPeerThreshold int +// configuration for 1 reseed server +type ReseedConfig struct { + // url of reseed server + Url string + // fingerprint of reseed su3 signing key + SU3Fingerprint string +} + +type BootstrapConfig struct { + // if we have less than this many peers we should reseed + LowPeerThreshold int + // reseed servers + ReseedServers []*ReseedConfig +} + +// default configuration for network bootstrap +var DefaultBootstrapConfig = BootstrapConfig{ + LowPeerThreshold: 10, + // TODO: add reseed servers + ReseedServers: []*ReseedConfig{}, } diff --git a/lib/config/router.go b/lib/config/router.go index 2aaea6e..eb87ab1 100644 --- a/lib/config/router.go +++ b/lib/config/router.go @@ -2,12 +2,14 @@ package config // router.config options type RouterConfig struct { - NetDbDir string - - Bootstrap BootstrapConfig + // netdb configuration + NetDb *NetDbConfig + // configuration for bootstrapping into the network + Bootstrap *BootstrapConfig } // defaults for router -var Router = &RouterConfig{ - NetDbDir: "./netDb", +var DefaultRouterConfig = &RouterConfig{ + NetDb: &DefaultNetDbConfig, + Bootstrap: &DefaultBootstrapConfig, } diff --git a/lib/crypto/doc.go b/lib/crypto/doc.go new file mode 100644 index 0000000..5a7dfa5 --- /dev/null +++ b/lib/crypto/doc.go @@ -0,0 +1,4 @@ +// +// package for i2p specific crpytography +// +package crypto diff --git a/lib/netdb/bootstrap.go b/lib/netdb/bootstrap.go deleted file mode 100644 index b96e08c..0000000 --- a/lib/netdb/bootstrap.go +++ /dev/null @@ -1,13 +0,0 @@ -package netdb - -type Reseed interface { - // do reseed, return nil on success otherwise error - // sends down all Netdb entries down chan - // closes channel when done - Reseed(chnl chan *Entry) error -} - -func GetRandomReseed() Reseed { - // TODO: hardcoded value - return HTTPSReseed("https://i2p.rocks:445/") -} diff --git a/lib/netdb/entry.go b/lib/netdb/entry.go index 5f3e250..b9f1873 100644 --- a/lib/netdb/entry.go +++ b/lib/netdb/entry.go @@ -1,16 +1,14 @@ package netdb import ( + "github.com/hkparker/go-i2p/lib/common" "io" - "path/filepath" ) +// netdb entry +// wraps a router info and provides serialization type Entry struct { - fname string -} - -func (e *Entry) FilePath(n StdNetDB) (str string) { - return filepath.Join(string(n), e.fname) + ri common.RouterInfo } func (e *Entry) WriteTo(w io.Writer) (err error) { diff --git a/lib/netdb/kad.go b/lib/netdb/kad.go new file mode 100644 index 0000000..cc51f7d --- /dev/null +++ b/lib/netdb/kad.go @@ -0,0 +1,32 @@ +package netdb + +import ( + "github.com/hkparker/go-i2p/lib/common" + "github.com/hkparker/go-i2p/lib/tunnel" + "time" +) + +// resolves router infos with recursive kademlia lookup +type kadResolver struct { + // netdb to store result into + netDB NetworkDatabase + // what tunnel pool to use when doing lookup + // if nil the lookup will be done directly + pool *tunnel.Pool +} + +// TODO: implement +func (kr *kadResolver) Lookup(h common.Hash, timeout time.Duration) (chnl chan common.RouterInfo) { + return +} + +// create a new resolver that stores result into a NetworkDatabase and uses a tunnel pool for the lookup +func KademliaResolver(netDb NetworkDatabase, pool *tunnel.Pool) (r Resolver) { + if pool != nil && netDb != nil { + r = &kadResolver{ + netDB: netDb, + pool: pool, + } + } + return +} diff --git a/lib/netdb/netdb.go b/lib/netdb/netdb.go index 5577bac..10146be 100644 --- a/lib/netdb/netdb.go +++ b/lib/netdb/netdb.go @@ -1,14 +1,35 @@ package netdb import ( + "github.com/hkparker/go-i2p/lib/bootstrap" "github.com/hkparker/go-i2p/lib/common" + "time" ) +// resolves unknown RouterInfos given the hash of their RouterIdentity +type Resolver interface { + // resolve a router info by hash + // return a chan that yields the found RouterInfo or nil if it could not be found after timeout + Lookup(hash common.Hash, timeout time.Duration) chan common.RouterInfo +} + // i2p network database, storage of i2p RouterInfos type NetworkDatabase interface { - // obtain a RouterInfo by its hash - // return a channel that gives 1 RouterInfo or nil if the RouterInfo cannot be found - GetRouterInfo(hash common.Hash) chan *common.RouterInfo + // obtain a RouterInfo by its hash locally + // return a RouterInfo if we found it locally + // return nil if the RouterInfo cannot be found locally + GetRouterInfo(hash common.Hash) common.RouterInfo + // store a router info locally - StoreRouterInfo(ri *common.RouterInfo) + StoreRouterInfo(ri common.RouterInfo) + + // try obtaining more peers with a bootstrap instance until we get minRouters number of router infos + // returns error if bootstrap.GetPeers returns an error otherwise returns nil + Reseed(b bootstrap.Bootstrap, minRouters int) error + + // return how many router infos we have + Size() int + + // ensure underlying resources exist , i.e. directories, files, configs + Ensure() error } diff --git a/lib/netdb/reseed.go b/lib/netdb/reseed.go deleted file mode 100644 index 5e0b2be..0000000 --- a/lib/netdb/reseed.go +++ /dev/null @@ -1,8 +0,0 @@ -package netdb - -type HTTPSReseed string - -func (r HTTPSReseed) Reseed(chnl chan *Entry) (err error) { - close(chnl) - return -} diff --git a/lib/netdb/std.go b/lib/netdb/std.go index 5016a78..d482d6b 100644 --- a/lib/netdb/std.go +++ b/lib/netdb/std.go @@ -1,14 +1,41 @@ package netdb import ( - log "github.com/golang/glog" + "bytes" + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/hkparker/go-i2p/lib/bootstrap" + "github.com/hkparker/go-i2p/lib/common" + "github.com/hkparker/go-i2p/lib/common/base64" "io" "os" + "path/filepath" ) -// standard network database implementation +// standard network database implementation using local filesystem skiplist type StdNetDB string +func (db StdNetDB) GetRouterInfo(hash common.Hash) (chnl chan common.RouterInfo) { + fname := db.SkiplistFile(hash) + f, err := os.Open(fname) + if err != nil { + return nil + } + buff := new(bytes.Buffer) + _, err = io.Copy(buff, f) + f.Close() + chnl = make(chan common.RouterInfo) + chnl <- common.RouterInfo(buff.Bytes()) + return +} + +// get the skiplist file that a RouterInfo with this hash would go in +func (db StdNetDB) SkiplistFile(hash common.Hash) (fpath string) { + fname := base64.EncodeToString(hash[:]) + fpath = filepath.Join(db.Path(), fmt.Sprintf("r%s", fname[0]), fmt.Sprintf("routerInfo-%s.dat", fname)) + return +} + // get netdb path func (db StdNetDB) Path() string { return string(db) @@ -17,26 +44,38 @@ func (db StdNetDB) Path() string { // // return how many routers we know about in our network database // -func (db StdNetDB) KnownPeerCount() (routers int) { +func (db StdNetDB) Size() (routers int) { return } // return true if the network db directory exists and is writable func (db StdNetDB) Exists() bool { - _, err := os.Stat(db.Path()) - return err != nil + p := db.Path() + // check root directory + _, err := os.Stat(p) + if err == nil { + // check subdirectories for skiplist + for _, c := range base64.Alphabet { + if _, err = os.Stat(filepath.Join(p, fmt.Sprintf("r%s", c))); err != nil { + return false + } + } + } + return err == nil } func (db StdNetDB) SaveEntry(e *Entry) (err error) { var f io.WriteCloser - f, err = os.OpenFile(e.FilePath(db), os.O_WRONLY, 0600) + var h common.Hash + h, err = e.ri.IdentHash() if err == nil { - err = e.WriteTo(f) - if err != nil { - log.Errorf("failed to write netdb entry: %s", err.Error()) + f, err = os.OpenFile(db.SkiplistFile(h), os.O_WRONLY|os.O_CREATE, 0700) + if err == nil { + err = e.WriteTo(f) + f.Close() } - f.Close() - } else { + } + if err != nil { log.Errorf("failed to save netdb entry: %s", err.Error()) } return @@ -44,49 +83,34 @@ func (db StdNetDB) SaveEntry(e *Entry) (err error) { // reseed if we have less than minRouters known routers // returns error if reseed failed -func (db StdNetDB) Reseed(minRouters int) (err error) { - current := db.KnownPeerCount() - if current <= minRouters { - // we need to reseed - rs := GetRandomReseed() - log.Infof("Reseeding from %s", rs) - chnl := make(chan *Entry) - // receive entries from reseed - go func(c chan *Entry) { - count := 0 - for { - e, ok := <-c - if ok { - // got an entry - // save it to our netdb - err := db.SaveEntry(e) - if err == nil { - count++ - } - } - } - }(chnl) // call - err = rs.Reseed(chnl) - } +func (db StdNetDB) Reseed(b bootstrap.Bootstrap, minRouters int) (err error) { return } -// ensure that the network database exists and is seeded with a minimum number of routers -func (db StdNetDB) Ensure(minRouters int) (err error) { +// ensure that the network database exists +func (db StdNetDB) Ensure() (err error) { if !db.Exists() { err = db.Create() } - if err == nil { - // database directory ensured - // try to reseed - err = db.Reseed(minRouters) - } return } // create base network database directory func (db StdNetDB) Create() (err error) { - log.Infof("Create network database in %s", db.Path()) - err = os.Mkdir(db.Path(), 0600) + mode := os.FileMode(0600) + p := db.Path() + log.Infof("Create network database in %s", p) + + // create root for skiplist + err = os.Mkdir(p, mode) + if err == nil { + // create all subdirectories for skiplist + for _, c := range base64.Alphabet { + err = os.Mkdir(filepath.Join(p, fmt.Sprintf("r%s", c)), mode) + if err != nil { + return + } + } + } return } diff --git a/lib/router/router.go b/lib/router/router.go index 57cf013..97fff7c 100644 --- a/lib/router/router.go +++ b/lib/router/router.go @@ -11,19 +11,24 @@ type Router struct { ndb netdb.StdNetDB } +// create router with default configuration func CreateRouter() (r *Router, err error) { - cfg := config.Router - r = &Router{ - cfg: cfg, - ndb: netdb.StdNetDB(cfg.NetDbDir), - } + cfg := config.DefaultRouterConfig + r, err = FromConfig(cfg) + return +} + +// create router from configuration +func FromConfig(c *config.RouterConfig) (r *Router, err error) { + + r = new(Router) return } // run i2p router mainloop func (r *Router) Run() { // make sure the netdb is ready - err := r.ndb.Ensure(r.cfg.Bootstrap.LowPeerThreshold) + err := r.ndb.Ensure() if err == nil { // netdb ready } diff --git a/lib/tunnel/pool.go b/lib/tunnel/pool.go new file mode 100644 index 0000000..de02722 --- /dev/null +++ b/lib/tunnel/pool.go @@ -0,0 +1,5 @@ +package tunnel + +// a pool of tunnels which we have created +type Pool struct { +}