// FromIP converts a net.IP type to a Multiaddr. func FromIP(ip net.IP) (ma.Multiaddr, error) { switch { case ip.To4() != nil: return ma.NewMultiaddr("/ip4/" + ip.String()) case ip.To16() != nil: return ma.NewMultiaddr("/ip6/" + ip.String()) default: return nil, errIncorrectNetAddr } }
func parseUtpNetAddr(a net.Addr) (ma.Multiaddr, error) { acc, ok := a.(*utp.Addr) if !ok { return nil, errIncorrectNetAddr } // Get UDP Addr ac, ok := acc.Child().(*net.UDPAddr) if !ok { return nil, errIncorrectNetAddr } // Get IP Addr ipm, err := FromIP(ac.IP) if err != nil { return nil, errIncorrectNetAddr } // Get UDP Addr utpm, err := ma.NewMultiaddr(fmt.Sprintf("/udp/%d/utp", ac.Port)) if err != nil { return nil, errIncorrectNetAddr } // Encapsulate return ipm.Encapsulate(utpm), nil }
func newMultiaddr(t *testing.T, s string) ma.Multiaddr { maddr, err := ma.NewMultiaddr(s) if err != nil { t.Fatal(err) } return maddr }
func init() { // initialize ZeroLocalTCPAddress maddr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0") if err != nil { panic(err) } ZeroLocalTCPAddress = maddr }
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests func serveHTTPGateway(req cmds.Request) (error, <-chan error) { cfg, err := req.InvocContext().GetConfig() if err != nil { return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil } gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway) if err != nil { return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil } writable, writableOptionFound, err := req.Option(writableKwd).Bool() if err != nil { return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil } if writableOptionFound { cfg.Gateway.Writable = writable } gwLis, err := manet.Listen(gatewayMaddr) if err != nil { return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil } // we might have listened to /tcp/0 - lets see what we are listing on gatewayMaddr = gwLis.Multiaddr() if writable { fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr) } else { fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr) } var opts = []corehttp.ServeOption{ corehttp.MetricsCollectionOption("gateway"), corehttp.CommandsROOption(*req.InvocContext()), corehttp.VersionOption(), corehttp.IPNSHostnameOption(), corehttp.GatewayOption("/ipfs", "/ipns"), } if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } node, err := req.InvocContext().ConstructNode() if err != nil { return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil } errc := make(chan error) go func() { errc <- corehttp.Serve(node, gwLis.NetListener(), opts...) close(errc) }() return nil, errc }
// ParseString parses a string representation of an address into an IPFSAddr func ParseString(str string) (a IPFSAddr, err error) { if str == "" { return nil, ErrInvalidAddr } m, err := ma.NewMultiaddr(str) if err != nil { return nil, err } return ParseMultiaddr(m) }
func TestParseMultiaddrBad(t *testing.T) { for _, b := range bad { m, err := ma.NewMultiaddr(b) if err != nil { continue // skip these. } if _, err := ParseMultiaddr(m); err == nil { t.Error("succeeded in parsing", m) } } }
func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) { var listen []ma.Multiaddr for _, addr := range cfg.Addresses.Swarm { maddr, err := ma.NewMultiaddr(addr) if err != nil { return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm: %s", cfg.Addresses.Swarm) } listen = append(listen, maddr) } return listen, nil }
// getApiClient checks the repo, and the given options, checking for // a running API service. if there is one, it returns a client. // otherwise, it returns errApiNotRunning, or another error. func getApiClient(repoPath, apiAddrStr string) (cmdsHttp.Client, error) { if apiAddrStr == "" { var err error if apiAddrStr, err = fsrepo.APIAddr(repoPath); err != nil { return nil, err } } addr, err := ma.NewMultiaddr(apiAddrStr) if err != nil { return nil, err } return apiClientForAddr(addr) }
// ListenAndServe runs an HTTP server listening at |listeningMultiAddr| with // the given serve options. The address must be provided in multiaddr format. // // TODO intelligently parse address strings in other formats so long as they // unambiguously map to a valid multiaddr. e.g. for convenience, ":8080" should // map to "/ip4/0.0.0.0/tcp/8080". func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...ServeOption) error { addr, err := ma.NewMultiaddr(listeningMultiAddr) if err != nil { return err } list, err := manet.Listen(addr) if err != nil { return err } // we might have listened to /tcp/0 - lets see what we are listing on addr = list.Multiaddr() fmt.Printf("API server listening on %s\n", addr) return Serve(n, list.NetListener(), options...) }
func main() { flag.Parse() // extract address from host flag addr, err := ma.NewMultiaddr(*host) if err != nil { log.Fatal("NewMultiaddr() failed: ", err) } p := addr.Protocols() if len(p) < 2 { log.Fatal("need two protocols in host flag (/ip/tcp): ", addr) } _, host, err := manet.DialArgs(addr) if err != nil { log.Fatal("manet.DialArgs() failed: ", err) } if *verbose { // lower log level logging.SetDebugLogging() } // construct url to dial var u url.URL u.Scheme = "http" u.Host = host u.Path = *endpoint // show what we got start := time.Now() log.Debug("starting at %s, tries: %d, timeout: %s, url: %s", start, *tries, *timeout, u) for *tries > 0 { err := checkOK(http.Get(u.String())) if err == nil { log.Debugf("ok - endpoint reachable with %d tries remaining, took %s", *tries, time.Since(start)) os.Exit(0) } log.Debug("get failed: ", err) time.Sleep(*timeout) *tries-- } log.Error("failed.") os.Exit(1) }
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests func serveHTTPGateway(node *core.OpenBazaarNode) (error, <-chan bool, <-chan error) { cfg, err := node.Context.GetConfig() if err != nil { return nil, nil, nil } gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway) if err != nil { return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil, nil } writable := cfg.Gateway.Writable gwLis, err := manet.Listen(gatewayMaddr) if err != nil { return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil, nil } // we might have listened to /tcp/0 - lets see what we are listing on gatewayMaddr = gwLis.Multiaddr() log.Infof("Gateway/API server listening on %s\n", gatewayMaddr) var opts = []corehttp.ServeOption{ corehttp.MetricsCollectionOption("gateway"), corehttp.CommandsROOption(node.Context), corehttp.VersionOption(), corehttp.IPNSHostnameOption(), corehttp.GatewayOption(writable, cfg.Gateway.PathPrefixes), } if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } if err != nil { return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil, nil } errc := make(chan error) cb := make(chan bool) go func() { errc <- api.Serve(cb, node, node.Context, gwLis.NetListener(), opts...) close(errc) }() return nil, cb, errc }
// RandLocalTCPAddress returns a random multiaddr. it suppresses errors // for nice composability-- do check the address isn't nil. // // Note: for real network tests, use ZeroLocalTCPAddress so the kernel // assigns an unused TCP port. otherwise you may get clashes. This // function remains here so that p2p/net/mock (which does not touch the // real network) can assign different addresses to peers. func RandLocalTCPAddress() ma.Multiaddr { // chances are it will work out, but it **might** fail if the port is in use // most ports above 10000 aren't in use by long running processes, so yay. // (maybe there should be a range of "loopback" ports that are guaranteed // to be open for the process, but naturally can only talk to self.) lastPort.Lock() if lastPort.port == 0 { lastPort.port = 10000 + SeededRand.Intn(50000) } port := lastPort.port lastPort.port++ lastPort.Unlock() addr := fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port) maddr, _ := ma.NewMultiaddr(addr) return maddr }
func parseTcpNetAddr(a net.Addr) (ma.Multiaddr, error) { ac, ok := a.(*net.TCPAddr) if !ok { return nil, errIncorrectNetAddr } // Get IP Addr ipm, err := FromIP(ac.IP) if err != nil { return nil, errIncorrectNetAddr } // Get TCP Addr tcpm, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", ac.Port)) if err != nil { return nil, errIncorrectNetAddr } // Encapsulate return ipm.Encapsulate(tcpm), nil }
func (p *PointersDB) GetAll() ([]ipfs.Pointer, error) { p.lock.Lock() defer p.lock.Unlock() stm := "select * from pointers" rows, err := p.db.Query(stm) if err != nil { log.Error(err) return nil, err } var ret []ipfs.Pointer for rows.Next() { var pointerID string var key string var address string var purpose int var timestamp int if err := rows.Scan(&pointerID, &key, &address, &purpose, ×tamp); err != nil { log.Error(err) } maAddr, err := ma.NewMultiaddr(address) if err != nil { return ret, err } pid, err := peer.IDB58Decode(pointerID) if err != nil { return ret, err } pointer := ipfs.Pointer{ Key: keys.B58KeyDecode(key), Value: peer.PeerInfo{ ID: pid, Addrs: []ma.Multiaddr{maAddr}, }, Purpose: ipfs.Purpose(purpose), Timestamp: time.Unix(int64(timestamp), 0), } ret = append(ret, pointer) } return ret, nil }
func (s *SelfHostedStorage) Store(peerID peer.ID, ciphertext []byte) (ma.Multiaddr, error) { b := sha256.Sum256(ciphertext) hash := hex.EncodeToString(b[:]) filePath := path.Join(s.repoPath, "outbox", hash) f, err := os.Create(filePath) defer f.Close() if err != nil { return nil, err } _, ferr := f.Write(ciphertext) if ferr != nil { return nil, ferr } addr, err := ipfs.AddFile(s.context, filePath) if err != nil { return nil, err } maAddr, err := ma.NewMultiaddr("/ipfs/" + addr + "/") if err != nil { return nil, err } return maAddr, nil }
func (s *DropBoxStorage) Store(peerID peer.ID, ciphertext []byte) (ma.Multiaddr, error) { api := dropbox.Client(s.apiToken, dropbox.Options{Verbose: true}) hash := sha256.Sum256(ciphertext) hex := hex.EncodeToString(hash[:]) // Upload ciphertext uploadArg := files.NewCommitInfo("/" + hex) r := bytes.NewReader(ciphertext) _, err := api.Upload(uploadArg, r) if err != nil { return nil, err } // Set public sharing sharingArg := sharing.NewCreateSharedLinkArg("/" + hex) res, err := api.CreateSharedLink(sharingArg) if err != nil { return nil, err } // Create encoded multiaddr url := res.Url[:len(res.Url)-1] + "1" b, err := mh.Encode([]byte(url), mh.SHA1) if err != nil { return nil, err } m, err := mh.Cast(b) if err != nil { return nil, err } addr, err := ma.NewMultiaddr("/ipfs/" + m.B58String() + "/https/") if err != nil { return nil, err } return addr, nil }
func (m *mapping) ExternalAddr() (ma.Multiaddr, error) { if time.Now().Sub(m.cacheTime) < CacheTime { return m.cached, nil } if m.ExternalPort() == 0 { // dont even try right now. return nil, ErrNoMapping } ip, err := m.nat.nat.GetExternalAddress() if err != nil { return nil, err } ipmaddr, err := manet.FromIP(ip) if err != nil { return nil, fmt.Errorf("error parsing ip") } // call m.ExternalPort again, as mapping may have changed under our feet. (tocttou) extport := m.ExternalPort() if extport == 0 { return nil, ErrNoMapping } tcp, err := ma.NewMultiaddr(fmt.Sprintf("/%s/%d", m.Protocol(), extport)) if err != nil { return nil, err } maddr2 := ipmaddr.Encapsulate(tcp) m.cached = maddr2 m.cacheTime = time.Now() return maddr2, nil }
func ParsePeerParam(text string) (ma.Multiaddr, peer.ID, error) { // to be replaced with just multiaddr parsing, once ptp is a multiaddr protocol idx := strings.LastIndex(text, "/") if idx == -1 { pid, err := peer.IDB58Decode(text) if err != nil { return nil, "", err } return nil, pid, nil } addrS := text[:idx] peeridS := text[idx+1:] var maddr ma.Multiaddr var pid peer.ID // make sure addrS parses as a multiaddr. if len(addrS) > 0 { var err error maddr, err = ma.NewMultiaddr(addrS) if err != nil { return nil, "", err } } // make sure idS parses as a peer.ID var err error pid, err = peer.IDB58Decode(peeridS) if err != nil { return nil, "", err } return maddr, pid, nil }
func (x *Start) Execute(args []string) error { printSplashScreen() // set repo path var repoPath string if x.Testnet { repoPath = "~/.openbazaar2-testnet" } else { repoPath = "~/.openbazaar2" } expPath, _ := homedir.Expand(filepath.Clean(repoPath)) // Database sqliteDB, err := db.Create(expPath, x.Password, x.Testnet) if err != nil { return err } // logging w := &lumberjack.Logger{ Filename: path.Join(expPath, "logs", "ob.log"), MaxSize: 10, // megabytes MaxBackups: 3, MaxAge: 30, //days } backendStdout := logging.NewLogBackend(os.Stdout, "", 0) backendFile := logging.NewLogBackend(w, "", 0) backendStdoutFormatter := logging.NewBackendFormatter(backendStdout, stdoutLogFormat) backendFileFormatter := logging.NewBackendFormatter(backendFile, fileLogFormat) logging.SetBackend(backendFileFormatter, backendStdoutFormatter) ipfslogging.LdJSONFormatter() w2 := &lumberjack.Logger{ Filename: path.Join(expPath, "logs", "ipfs.log"), MaxSize: 10, // megabytes MaxBackups: 3, MaxAge: 30, //days } ipfslogging.Output(w2)() // initalize the ipfs repo if it doesn't already exist err = repo.DoInit(os.Stdout, expPath, 4096, x.Testnet, x.Password, sqliteDB.Config().Init) if err != nil && err != repo.ErrRepoExists { log.Error(err) return err } // if the db can't be decrypted, exit if sqliteDB.Config().IsEncrypted() { return encryptedDatabaseError } // ipfs node setup r, err := fsrepo.Open(repoPath) if err != nil { log.Error(err) return err } cctx, cancel := context.WithCancel(context.Background()) defer cancel() cfg, err := r.Config() if err != nil { log.Error(err) return err } identityKey, err := sqliteDB.Config().GetIdentityKey() if err != nil { log.Error(err) return err } identity, err := ipfs.IdentityFromKey(identityKey) if err != nil { return err } cfg.Identity = identity // Run stun and set uTP port if x.STUN { for i, addr := range cfg.Addresses.Swarm { m, _ := ma.NewMultiaddr(addr) p := m.Protocols() if p[0].Name == "ip4" && p[1].Name == "udp" && p[2].Name == "utp" { port, serr := net.Stun() if serr != nil { log.Error(serr) return err } cfg.Addresses.Swarm = append(cfg.Addresses.Swarm[:i], cfg.Addresses.Swarm[i+1:]...) cfg.Addresses.Swarm = append(cfg.Addresses.Swarm, "/ip4/0.0.0.0/udp/"+strconv.Itoa(port)+"/utp") break } } } ncfg := &ipfscore.BuildCfg{ Repo: r, Online: true, } nd, err := ipfscore.NewNode(cctx, ncfg) if err != nil { log.Error(err) return err } ctx := commands.Context{} ctx.Online = true ctx.ConfigRoot = expPath ctx.LoadConfig = func(path string) (*config.Config, error) { return fsrepo.ConfigAt(expPath) } ctx.ConstructNode = func() (*ipfscore.IpfsNode, error) { return nd, nil } log.Info("Peer ID: ", nd.Identity.Pretty()) printSwarmAddrs(nd) // Get current directory root hash _, ipnskey := namesys.IpnsKeysForID(nd.Identity) ival, _ := nd.Repo.Datastore().Get(ipnskey.DsKey()) val := ival.([]byte) dhtrec := new(dhtpb.Record) proto.Unmarshal(val, dhtrec) e := new(namepb.IpnsEntry) proto.Unmarshal(dhtrec.GetValue(), e) // Wallet mn, err := sqliteDB.Config().GetMnemonic() if err != nil { log.Error(err) return err } var params chaincfg.Params if !x.Testnet { params = chaincfg.MainNetParams } else { params = chaincfg.TestNet3Params } libbitcoinServers, err := repo.GetLibbitcoinServers(path.Join(expPath, "config")) if err != nil { log.Error(err) return err } maxFee, err := repo.GetMaxFee(path.Join(expPath, "config")) if err != nil { log.Error(err) return err } feeApi, err := repo.GetFeeAPI(path.Join(expPath, "config")) if err != nil { log.Error(err) return err } low, medium, high, err := repo.GetDefaultFees(path.Join(expPath, "config")) if err != nil { log.Error(err) return err } wallet := libbitcoin.NewLibbitcoinWallet(mn, ¶ms, sqliteDB, libbitcoinServers, maxFee, low, medium, high, feeApi) // Offline messaging storage var storage sto.OfflineMessagingStorage if x.Storage == "self-hosted" || x.Storage == "" { storage = selfhosted.NewSelfHostedStorage(expPath, ctx) } else if x.Storage == "dropbox" { token, err := repo.GetDropboxApiToken(path.Join(expPath, "config")) if err != nil { log.Error(err) return err } else if token == "" { err = errors.New("Dropbox token not set in config file") log.Error(err) return err } storage, err = dropbox.NewDropBoxStorage(token) if err != nil { log.Error(err) return err } } else { err = errors.New("Invalid storage option") log.Error(err) return err } // OpenBazaar node setup core.Node = &core.OpenBazaarNode{ Context: ctx, IpfsNode: nd, RootHash: ipath.Path(e.Value).String(), RepoPath: expPath, Datastore: sqliteDB, Wallet: wallet, MessageStorage: storage, } var gwErrc <-chan error var cb <-chan bool if len(cfg.Addresses.Gateway) > 0 { var err error err, cb, gwErrc = serveHTTPGateway(core.Node) if err != nil { log.Error(err) return err } } // Wait for gateway to start before starting the network service. // This way the websocket channel we pass into the service gets created first. // FIXME: There has to be a better way for b := range cb { if b == true { OBService := service.SetupOpenBazaarService(nd, core.Node.Broadcast, ctx, sqliteDB) core.Node.Service = OBService MR := net.NewMessageRetriever(sqliteDB, ctx, nd, OBService, 16, core.Node.SendOfflineAck) go MR.Run() core.Node.MessageRetriever = MR PR := net.NewPointerRepublisher(nd, sqliteDB) go PR.Run() core.Node.PointerRepublisher = PR } break } for err := range gwErrc { fmt.Println(err) } return nil }
// FromNetAddr converts a net.Addr type to a Multiaddr. func FromNetAddr(a net.Addr) (ma.Multiaddr, error) { if a == nil { return nil, fmt.Errorf("nil multiaddr") } switch a.Network() { case "tcp", "tcp4", "tcp6": ac, ok := a.(*net.TCPAddr) if !ok { return nil, errIncorrectNetAddr } // Get IP Addr ipm, err := FromIP(ac.IP) if err != nil { return nil, errIncorrectNetAddr } // Get TCP Addr tcpm, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", ac.Port)) if err != nil { return nil, errIncorrectNetAddr } // Encapsulate return ipm.Encapsulate(tcpm), nil case "udp", "upd4", "udp6": ac, ok := a.(*net.UDPAddr) if !ok { return nil, errIncorrectNetAddr } // Get IP Addr ipm, err := FromIP(ac.IP) if err != nil { return nil, errIncorrectNetAddr } // Get UDP Addr udpm, err := ma.NewMultiaddr(fmt.Sprintf("/udp/%d", ac.Port)) if err != nil { return nil, errIncorrectNetAddr } // Encapsulate return ipm.Encapsulate(udpm), nil case "utp", "utp4", "utp6": acc, ok := a.(*utp.Addr) if !ok { return nil, errIncorrectNetAddr } // Get UDP Addr ac, ok := acc.Child().(*net.UDPAddr) if !ok { return nil, errIncorrectNetAddr } // Get IP Addr ipm, err := FromIP(ac.IP) if err != nil { return nil, errIncorrectNetAddr } // Get UDP Addr utpm, err := ma.NewMultiaddr(fmt.Sprintf("/udp/%d/utp", ac.Port)) if err != nil { return nil, errIncorrectNetAddr } // Encapsulate return ipm.Encapsulate(utpm), nil case "ip", "ip4", "ip6": ac, ok := a.(*net.IPAddr) if !ok { return nil, errIncorrectNetAddr } return FromIP(ac.IP) case "ip+net": ac, ok := a.(*net.IPNet) if !ok { return nil, errIncorrectNetAddr } return FromIP(ac.IP) default: return nil, fmt.Errorf("unknown network %v", a.Network()) } }
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests func serveHTTPApi(req cmds.Request) (error, <-chan error) { cfg, err := req.InvocContext().GetConfig() if err != nil { return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil } apiAddr, _, err := req.Option(commands.ApiOption).String() if err != nil { return fmt.Errorf("serveHTTPApi: %s", err), nil } if apiAddr == "" { apiAddr = cfg.Addresses.API } apiMaddr, err := ma.NewMultiaddr(apiAddr) if err != nil { return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err), nil } apiLis, err := manet.Listen(apiMaddr) if err != nil { return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil } // we might have listened to /tcp/0 - lets see what we are listing on apiMaddr = apiLis.Multiaddr() fmt.Printf("API server listening on %s\n", apiMaddr) // by default, we don't let you load arbitrary ipfs objects through the api, // because this would open up the api to scripting vulnerabilities. // only the webui objects are allowed. // if you know what you're doing, go ahead and pass --unrestricted-api. unrestricted, _, err := req.Option(unrestrictedApiAccessKwd).Bool() if err != nil { return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil } gatewayOpt := corehttp.GatewayOption(corehttp.WebUIPaths...) if unrestricted { gatewayOpt = corehttp.GatewayOption("/ipfs", "/ipns") } var opts = []corehttp.ServeOption{ corehttp.MetricsCollectionOption("api"), corehttp.CommandsOption(*req.InvocContext()), corehttp.WebUIOption, gatewayOpt, corehttp.VersionOption(), defaultMux("/debug/vars"), defaultMux("/debug/pprof/"), corehttp.MetricsScrapingOption("/debug/metrics/prometheus"), corehttp.LogOption(), } if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } node, err := req.InvocContext().ConstructNode() if err != nil { return fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err), nil } if err := node.Repo.SetAPIAddr(apiMaddr.String()); err != nil { return fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err), nil } errc := make(chan error) go func() { errc <- corehttp.Serve(node, apiLis.NetListener(), opts...) close(errc) }() return nil, errc }