diff --git a/README.md b/README.md index a32d6465..b8ac7b96 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ ndnd fw run yanfd.config.yml A full configuration example can be found in [fw/yanfd.sample.yml](fw/yanfd.sample.yml). Note that the default configuration may require root privileges to bind to multicast interfaces. +Once started, you can use the [Forwarder Control](tools/nfdc/README.md) tool to manage faces and routes. + ## 📡 Distance Vector Router The `ndnd/dv` package implements `ndn-dv`, an NDN Distance Vector routing daemon. @@ -76,6 +78,8 @@ ndnd dv run dv.config.yml A full configuration example can be found in [dv/dv.sample.yml](dv/dv.sample.yml). Make sure the network and router name are correctly configured and the forwarder is running. +Once started, you can use the [DV Control](tools/dvc/README.md) tool to create and destroy neighbor links. + ## 📚 Standard Library The `ndnd/std` package implements `go-ndn`, a standard library for NDN applications. diff --git a/cmd/ndnd/main.go b/cmd/ndnd/main.go index 7fc25789..38a7b4e5 100644 --- a/cmd/ndnd/main.go +++ b/cmd/ndnd/main.go @@ -3,32 +3,46 @@ package main import ( "os" - "github.com/named-data/ndnd/cmd" dv "github.com/named-data/ndnd/dv/executor" fw "github.com/named-data/ndnd/fw/executor" + "github.com/named-data/ndnd/std/utils" tools "github.com/named-data/ndnd/tools" + dvc "github.com/named-data/ndnd/tools/dvc" + nfdc "github.com/named-data/ndnd/tools/nfdc" ) func main() { + // subtrees from other packages + nfdcTree := nfdc.GetNfdcCmdTree() + // create a command tree - tree := cmd.CmdTree{ + tree := utils.CmdTree{ Name: "ndnd", Help: "Named Data Networking Daemon", - Sub: []*cmd.CmdTree{{ + Sub: []*utils.CmdTree{{ Name: "fw", Help: "NDN Forwarding Daemon", - Sub: []*cmd.CmdTree{{ + Sub: append([]*utils.CmdTree{{ Name: "run", Help: "Start the NDN Forwarding Daemon", Fun: fw.Main, - }}, + }, {}, + }, nfdcTree.Sub...), }, { Name: "dv", Help: "NDN Distance Vector Routing Daemon", - Sub: []*cmd.CmdTree{{ + Sub: []*utils.CmdTree{{ Name: "run", Help: "Start the NDN Distance Vector Routing Daemon", Fun: dv.Main, + }, {}, { + Name: "link create", + Help: "Create a new active neighbor link", + Fun: dvc.RunDvLinkCreate(&nfdcTree), + }, { + Name: "link destroy", + Help: "Destroy an active neighbor link", + Fun: dvc.RunDvLinkDestroy(&nfdcTree), }}, }, { // tools separator diff --git a/dv/config/config.go b/dv/config/config.go index 5cd51d1e..74159864 100644 --- a/dv/config/config.go +++ b/dv/config/config.go @@ -41,7 +41,7 @@ type Config struct { // Prefix Table Data Prefix pfxDataPfxN enc.Name // NLSR readvertise prefix - readvertisePfxN enc.Name + localPfxN enc.Name } func DefaultConfig() *Config { @@ -103,7 +103,7 @@ func (c *Config) Parse() (err error) { enc.NewStringComponent(enc.TypeKeywordNameComponent, "DV"), enc.NewStringComponent(enc.TypeKeywordNameComponent, "PFX"), ) - c.readvertisePfxN = append(Localhost, + c.localPfxN = append(Localhost, enc.NewStringComponent(enc.TypeGenericNameComponent, "nlsr"), ) @@ -142,8 +142,20 @@ func (c *Config) PrefixTableDataPrefix() enc.Name { return c.pfxDataPfxN } +func (c *Config) LocalPrefix() enc.Name { + return c.localPfxN +} + func (c *Config) ReadvertisePrefix() enc.Name { - return c.readvertisePfxN + return append(c.localPfxN, + enc.NewStringComponent(enc.TypeGenericNameComponent, "rib"), + ) +} + +func (c *Config) StatusPrefix() enc.Name { + return append(c.localPfxN, + enc.NewStringComponent(enc.TypeGenericNameComponent, "status"), + ) } func (c *Config) AdvertisementSyncInterval() time.Duration { diff --git a/dv/dv.sample.yml b/dv/dv.sample.yml index 0a8fceda..9d60b112 100644 --- a/dv/dv.sample.yml +++ b/dv/dv.sample.yml @@ -1,8 +1,4 @@ -nfd: - # [optional] Unix socket used to connect to the local forwarder - unix: /var/run/nfd/nfd.sock - -config: +dv: # [required] Global prefix for all DV routers in the network network: /ndn # [required] Unique name for each router in the network diff --git a/dv/dv/router.go b/dv/dv/router.go index c610bd95..fb357769 100644 --- a/dv/dv/router.go +++ b/dv/dv/router.go @@ -197,13 +197,22 @@ func (dv *Router) register() (err error) { return err } + // Router status + err = dv.engine.AttachHandler(dv.config.StatusPrefix(), + func(args ndn.InterestHandlerArgs) { + go dv.statusOnInterest(args) + }) + if err != nil { + return err + } + // Register routes to forwarder pfxs := []enc.Name{ dv.config.AdvertisementSyncPrefix(), dv.config.AdvertisementDataPrefix(), dv.config.PrefixTableSyncPrefix(), dv.config.PrefixTableDataPrefix(), - dv.config.ReadvertisePrefix(), + dv.config.LocalPrefix(), } for _, prefix := range pfxs { dv.nfdc.Exec(nfdc.NfdMgmtCmd{ diff --git a/dv/dv/status.go b/dv/dv/status.go new file mode 100644 index 00000000..75395b52 --- /dev/null +++ b/dv/dv/status.go @@ -0,0 +1,34 @@ +package dv + +import ( + "time" + + "github.com/named-data/ndnd/dv/tlv" + "github.com/named-data/ndnd/std/log" + "github.com/named-data/ndnd/std/ndn" + "github.com/named-data/ndnd/std/security" + "github.com/named-data/ndnd/std/utils" +) + +// Received advertisement Interest +func (dv *Router) statusOnInterest(args ndn.InterestHandlerArgs) { + status := tlv.Status{ + NetworkName: &tlv.Destination{Name: dv.config.NetworkName()}, + RouterName: &tlv.Destination{Name: dv.config.RouterName()}, + } + + name := args.Interest.Name() + cfg := &ndn.DataConfig{ + ContentType: utils.IdPtr(ndn.ContentTypeBlob), + Freshness: utils.IdPtr(time.Second), + } + signer := security.NewSha256Signer() + + data, err := dv.engine.Spec().MakeData(name, cfg, status.Encode(), signer) + if err != nil { + log.Warnf("readvertise: failed to make response Data: %+v", err) + return + } + + args.Reply(data.Wire) +} diff --git a/dv/executor/executor.go b/dv/executor/executor.go index fe1748aa..c93acc03 100644 --- a/dv/executor/executor.go +++ b/dv/executor/executor.go @@ -10,20 +10,13 @@ import ( ) type DvConfig struct { - // NFD related options - Nfd struct { - Unix string `json:"unix"` - } `json:"nfd"` - - // Underlying configuration - Config *config.Config `json:"config"` + Config *config.Config `json:"dv"` } func DefaultConfig() DvConfig { - dc := DvConfig{} - dc.Nfd.Unix = "/var/run/nfd/nfd.sock" - dc.Config = config.DefaultConfig() - return dc + return DvConfig{ + Config: config.DefaultConfig(), + } } func (dc DvConfig) Parse() error { @@ -45,7 +38,7 @@ func NewDvExecutor(dc DvConfig) (*DvExecutor, error) { } // Start NDN engine - dve.engine = engine.NewBasicEngine(engine.NewUnixFace(dc.Nfd.Unix)) + dve.engine = engine.NewBasicEngine(engine.NewDefaultFace()) // Create the DV router dve.router, err = dv.NewRouter(dc.Config, dve.engine) diff --git a/dv/nfdc/nfdc.go b/dv/nfdc/nfdc.go index 7f26d5f1..f7ac5a44 100644 --- a/dv/nfdc/nfdc.go +++ b/dv/nfdc/nfdc.go @@ -37,7 +37,7 @@ func (m *NfdMgmtThread) Start() { select { case cmd := <-m.channel: for i := 0; i < cmd.Retries || cmd.Retries < 0; i++ { - err := m.engine.ExecMgmtCmd(cmd.Module, cmd.Cmd, cmd.Args) + _, err := m.engine.ExecMgmtCmd(cmd.Module, cmd.Cmd, cmd.Args) if err != nil { log.Errorf("nfdc %s %s failed: %s %+v [%d]", cmd.Module, cmd.Cmd, cmd.Args.Name, err, i) time.Sleep(100 * time.Millisecond) diff --git a/dv/table/neighbor_table.go b/dv/table/neighbor_table.go index 6706cd2c..96cecb76 100644 --- a/dv/table/neighbor_table.go +++ b/dv/table/neighbor_table.go @@ -96,16 +96,18 @@ func (ns *NeighborState) IsDead() bool { // and update the last seen time for the neighbor. // Return => true if the face ID has changed func (ns *NeighborState) RecvPing(faceId uint64, active bool) (error, bool) { + if ns.isFaceActive && !active { + // This ping is passive, but we already have an active ping. + return nil, false // ignore this ping. + } + // Update last seen time for neighbor + // Note that we skip this when the face is active and the ping is passive. + // This is because we want to detect if the active face is removed. ns.lastSeen = time.Now() // If face ID has changed, re-register face. if ns.faceId != faceId { - if ns.isFaceActive && !active { - // This ping is passive, but we already have an active ping. - return nil, false // ignore this ping. - } - ns.isFaceActive = active log.Infof("neighbor: %s face ID changed from %d to %d", ns.Name, ns.faceId, faceId) ns.routeUnregister() diff --git a/dv/tlv/tlv.go b/dv/tlv/tlv.go index 98b59c45..b7d27ef5 100644 --- a/dv/tlv/tlv.go +++ b/dv/tlv/tlv.go @@ -53,3 +53,10 @@ type PrefixOpRemove struct { //+field:name Name enc.Name `tlv:"0x07"` } + +type Status struct { + //+field:struct:Destination + NetworkName *Destination `tlv:"0x191"` + //+field:struct:Destination + RouterName *Destination `tlv:"0x193"` +} diff --git a/dv/tlv/zz_generated.go b/dv/tlv/zz_generated.go index 3d8034cb..dd182777 100644 --- a/dv/tlv/zz_generated.go +++ b/dv/tlv/zz_generated.go @@ -1816,3 +1816,222 @@ func ParsePrefixOpRemove(reader enc.ParseReader, ignoreCritical bool) (*PrefixOp context.Init() return context.Parse(reader, ignoreCritical) } + +type StatusEncoder struct { + length uint + + NetworkName_encoder DestinationEncoder + RouterName_encoder DestinationEncoder +} + +type StatusParsingContext struct { + NetworkName_context DestinationParsingContext + RouterName_context DestinationParsingContext +} + +func (encoder *StatusEncoder) Init(value *Status) { + if value.NetworkName != nil { + encoder.NetworkName_encoder.Init(value.NetworkName) + } + if value.RouterName != nil { + encoder.RouterName_encoder.Init(value.RouterName) + } + + l := uint(0) + if value.NetworkName != nil { + l += 3 + switch x := encoder.NetworkName_encoder.length; { + case x <= 0xfc: + l += 1 + case x <= 0xffff: + l += 3 + case x <= 0xffffffff: + l += 5 + default: + l += 9 + } + l += encoder.NetworkName_encoder.length + } + if value.RouterName != nil { + l += 3 + switch x := encoder.RouterName_encoder.length; { + case x <= 0xfc: + l += 1 + case x <= 0xffff: + l += 3 + case x <= 0xffffffff: + l += 5 + default: + l += 9 + } + l += encoder.RouterName_encoder.length + } + encoder.length = l + +} + +func (context *StatusParsingContext) Init() { + context.NetworkName_context.Init() + context.RouterName_context.Init() +} + +func (encoder *StatusEncoder) EncodeInto(value *Status, buf []byte) { + + pos := uint(0) + + if value.NetworkName != nil { + buf[pos] = 253 + binary.BigEndian.PutUint16(buf[pos+1:], uint16(401)) + pos += 3 + switch x := encoder.NetworkName_encoder.length; { + case x <= 0xfc: + buf[pos] = byte(x) + pos += 1 + case x <= 0xffff: + buf[pos] = 0xfd + binary.BigEndian.PutUint16(buf[pos+1:], uint16(x)) + pos += 3 + case x <= 0xffffffff: + buf[pos] = 0xfe + binary.BigEndian.PutUint32(buf[pos+1:], uint32(x)) + pos += 5 + default: + buf[pos] = 0xff + binary.BigEndian.PutUint64(buf[pos+1:], uint64(x)) + pos += 9 + } + if encoder.NetworkName_encoder.length > 0 { + encoder.NetworkName_encoder.EncodeInto(value.NetworkName, buf[pos:]) + pos += encoder.NetworkName_encoder.length + } + } + if value.RouterName != nil { + buf[pos] = 253 + binary.BigEndian.PutUint16(buf[pos+1:], uint16(403)) + pos += 3 + switch x := encoder.RouterName_encoder.length; { + case x <= 0xfc: + buf[pos] = byte(x) + pos += 1 + case x <= 0xffff: + buf[pos] = 0xfd + binary.BigEndian.PutUint16(buf[pos+1:], uint16(x)) + pos += 3 + case x <= 0xffffffff: + buf[pos] = 0xfe + binary.BigEndian.PutUint32(buf[pos+1:], uint32(x)) + pos += 5 + default: + buf[pos] = 0xff + binary.BigEndian.PutUint64(buf[pos+1:], uint64(x)) + pos += 9 + } + if encoder.RouterName_encoder.length > 0 { + encoder.RouterName_encoder.EncodeInto(value.RouterName, buf[pos:]) + pos += encoder.RouterName_encoder.length + } + } +} + +func (encoder *StatusEncoder) Encode(value *Status) enc.Wire { + + wire := make(enc.Wire, 1) + wire[0] = make([]byte, encoder.length) + buf := wire[0] + encoder.EncodeInto(value, buf) + + return wire +} + +func (context *StatusParsingContext) Parse(reader enc.ParseReader, ignoreCritical bool) (*Status, error) { + if reader == nil { + return nil, enc.ErrBufferOverflow + } + + var handled_NetworkName bool = false + var handled_RouterName bool = false + + progress := -1 + _ = progress + + value := &Status{} + var err error + var startPos int + for { + startPos = reader.Pos() + if startPos >= reader.Length() { + break + } + typ := enc.TLNum(0) + l := enc.TLNum(0) + typ, err = enc.ReadTLNum(reader) + if err != nil { + return nil, enc.ErrFailToParse{TypeNum: 0, Err: err} + } + l, err = enc.ReadTLNum(reader) + if err != nil { + return nil, enc.ErrFailToParse{TypeNum: 0, Err: err} + } + + err = nil + if handled := false; true { + switch typ { + case 401: + if true { + handled = true + handled_NetworkName = true + value.NetworkName, err = context.NetworkName_context.Parse(reader.Delegate(int(l)), ignoreCritical) + } + case 403: + if true { + handled = true + handled_RouterName = true + value.RouterName, err = context.RouterName_context.Parse(reader.Delegate(int(l)), ignoreCritical) + } + default: + if !ignoreCritical && ((typ <= 31) || ((typ & 1) == 1)) { + return nil, enc.ErrUnrecognizedField{TypeNum: typ} + } + handled = true + err = reader.Skip(int(l)) + } + if err == nil && !handled { + } + if err != nil { + return nil, enc.ErrFailToParse{TypeNum: typ, Err: err} + } + } + } + + startPos = reader.Pos() + err = nil + + if !handled_NetworkName && err == nil { + value.NetworkName = nil + } + if !handled_RouterName && err == nil { + value.RouterName = nil + } + + if err != nil { + return nil, err + } + + return value, nil +} + +func (value *Status) Encode() enc.Wire { + encoder := StatusEncoder{} + encoder.Init(value) + return encoder.Encode(value) +} + +func (value *Status) Bytes() []byte { + return value.Encode().Join() +} + +func ParseStatus(reader enc.ParseReader, ignoreCritical bool) (*Status, error) { + context := StatusParsingContext{} + context.Init() + return context.Parse(reader, ignoreCritical) +} diff --git a/fw/README.md b/fw/README.md index 9165379a..eb702800 100644 --- a/fw/README.md +++ b/fw/README.md @@ -37,7 +37,6 @@ yanfd /etc/ndn/yanfd.yml ``` *Runtime configuration* is performed via the [NFD Management Protocol](https://redmine.named-data.net/projects/nfd/wiki/Management). -At the moment, this requires the installation of the [NFD](https://github.com/named-data/NFD) package to obtain the `nfdc` configuration utility. ## Building from source diff --git a/fw/core/config.go b/fw/core/config.go index 138abcd7..5b7971a7 100644 --- a/fw/core/config.go +++ b/fw/core/config.go @@ -31,6 +31,10 @@ type Config struct { LockThreadsToCores bool `json:"lock_threads_to_cores"` Udp struct { + // Whether to enable unicast UDP listener + EnabledUnicast bool `json:"enabled_unicast"` + // Whether to enable multicast UDP listener + EnabledMulticast bool `json:"enabled_multicast"` // Port used for unicast UDP faces PortUnicast uint16 `json:"port_unicast"` // Port used for multicast UDP faces @@ -143,6 +147,8 @@ func DefaultConfig() *Config { c.Faces.CongestionMarking = true c.Faces.LockThreadsToCores = false + c.Faces.Udp.EnabledUnicast = true + c.Faces.Udp.EnabledMulticast = true c.Faces.Udp.PortUnicast = 6363 c.Faces.Udp.PortMulticast = 56363 c.Faces.Udp.MulticastAddressIpv4 = "224.0.23.170" diff --git a/fw/defn/uri.go b/fw/defn/uri.go index 53d66da6..3fb472d9 100644 --- a/fw/defn/uri.go +++ b/fw/defn/uri.go @@ -22,12 +22,8 @@ import ( // URIType represents the type of the URI. type URIType int -const devPattern = `^(?Pdev)://(?P[A-Za-z0-9\-]+)$` -const fdPattern = `^(?Pfd)://(?P[0-9]+)$` -const ipv4Pattern = `^((25[0-4]|2[0-4][0-9]|1[0-9][0-9]|[0-9][0-9]|[0-9])\.){3}(25[0-4]|2[0-4][0-9]|1[0-9][0-9]|[0-9][0-9]|[0-9])$` -const udpPattern = `^(?Pudp[46]?)://\[?(?P[0-9A-Za-z\:\.\-]+)(%(?P[A-Za-z0-9\-]+))?\]?:(?P[0-9]+)$` -const tcpPattern = `^(?Ptcp[46]?)://\[?(?P[0-9A-Za-z\:\.\-]+)(%(?P[A-Za-z0-9\-]+))?\]?:(?P[0-9]+)$` -const unixPattern = `^(?Punix)://(?P[/\\A-Za-z0-9\:\.\-_]+)$` +// Regex to extract zone from URI +var zoneRegex, _ = regexp.Compile(`:\/\/\[?(?:[0-9A-Za-z\:\.\-]+)(?:%(?P[A-Za-z0-9\-]+))?\]?`) const ( unknownURI URIType = iota @@ -95,10 +91,6 @@ func MakeNullFaceURI() *URI { // MakeUDPFaceURI constructs a URI for a UDP face. func MakeUDPFaceURI(ipVersion int, host string, port uint16) *URI { - /*path := host - if zone != "" { - path += "%" + zone - }*/ uri := new(URI) uri.uriType = udpURI uri.scheme = "udp" + strconv.Itoa(ipVersion) @@ -153,141 +145,81 @@ func MakeWebSocketClientFaceURI(addr net.Addr) *URI { } } -// DecodeURIString decodes a URI from a string. func DecodeURIString(str string) *URI { - u := new(URI) - u.uriType = unknownURI - u.scheme = "unknown" - schemeSplit := strings.SplitN(str, ":", 2) - if len(schemeSplit) < 2 { - // No scheme - return u + ret := &URI{ + uriType: unknownURI, + scheme: "unknown", } - switch { - case strings.EqualFold("dev", schemeSplit[0]): - u.uriType = devURI - u.scheme = "dev" - - regex, err := regexp.Compile(devPattern) - if err != nil { - return u - } - - matches := regex.FindStringSubmatch(str) - if regex.SubexpIndex("ifname") < 0 || len(matches) <= regex.SubexpIndex("ifname") { - return u - } - - ifname := matches[regex.SubexpIndex("ifname")] - // Pure function is not allowed to have side effect - // _, err = net.InterfaceByName(ifname) - // if err != nil { - // return u - // } - u.path = ifname - case strings.EqualFold("fd", schemeSplit[0]): - u.uriType = fdURI - u.scheme = "fd" - - regex, err := regexp.Compile(fdPattern) - if err != nil { - return u - } + // extract zone if present first, since this is non-standard + var zone string = "" + zoneMatch := zoneRegex.FindStringSubmatch(str) + if len(zoneMatch) > zoneRegex.SubexpIndex("zone") { + zone = zoneMatch[zoneRegex.SubexpIndex("zone")] + str = strings.Replace(str, "%"+zone, "", 1) + } - matches := regex.FindStringSubmatch(str) - // fmt.Println(matches, len(matches), regex.SubexpIndex("fd")) - if regex.SubexpIndex("fd") < 0 || len(matches) <= regex.SubexpIndex("fd") { - return u - } - u.path = matches[regex.SubexpIndex("fd")] - case strings.EqualFold("internal", schemeSplit[0]): - u.uriType = internalURI - u.scheme = "internal" - case strings.EqualFold("null", schemeSplit[0]): - u.uriType = nullURI - u.scheme = "null" - case strings.EqualFold("udp", schemeSplit[0]), - strings.EqualFold("udp4", schemeSplit[0]), - strings.EqualFold("udp6", schemeSplit[0]): - u.uriType = udpURI - u.scheme = "udp" - - regex, err := regexp.Compile(udpPattern) - if err != nil { - return u - } + // parse common URI schemes + uri, err := url.Parse(str) + if err != nil { + return ret + } - matches := regex.FindStringSubmatch(str) - if regex.SubexpIndex("host") < 0 || len(matches) <= regex.SubexpIndex("host") || regex.SubexpIndex("port") < 0 || len(matches) <= regex.SubexpIndex("port") { - return u - } - u.path = matches[regex.SubexpIndex("host")] - if regex.SubexpIndex("zone") < 0 || len(matches) >= regex.SubexpIndex("zone") && matches[regex.SubexpIndex("zone")] != "" { - u.path += "%" + matches[regex.SubexpIndex("zone")] - } - port, err := strconv.ParseUint(matches[regex.SubexpIndex("port")], 10, 16) - if err != nil || port <= 0 || port > 65535 { - return u - } - u.port = uint16(port) - case strings.EqualFold("tcp", schemeSplit[0]), - strings.EqualFold("tcp4", schemeSplit[0]), - strings.EqualFold("tcp6", schemeSplit[0]): - u.uriType = tcpURI - u.scheme = "tcp" - - regex, err := regexp.Compile(tcpPattern) - if err != nil { - return u + switch uri.Scheme { + case "dev": + ret.uriType = devURI + ret.scheme = uri.Scheme + ret.path = uri.Host + case "fd": + ret.uriType = fdURI + ret.scheme = uri.Scheme + ret.path = uri.Host + case "internal": + ret.uriType = internalURI + ret.scheme = uri.Scheme + case "null": + ret.uriType = nullURI + ret.scheme = uri.Scheme + case "udp", "udp4", "udp6", "tcp", "tcp4", "tcp6": + if strings.HasPrefix(uri.Scheme, "udp") { + ret.uriType = udpURI + } else { + ret.uriType = tcpURI } - matches := regex.FindStringSubmatch(str) - if regex.SubexpIndex("host") < 0 || len(matches) <= regex.SubexpIndex("host") || regex.SubexpIndex("port") < 0 || len(matches) <= regex.SubexpIndex("port") { - return u - } - u.path = matches[regex.SubexpIndex("host")] - if regex.SubexpIndex("zone") < 0 || len(matches) >= regex.SubexpIndex("zone") && matches[regex.SubexpIndex("zone")] != "" { - u.path += "%" + matches[regex.SubexpIndex("zone")] - } - port, err := strconv.ParseUint(matches[regex.SubexpIndex("port")], 10, 16) - if err != nil || port <= 0 || port > 65535 { - return u + ret.scheme = uri.Scheme + ret.path = uri.Hostname() + if uri.Port() != "" { + port, _ := strconv.ParseUint(uri.Port(), 10, 16) + ret.port = uint16(port) + } else { + ret.port = uint16(6363) // default NDN port } - u.port = uint16(port) - case strings.EqualFold("unix", schemeSplit[0]): - u.uriType = unixURI - u.scheme = "unix" - regex, err := regexp.Compile(unixPattern) - if err != nil { - return u + if zone != "" { + ret.path += "%" + zone } - matches := regex.FindStringSubmatch(str) - if len(matches) != 3 { - return u - } - u.path = matches[2] - case strings.EqualFold("ws", schemeSplit[0]), - strings.EqualFold("wss", schemeSplit[0]): - uri, e := url.Parse(str) - if e != nil || uri.User != nil || strings.TrimLeft(uri.Path, "/") != "" || - uri.RawQuery != "" || uri.Fragment != "" { + case "unix": + ret.uriType = unixURI + ret.scheme = uri.Scheme + ret.path = uri.Path + case "ws", "wss": + if uri.User != nil || strings.TrimLeft(uri.Path, "/") != "" || uri.RawQuery != "" || uri.Fragment != "" { return nil } return MakeWebSocketServerFaceURI(uri) - case strings.EqualFold("wsclient", schemeSplit[0]): - addr, e := net.ResolveTCPAddr("tcp", strings.Trim(schemeSplit[1], "/")) - if e != nil { + case "wsclient": + addr, err := net.ResolveTCPAddr("tcp", uri.Host) + if err != nil { return nil } return MakeWebSocketClientFaceURI(addr) } - // Canonize, if possible - u.Canonize() - return u + ret.Canonize() + + return ret } // URIType returns the type of the face URI. @@ -346,14 +278,15 @@ func (u *URI) IsCanonical() bool { ip := net.ParseIP(u.PathHost()) // Port number is implicitly limited to <= 65535 by type uint16 // We have to test whether To16() && not IPv4 because the Go net library considers IPv4 addresses to be valid IPv6 addresses - isIPv4, _ := regexp.MatchString(ipv4Pattern, u.PathHost()) - return ip != nil && ((u.scheme == "udp4" && ip.To4() != nil) || (u.scheme == "udp6" && ip.To16() != nil && !isIPv4)) && u.port > 0 + isIPv4 := ip.To4() != nil + return ip != nil && ((u.scheme == "udp4" && isIPv4) || + (u.scheme == "udp6" && ip.To16() != nil && !isIPv4)) && u.port > 0 case tcpURI: // Split off zone, if any ip := net.ParseIP(u.PathHost()) // Port number is implicitly limited to <= 65535 by type uint16 // We have to test whether To16() && not IPv4 because the Go net library considers IPv4 addresses to be valid IPv6 addresses - isIPv4, _ := regexp.MatchString(ipv4Pattern, u.PathHost()) + isIPv4 := ip.To4() != nil return ip != nil && u.port > 0 && ((u.scheme == "tcp4" && ip.To4() != nil) || (u.scheme == "tcp6" && ip.To16() != nil && !isIPv4)) case unixURI: diff --git a/fw/defn/uri_test.go b/fw/defn/uri_test.go new file mode 100644 index 00000000..283f0f2a --- /dev/null +++ b/fw/defn/uri_test.go @@ -0,0 +1,116 @@ +package defn_test + +import ( + "strings" + "testing" + + "github.com/named-data/ndnd/fw/defn" + "github.com/stretchr/testify/assert" +) + +func TestDecodeUri(t *testing.T) { + var uri *defn.URI + + // Unknown URI + uri = defn.DecodeURIString("test://myhost:1234") + assert.False(t, uri.IsCanonical()) + assert.Equal(t, "unknown", uri.Scheme()) + + // Device URI + uri = defn.DecodeURIString("dev://eth0") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "dev", uri.Scheme()) + assert.Equal(t, "eth0", uri.PathHost()) + + // FD URI + uri = defn.DecodeURIString("fd://3") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "fd", uri.Scheme()) + assert.Equal(t, "3", uri.PathHost()) + + // Internal URI + uri = defn.DecodeURIString("internal://") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "internal", uri.Scheme()) + + // NULL URI + uri = defn.DecodeURIString("null://") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "null", uri.Scheme()) + + // Unix URI + uri = defn.DecodeURIString("unix:///tmp/test.sock") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "unix", uri.Scheme()) + assert.Equal(t, "/tmp/test.sock", uri.PathHost()) + assert.Equal(t, uint16(0), uri.Port()) + + // UDP URI + uri = defn.DecodeURIString("udp://127.0.0.1:5000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp4", uri.Scheme()) + assert.Equal(t, "127.0.0.1", uri.PathHost()) + assert.Equal(t, uint16(5000), uri.Port()) + + uri = defn.DecodeURIString("udp://[2001:db8::1]:5000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp6", uri.Scheme()) + assert.Equal(t, "2001:db8::1", uri.PathHost()) + assert.Equal(t, uint16(5000), uri.Port()) + + // TCP URI + uri = defn.DecodeURIString("tcp://127.0.0.1:4600") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "tcp4", uri.Scheme()) + assert.Equal(t, "127.0.0.1", uri.PathHost()) + assert.Equal(t, uint16(4600), uri.Port()) + + uri = defn.DecodeURIString("tcp://[2002:db8::1]:4600") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "tcp6", uri.Scheme()) + assert.Equal(t, "2002:db8::1", uri.PathHost()) + assert.Equal(t, uint16(4600), uri.Port()) + + // Ws URI (server) + uri = defn.DecodeURIString("ws://0.0.0.0:5000") + assert.False(t, uri.IsCanonical()) + assert.Equal(t, "ws", uri.Scheme()) + assert.Equal(t, uint16(5000), uri.Port()) + + // Ws URI (client) + uri = defn.DecodeURIString("wsclient://127.0.0.1:800") + assert.False(t, uri.IsCanonical()) + assert.Equal(t, "wsclient", uri.Scheme()) + assert.Equal(t, "127.0.0.1", uri.PathHost()) + assert.Equal(t, uint16(800), uri.Port()) + + // UDP4 with zone + uri = defn.DecodeURIString("udp://127.0.0.1%eth0:3000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp4", uri.Scheme()) + assert.Equal(t, "127.0.0.1", uri.PathHost()) + assert.Equal(t, "127.0.0.1%eth0", uri.Path()) + assert.Equal(t, "eth0", uri.PathZone()) + assert.Equal(t, uint16(3000), uri.Port()) + + // UDP6 with zone + uri = defn.DecodeURIString("udp://[2001:db8::1%eth0]:3000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp6", uri.Scheme()) + assert.Equal(t, "2001:db8::1", uri.PathHost()) + assert.Equal(t, "2001:db8::1%eth0", uri.Path()) + assert.Equal(t, "eth0", uri.PathZone()) + assert.Equal(t, uint16(3000), uri.Port()) + + // Test name resolution for IPv4 and IPv6 + uri = defn.DecodeURIString("udp://ipv4.google.com:5000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp4", uri.Scheme()) + assert.Equal(t, 3, strings.Count(uri.PathHost(), ".")) + assert.Equal(t, uint16(5000), uri.Port()) + + uri = defn.DecodeURIString("udp://ipv6.google.com:5000") + assert.True(t, uri.IsCanonical()) + assert.Equal(t, "udp6", uri.Scheme()) + assert.Equal(t, uint16(5000), uri.Port()) +} diff --git a/fw/executor/main.go b/fw/executor/main.go index ba924241..ac9a6435 100644 --- a/fw/executor/main.go +++ b/fw/executor/main.go @@ -70,7 +70,7 @@ func Main(args []string) { sigChannel := make(chan os.Signal, 1) signal.Notify(sigChannel, os.Interrupt, syscall.SIGTERM) receivedSig := <-sigChannel - core.LogInfo("Main", "Received signal ", receivedSig, " - exiting") + core.LogInfo(yanfd, "Received signal ", receivedSig, " - exiting") yanfd.Stop() } diff --git a/fw/executor/profiler.go b/fw/executor/profiler.go index 061cee10..c5f9cd93 100644 --- a/fw/executor/profiler.go +++ b/fw/executor/profiler.go @@ -18,19 +18,23 @@ func NewProfiler(config *YaNFDConfig) *Profiler { return &Profiler{config: config} } +func (p *Profiler) String() string { + return "Profiler" +} + func (p *Profiler) Start() (err error) { if p.config.CpuProfile != "" { p.cpuFile, err = os.Create(p.config.CpuProfile) if err != nil { - core.LogFatal("Main", "Unable to open output file for CPU profile: ", err) + core.LogFatal(p, "Unable to open output file for CPU profile: ", err) } - core.LogInfo("Main", "Profiling CPU - outputting to ", p.config.CpuProfile) + core.LogInfo(p, "Profiling CPU - outputting to ", p.config.CpuProfile) pprof.StartCPUProfile(p.cpuFile) } if p.config.BlockProfile != "" { - core.LogInfo("Main", "Profiling blocking operations - outputting to ", p.config.BlockProfile) + core.LogInfo(p, "Profiling blocking operations - outputting to ", p.config.BlockProfile) runtime.SetBlockProfileRate(1) p.block = pprof.Lookup("block") } @@ -42,10 +46,10 @@ func (p *Profiler) Stop() { if p.block != nil { blockProfileFile, err := os.Create(p.config.BlockProfile) if err != nil { - core.LogFatal("Main", "Unable to open output file for block profile: ", err) + core.LogFatal(p, "Unable to open output file for block profile: ", err) } if err := p.block.WriteTo(blockProfileFile, 0); err != nil { - core.LogFatal("Main", "Unable to write block profile: ", err) + core.LogFatal(p, "Unable to write block profile: ", err) } blockProfileFile.Close() } @@ -53,14 +57,14 @@ func (p *Profiler) Stop() { if p.config.MemProfile != "" { memProfileFile, err := os.Create(p.config.MemProfile) if err != nil { - core.LogFatal("Main", "Unable to open output file for memory profile: ", err) + core.LogFatal(p, "Unable to open output file for memory profile: ", err) } defer memProfileFile.Close() - core.LogInfo("Main", "Profiling memory - outputting to ", p.config.MemProfile) + core.LogInfo(p, "Profiling memory - outputting to ", p.config.MemProfile) runtime.GC() if err := pprof.WriteHeapProfile(memProfileFile); err != nil { - core.LogFatal("Main", "Unable to write memory profile: ", err) + core.LogFatal(p, "Unable to write memory profile: ", err) } } diff --git a/fw/executor/yanfd.go b/fw/executor/yanfd.go index ecbbf982..1d3f462b 100644 --- a/fw/executor/yanfd.go +++ b/fw/executor/yanfd.go @@ -8,6 +8,7 @@ package executor import ( + "fmt" "net" "os" "time" @@ -44,7 +45,11 @@ type YaNFD struct { unixListener *face.UnixStreamListener wsListener *face.WebSocketListener tcpListeners []*face.TCPListener - udpListener *face.UDPListener + udpListeners []*face.UDPListener +} + +func (y *YaNFD) String() string { + return "YaNFD" } // NewYaNFD creates a YaNFD. Don't call this function twice. @@ -75,7 +80,7 @@ func NewYaNFD(config *YaNFDConfig) *YaNFD { // Start runs YaNFD. Note: this function may exit the program when there is error. // This function is non-blocking. func (y *YaNFD) Start() { - core.LogInfo("Main", "Starting YaNFD") + core.LogInfo(y, "Starting YaNFD") // Start profiler y.profiler.Start() @@ -92,10 +97,9 @@ func (y *YaNFD) Start() { // Create forwarding threads if fw.NumFwThreads < 1 || fw.NumFwThreads > fw.MaxFwThreads { - core.LogFatal("Main", "Number of forwarding threads must be in range [1, ", fw.MaxFwThreads, "]") + core.LogFatal(y, "Number of forwarding threads must be in range [1, ", fw.MaxFwThreads, "]") os.Exit(2) } - fw.Threads = make([]*fw.Thread, fw.NumFwThreads) var fwForDispatch []dispatch.FWThread for i := 0; i < fw.NumFwThreads; i++ { @@ -106,89 +110,118 @@ func (y *YaNFD) Start() { } dispatch.InitializeFWThreads(fwForDispatch) - // Perform setup operations for each network interface - faceCnt := 0 - ifaces, err := net.Interfaces() - if err != nil { - core.LogFatal("Main", "Unable to access network interfaces: ", err) - os.Exit(2) + // Set up listeners for faces + listenerCount := 0 + + // Create unicast UDP face + if core.GetConfig().Faces.Udp.EnabledUnicast { + udpAddrs := []*net.UDPAddr{{ + IP: net.IPv4zero, + Port: int(face.UDPUnicastPort), + }, { + IP: net.IPv6zero, + Port: int(face.UDPUnicastPort), + }} + + for _, udpAddr := range udpAddrs { + uri := fmt.Sprintf("udp://%s", udpAddr) + udpListener, err := face.MakeUDPListener(defn.DecodeURIString(uri)) + if err != nil { + core.LogError(y, "Unable to create UDP listener for ", uri, ": ", err) + } else { + listenerCount++ + go udpListener.Run() + y.udpListeners = append(y.udpListeners, udpListener) + core.LogInfo(y, "Created unicast UDP listener for ", uri) + } + } } - tcpEnabled := core.GetConfig().Faces.Tcp.Enabled - tcpPort := face.TCPUnicastPort - y.tcpListeners = make([]*face.TCPListener, 0) - for _, iface := range ifaces { - if iface.Flags&net.FlagUp == 0 { - core.LogInfo("Main", "Skipping interface ", iface.Name, " because not up") - continue + + // Create unicast TCP face + if core.GetConfig().Faces.Tcp.Enabled { + tcpAddrs := []*net.TCPAddr{{ + IP: net.IPv4zero, + Port: int(face.TCPUnicastPort), + }, { + IP: net.IPv6zero, + Port: int(face.TCPUnicastPort), + }} + + for _, tcpAddr := range tcpAddrs { + uri := fmt.Sprintf("tcp://%s", tcpAddr) + tcpListener, err := face.MakeTCPListener(defn.DecodeURIString(uri)) + if err != nil { + core.LogError(y, "Unable to create TCP listener for ", uri, ": ", err) + } else { + listenerCount++ + go tcpListener.Run() + y.tcpListeners = append(y.tcpListeners, tcpListener) + core.LogInfo(y, "Created unicast TCP listener for ", uri) + } } + } - // Create UDP/TCP listener and multicast UDP interface for every address on interface - addrs, err := iface.Addrs() + // Create multicast UDP face on each non-loopback interface + if core.GetConfig().Faces.Udp.EnabledMulticast { + ifaces, err := net.Interfaces() if err != nil { - core.LogFatal("Main", "Unable to access addresses on network interface ", iface.Name, ": ", err) + core.LogError(y, "Unable to access network interfaces: ", err) } - for _, addr := range addrs { - ipAddr := addr.(*net.IPNet) - - ipVersion := 4 - path := ipAddr.IP.String() - if ipAddr.IP.To4() == nil { - ipVersion = 6 - path += "%" + iface.Name - } - - if !addr.(*net.IPNet).IP.IsLoopback() { - multicastUDPTransport, err := face.MakeMulticastUDPTransport( - defn.MakeUDPFaceURI(ipVersion, path, face.UDPMulticastPort)) - if err != nil { - core.LogError("Main", "Unable to create MulticastUDPTransport for ", path, " on ", iface.Name, ": ", err) - continue - } - - face.MakeNDNLPLinkService( - multicastUDPTransport, - face.MakeNDNLPLinkServiceOptions(), - ).Run(nil) - faceCnt += 1 - core.LogInfo("Main", "Created multicast UDP face for ", path, " on ", iface.Name) + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + core.LogInfo(y, "Skipping interface ", iface.Name, " because not up") + continue } - udpListener, err := face.MakeUDPListener(defn.MakeUDPFaceURI(ipVersion, path, face.UDPUnicastPort)) + addrs, err := iface.Addrs() if err != nil { - core.LogError("Main", "Unable to create UDP listener for ", path, " on ", iface.Name, ": ", err) + core.LogError(y, "Unable to access addresses on network interface ", iface.Name, ": ", err) continue } - faceCnt += 1 - go udpListener.Run() - y.udpListener = udpListener - core.LogInfo("Main", "Created UDP listener for ", path, " on ", iface.Name) - - if tcpEnabled { - tcpListener, err := face.MakeTCPListener(defn.MakeTCPFaceURI(ipVersion, path, tcpPort)) - if err != nil { - core.LogError("Main", "Unable to create TCP listener for ", path, " on ", iface.Name, ": ", err) - continue + + for _, addr := range addrs { + ipAddr := addr.(*net.IPNet) + udpAddr := net.UDPAddr{ + IP: ipAddr.IP, + Zone: iface.Name, + Port: int(face.UDPMulticastPort), + } + uri := fmt.Sprintf("udp://%s", &udpAddr) + + if !addr.(*net.IPNet).IP.IsLoopback() { + multicastUDPTransport, err := face.MakeMulticastUDPTransport(defn.DecodeURIString(uri)) + if err != nil { + core.LogError(y, "Unable to create MulticastUDPTransport for ", uri, ": ", err) + continue + } + + face.MakeNDNLPLinkService( + multicastUDPTransport, + face.MakeNDNLPLinkServiceOptions(), + ).Run(nil) + + listenerCount++ + core.LogInfo(y, "Created multicast UDP face for ", uri) } - faceCnt += 1 - go tcpListener.Run() - y.tcpListeners = append(y.tcpListeners, tcpListener) - core.LogInfo("Main", "Created TCP listener for ", path, " on ", iface.Name) } } } + + // Set up Unix stream listener if core.GetConfig().Faces.Unix.Enabled { - // Set up Unix stream listener - y.unixListener, err = face.MakeUnixStreamListener(defn.MakeUnixFaceURI(face.UnixSocketPath)) + unixListener, err := face.MakeUnixStreamListener(defn.MakeUnixFaceURI(face.UnixSocketPath)) if err != nil { - core.LogError("Main", "Unable to create Unix stream listener at ", face.UnixSocketPath, ": ", err) + core.LogError(y, "Unable to create Unix stream listener at ", face.UnixSocketPath, ": ", err) } else { - faceCnt += 1 - go y.unixListener.Run() - core.LogInfo("Main", "Created Unix stream listener for ", face.UnixSocketPath) + listenerCount++ + go unixListener.Run() + y.unixListener = unixListener + core.LogInfo(y, "Created Unix stream listener for ", face.UnixSocketPath) } } + // Set up WebSocket listener if core.GetConfig().Faces.WebSocket.Enabled { cfg := face.WebSocketListenerConfig{ Bind: core.GetConfig().Faces.WebSocket.Bind, @@ -197,25 +230,28 @@ func (y *YaNFD) Start() { TLSCert: core.ResolveConfigFileRelPath(core.GetConfig().Faces.WebSocket.TlsCert), TLSKey: core.ResolveConfigFileRelPath(core.GetConfig().Faces.WebSocket.TlsKey), } - y.wsListener, err = face.NewWebSocketListener(cfg) + + wsListener, err := face.NewWebSocketListener(cfg) if err != nil { - core.LogError("Main", "Unable to create ", cfg, ": ", err) + core.LogError(y, "Unable to create ", cfg, ": ", err) } else { - faceCnt++ - go y.wsListener.Run() - core.LogInfo("Main", "Created ", cfg) + listenerCount++ + go wsListener.Run() + y.wsListener = wsListener + core.LogInfo(y, "Created ", cfg) } } - if faceCnt <= 0 { - core.LogFatal("Main", "No face or listener is successfully created. Quit.") + // Check if any faces were created + if listenerCount <= 0 { + core.LogFatal(y, "No face or listener is successfully created. Quit.") os.Exit(2) } } // Stop shuts down YaNFD. func (y *YaNFD) Stop() { - core.LogInfo("Main", "Forwarder shutting down ...") + core.LogInfo(y, "Forwarder shutting down ...") core.ShouldQuit = true // Stop profiler @@ -230,8 +266,8 @@ func (y *YaNFD) Stop() { } // Wait for UDP listener to quit - if y.udpListener != nil { - y.udpListener.Close() + for _, udpListener := range y.udpListeners { + udpListener.Close() } // Wait for TCP listeners to quit diff --git a/fw/face/internal-transport.go b/fw/face/internal-transport.go index 7c9ee4e6..3ce46838 100644 --- a/fw/face/internal-transport.go +++ b/fw/face/internal-transport.go @@ -13,6 +13,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" enc "github.com/named-data/ndnd/std/encoding" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" spec "github.com/named-data/ndnd/std/ndn/spec_2022" "github.com/named-data/ndnd/std/utils" ) @@ -30,7 +31,7 @@ func MakeInternalTransport() *InternalTransport { t.makeTransportBase( defn.MakeInternalFaceURI(), defn.MakeInternalFaceURI(), - PersistencyPersistent, + spec_mgmt.PersistencyPersistent, defn.Local, defn.PointToPoint, defn.MaxNDNPacketSize) @@ -59,12 +60,12 @@ func (t *InternalTransport) String() string { } // SetPersistency changes the persistency of the face. -func (t *InternalTransport) SetPersistency(persistency Persistency) bool { +func (t *InternalTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { if persistency == t.persistency { return true } - if persistency == PersistencyPersistent { + if persistency == spec_mgmt.PersistencyPersistent { t.persistency = persistency return true } diff --git a/fw/face/link-service.go b/fw/face/link-service.go index 579c9cd0..062e51ec 100644 --- a/fw/face/link-service.go +++ b/fw/face/link-service.go @@ -16,6 +16,7 @@ import ( defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/dispatch" "github.com/named-data/ndnd/fw/fw" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // LinkService is an interface for link service implementations @@ -27,8 +28,8 @@ type LinkService interface { FaceID() uint64 LocalURI() *defn.URI RemoteURI() *defn.URI - Persistency() Persistency - SetPersistency(persistency Persistency) + Persistency() spec_mgmt.Persistency + SetPersistency(persistency spec_mgmt.Persistency) Scope() defn.Scope LinkType() defn.LinkType MTU() int @@ -121,12 +122,12 @@ func (l *linkServiceBase) RemoteURI() *defn.URI { } // Persistency returns the MTU of the underlying transport. -func (l *linkServiceBase) Persistency() Persistency { +func (l *linkServiceBase) Persistency() spec_mgmt.Persistency { return l.transport.Persistency() } // SetPersistency sets the MTU of the underlying transport. -func (l *linkServiceBase) SetPersistency(persistency Persistency) { +func (l *linkServiceBase) SetPersistency(persistency spec_mgmt.Persistency) { l.transport.SetPersistency(persistency) } diff --git a/fw/face/multicast-udp-transport.go b/fw/face/multicast-udp-transport.go index 7305f51d..dd3dd52a 100644 --- a/fw/face/multicast-udp-transport.go +++ b/fw/face/multicast-udp-transport.go @@ -16,6 +16,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // MulticastUDPTransport is a multicast UDP transport. @@ -48,7 +49,7 @@ func MakeMulticastUDPTransport(localURI *defn.URI) (*MulticastUDPTransport, erro t := &MulticastUDPTransport{} t.makeTransportBase( defn.DecodeURIString(remote), - localURI, PersistencyPermanent, + localURI, spec_mgmt.PersistencyPermanent, defn.NonLocal, defn.MultiAccess, defn.MaxNDNPacketSize) @@ -107,12 +108,12 @@ func (t *MulticastUDPTransport) String() string { return fmt.Sprintf("MulticastUDPTransport, FaceID=%d, RemoteURI=%s, LocalURI=%s", t.faceID, t.remoteURI, t.localURI) } -func (t *MulticastUDPTransport) SetPersistency(persistency Persistency) bool { +func (t *MulticastUDPTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { if persistency == t.persistency { return true } - if persistency == PersistencyPermanent { + if persistency == spec_mgmt.PersistencyPermanent { t.persistency = persistency return true } diff --git a/fw/face/null-transport.go b/fw/face/null-transport.go index 8fb29b11..ca076c4a 100644 --- a/fw/face/null-transport.go +++ b/fw/face/null-transport.go @@ -11,6 +11,7 @@ import ( "strconv" defn "github.com/named-data/ndnd/fw/defn" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // NullTransport is a transport that drops all packets. @@ -27,7 +28,7 @@ func MakeNullTransport() *NullTransport { t.makeTransportBase( defn.MakeNullFaceURI(), defn.MakeNullFaceURI(), - PersistencyPermanent, + spec_mgmt.PersistencyPermanent, defn.NonLocal, defn.PointToPoint, defn.MaxNDNPacketSize) @@ -39,12 +40,12 @@ func (t *NullTransport) String() string { } // SetPersistency changes the persistency of the face. -func (t *NullTransport) SetPersistency(persistency Persistency) bool { +func (t *NullTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { if persistency == t.persistency { return true } - if persistency == PersistencyPermanent { + if persistency == spec_mgmt.PersistencyPermanent { t.persistency = persistency return true } diff --git a/fw/face/tcp-listener.go b/fw/face/tcp-listener.go index 48045396..629a1770 100644 --- a/fw/face/tcp-listener.go +++ b/fw/face/tcp-listener.go @@ -16,6 +16,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // TCPListener listens for incoming TCP unicast connections. @@ -75,7 +76,7 @@ func (l *TCPListener) Run() { continue } - newTransport, err := AcceptUnicastTCPTransport(remoteConn, l.localURI, PersistencyPersistent) + newTransport, err := AcceptUnicastTCPTransport(remoteConn, l.localURI, mgmt_2022.PersistencyPersistent) if err != nil { core.LogError(l, "Failed to create new unicast TCP transport: ", err) continue diff --git a/fw/face/transport.go b/fw/face/transport.go index 3077933c..5e4a1bf5 100644 --- a/fw/face/transport.go +++ b/fw/face/transport.go @@ -12,6 +12,7 @@ import ( "time" defn "github.com/named-data/ndnd/fw/defn" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // transport provides an interface for transports for specific face types @@ -22,8 +23,8 @@ type transport interface { RemoteURI() *defn.URI LocalURI() *defn.URI - Persistency() Persistency - SetPersistency(persistency Persistency) bool + Persistency() spec_mgmt.Persistency + SetPersistency(persistency spec_mgmt.Persistency) bool Scope() defn.Scope LinkType() defn.LinkType MTU() int @@ -56,7 +57,7 @@ type transportBase struct { remoteURI *defn.URI localURI *defn.URI scope defn.Scope - persistency Persistency + persistency spec_mgmt.Persistency linkType defn.LinkType mtu int expirationTime *time.Time @@ -69,7 +70,7 @@ type transportBase struct { func (t *transportBase) makeTransportBase( remoteURI *defn.URI, localURI *defn.URI, - persistency Persistency, + persistency spec_mgmt.Persistency, scope defn.Scope, linkType defn.LinkType, mtu int, @@ -107,7 +108,7 @@ func (t *transportBase) RemoteURI() *defn.URI { return t.remoteURI } -func (t *transportBase) Persistency() Persistency { +func (t *transportBase) Persistency() spec_mgmt.Persistency { return t.persistency } @@ -130,7 +131,7 @@ func (t *transportBase) SetMTU(mtu int) { // ExpirationPeriod returns the time until this face expires. // If transport not on-demand, returns 0. func (t *transportBase) ExpirationPeriod() time.Duration { - if t.expirationTime == nil || t.persistency != PersistencyOnDemand { + if t.expirationTime == nil || t.persistency != spec_mgmt.PersistencyOnDemand { return 0 } return time.Until(*t.expirationTime) diff --git a/fw/face/udp-listener.go b/fw/face/udp-listener.go index 5a14a27d..8b52422c 100644 --- a/fw/face/udp-listener.go +++ b/fw/face/udp-listener.go @@ -12,11 +12,11 @@ import ( "errors" "fmt" "net" - "strconv" "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // UDPListener listens for incoming UDP unicast connections. @@ -79,26 +79,22 @@ func (l *UDPListener) Run() { } // Construct remote URI - var remoteURI *defn.URI - host, port, err := net.SplitHostPort(remoteAddr.String()) - if err != nil { - core.LogWarn(l, "Unable to create face from ", remoteAddr, ": could not split host from port") - continue - } - portInt, err := strconv.ParseUint(port, 10, 16) - if err != nil { - core.LogWarn(l, "Unable to create face from ", remoteAddr, ": could not split host from port") + remoteURI := defn.DecodeURIString(fmt.Sprintf("udp://%s", remoteAddr)) + if remoteURI == nil || !remoteURI.IsCanonical() { + core.LogWarn(l, "Unable to create face from ", remoteURI, ": remote URI is not canonical") continue } - remoteURI = defn.MakeUDPFaceURI(4, host, uint16(portInt)) - remoteURI.Canonize() - if !remoteURI.IsCanonical() { - core.LogWarn(l, "Unable to create face from ", remoteURI, ": remote URI is not canonical") + + // Check if frame received here is for an existing face. + // This is probably because it was received too fast. + // For now just drop the frame, ideally we should pass it to face. + if face := FaceTable.GetByURI(remoteURI); face != nil { + core.LogTrace(l, "Received frame for existing face ", face) continue } // If frame received here, must be for new remote endpoint - newTransport, err := MakeUnicastUDPTransport(remoteURI, l.localURI, PersistencyOnDemand) + newTransport, err := MakeUnicastUDPTransport(remoteURI, l.localURI, spec_mgmt.PersistencyOnDemand) if err != nil { core.LogError(l, "Failed to create new unicast UDP transport: ", err) continue diff --git a/fw/face/unicast-tcp-transport.go b/fw/face/unicast-tcp-transport.go index 110b24bf..2f1f205c 100644 --- a/fw/face/unicast-tcp-transport.go +++ b/fw/face/unicast-tcp-transport.go @@ -17,6 +17,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" "github.com/named-data/ndnd/std/utils" ) @@ -38,7 +39,7 @@ type UnicastTCPTransport struct { func MakeUnicastTCPTransport( remoteURI *defn.URI, localURI *defn.URI, - persistency Persistency, + persistency spec_mgmt.Persistency, ) (*UnicastTCPTransport, error) { // Validate URIs. if !remoteURI.IsCanonical() || @@ -53,7 +54,7 @@ func MakeUnicastTCPTransport( t := new(UnicastTCPTransport) t.makeTransportBase(remoteURI, localURI, persistency, defn.NonLocal, defn.PointToPoint, defn.MaxNDNPacketSize) t.expirationTime = utils.IdPtr(time.Now().Add(tcpLifetime)) - t.rechan = make(chan bool, 1) + t.rechan = make(chan bool, 2) // Set scope ip := net.ParseIP(remoteURI.Path()) @@ -87,27 +88,11 @@ func MakeUnicastTCPTransport( func AcceptUnicastTCPTransport( remoteConn net.Conn, localURI *defn.URI, - persistency Persistency, + persistency spec_mgmt.Persistency, ) (*UnicastTCPTransport, error) { // Construct remote URI - var remoteURI *defn.URI remoteAddr := remoteConn.RemoteAddr() - host, port, err := net.SplitHostPort(remoteAddr.String()) - if err != nil { - core.LogWarn("UnicastTCPTransport", "Unable to create face from ", remoteAddr, ": could not split host from port") - return nil, err - } - portInt, err := strconv.ParseUint(port, 10, 16) - if err != nil { - core.LogWarn("UnicastTCPTransport", "Unable to create face from ", remoteAddr, ": could not split host from port") - return nil, err - } - remoteURI = defn.MakeTCPFaceURI(4, host, uint16(portInt)) - remoteURI.Canonize() - if !remoteURI.IsCanonical() { - core.LogWarn("UnicastTCPTransport", "Unable to create face from ", remoteURI, ": remote URI is not canonical") - return nil, err - } + remoteURI := defn.DecodeURIString(fmt.Sprintf("tcp://%s", remoteAddr)) // Construct transport t := new(UnicastTCPTransport) @@ -150,7 +135,7 @@ func (t *UnicastTCPTransport) String() string { return fmt.Sprintf("UnicastTCPTransport, FaceID=%d, RemoteURI=%s, LocalURI=%s", t.faceID, t.remoteURI, t.localURI) } -func (t *UnicastTCPTransport) SetPersistency(persistency Persistency) bool { +func (t *UnicastTCPTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { t.persistency = persistency return true } @@ -191,7 +176,7 @@ func (t *UnicastTCPTransport) reconnect() { // However, make only one attempt to connect for non-permanent faces if !(t.conn == nil && attempt == 1) { // Do not continue if the transport is not permanent or closed - if t.Persistency() != PersistencyPermanent || t.closed { + if t.Persistency() != spec_mgmt.PersistencyPermanent || t.closed { t.rechan <- false // do not continue return } diff --git a/fw/face/unicast-udp-transport.go b/fw/face/unicast-udp-transport.go index 2865ad17..e48165cc 100644 --- a/fw/face/unicast-udp-transport.go +++ b/fw/face/unicast-udp-transport.go @@ -18,6 +18,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // UnicastUDPTransport is a unicast UDP transport. @@ -33,11 +34,15 @@ type UnicastUDPTransport struct { func MakeUnicastUDPTransport( remoteURI *defn.URI, localURI *defn.URI, - persistency Persistency, + persistency spec_mgmt.Persistency, ) (*UnicastUDPTransport, error) { - // Validate URIs - if !remoteURI.IsCanonical() || (remoteURI.Scheme() != "udp4" && remoteURI.Scheme() != "udp6") || - (localURI != nil && !localURI.IsCanonical()) || (localURI != nil && remoteURI.Scheme() != localURI.Scheme()) { + // Validate remote URI + if remoteURI == nil || !remoteURI.IsCanonical() || (remoteURI.Scheme() != "udp4" && remoteURI.Scheme() != "udp6") { + return nil, core.ErrNotCanonical + } + + // Validate local URI + if localURI != nil && (!localURI.IsCanonical() || remoteURI.Scheme() != localURI.Scheme()) { return nil, core.ErrNotCanonical } @@ -90,7 +95,7 @@ func (t *UnicastUDPTransport) String() string { return fmt.Sprintf("UnicastUDPTransport, FaceID=%d, RemoteURI=%s, LocalURI=%s", t.faceID, t.remoteURI, t.localURI) } -func (t *UnicastUDPTransport) SetPersistency(persistency Persistency) bool { +func (t *UnicastUDPTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { t.persistency = persistency return true } diff --git a/fw/face/unix-stream-transport.go b/fw/face/unix-stream-transport.go index 14fdf843..d5f4967a 100644 --- a/fw/face/unix-stream-transport.go +++ b/fw/face/unix-stream-transport.go @@ -14,6 +14,7 @@ import ( "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/face/impl" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // UnixStreamTransport is a Unix stream transport for communicating with local applications. @@ -30,7 +31,7 @@ func MakeUnixStreamTransport(remoteURI *defn.URI, localURI *defn.URI, conn net.C } t := new(UnixStreamTransport) - t.makeTransportBase(remoteURI, localURI, PersistencyPersistent, defn.Local, defn.PointToPoint, defn.MaxNDNPacketSize) + t.makeTransportBase(remoteURI, localURI, spec_mgmt.PersistencyPersistent, defn.Local, defn.PointToPoint, defn.MaxNDNPacketSize) // Set connection t.conn = conn.(*net.UnixConn) @@ -44,12 +45,12 @@ func (t *UnixStreamTransport) String() string { } // SetPersistency changes the persistency of the face. -func (t *UnixStreamTransport) SetPersistency(persistency Persistency) bool { +func (t *UnixStreamTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { if persistency == t.persistency { return true } - if persistency == PersistencyPersistent { + if persistency == spec_mgmt.PersistencyPersistent { t.persistency = persistency return true } diff --git a/fw/face/web-socket-transport.go b/fw/face/web-socket-transport.go index d48abdbf..abe53b02 100644 --- a/fw/face/web-socket-transport.go +++ b/fw/face/web-socket-transport.go @@ -14,6 +14,7 @@ import ( "github.com/gorilla/websocket" "github.com/named-data/ndnd/fw/core" defn "github.com/named-data/ndnd/fw/defn" + spec_mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" ) // WebSocketTransport communicates with web applications via WebSocket. @@ -32,7 +33,7 @@ func NewWebSocketTransport(localURI *defn.URI, c *websocket.Conn) (t *WebSocketT } t = &WebSocketTransport{c: c} - t.makeTransportBase(remoteURI, localURI, PersistencyOnDemand, scope, defn.PointToPoint, defn.MaxNDNPacketSize) + t.makeTransportBase(remoteURI, localURI, spec_mgmt.PersistencyOnDemand, scope, defn.PointToPoint, defn.MaxNDNPacketSize) t.running.Store(true) return t @@ -42,8 +43,8 @@ func (t *WebSocketTransport) String() string { return fmt.Sprintf("WebSocketTransport, FaceID=%d, RemoteURI=%s, LocalURI=%s", t.faceID, t.remoteURI, t.localURI) } -func (t *WebSocketTransport) SetPersistency(persistency Persistency) bool { - return persistency == PersistencyOnDemand +func (t *WebSocketTransport) SetPersistency(persistency spec_mgmt.Persistency) bool { + return persistency == spec_mgmt.PersistencyOnDemand } func (t *WebSocketTransport) GetSendQueueSize() uint64 { diff --git a/fw/mgmt/face.go b/fw/mgmt/face.go index 63800284..f0503bcd 100644 --- a/fw/mgmt/face.go +++ b/fw/mgmt/face.go @@ -141,12 +141,12 @@ func (f *FaceModule) create(interest *spec.Interest, pitToken []byte, inFace uin } // Check face persistency - persistency := face.PersistencyPersistent - if params.FacePersistency != nil && (*params.FacePersistency == uint64(face.PersistencyPersistent) || - *params.FacePersistency == uint64(face.PersistencyPermanent)) { - persistency = face.Persistency(*params.FacePersistency) + persistency := mgmt.PersistencyPersistent + if params.FacePersistency != nil && (*params.FacePersistency == uint64(mgmt.PersistencyPersistent) || + *params.FacePersistency == uint64(mgmt.PersistencyPermanent)) { + persistency = mgmt.Persistency(*params.FacePersistency) } else if params.FacePersistency != nil { - core.LogWarn(f, "Unacceptable persistency ", face.Persistency(*params.FacePersistency), + core.LogWarn(f, "Unacceptable persistency ", mgmt.Persistency(*params.FacePersistency), " for UDP face specified in ControlParameters for ", interest.Name()) response = makeControlResponse(406, "Unacceptable persistency", nil) f.manager.sendResponse(response, interest, pitToken, inFace) @@ -167,7 +167,7 @@ func (f *FaceModule) create(interest *spec.Interest, pitToken []byte, inFace uin // Create new UDP face transport, err := face.MakeUnicastUDPTransport(URI, nil, persistency) if err != nil { - core.LogWarn(f, "Unable to create unicast UDP face with URI ", URI, ":", err.Error()) + core.LogWarn(f, "Unable to create unicast UDP face with URI ", URI, ": ", err.Error()) response = makeControlResponse(406, "Transport error", nil) f.manager.sendResponse(response, interest, pitToken, inFace) return @@ -231,12 +231,12 @@ func (f *FaceModule) create(interest *spec.Interest, pitToken []byte, inFace uin } // Check face persistency - persistency := face.PersistencyPersistent - if params.FacePersistency != nil && (*params.FacePersistency == uint64(face.PersistencyPersistent) || - *params.FacePersistency == uint64(face.PersistencyPermanent)) { - persistency = face.Persistency(*params.FacePersistency) + persistency := mgmt.PersistencyPersistent + if params.FacePersistency != nil && (*params.FacePersistency == uint64(mgmt.PersistencyPersistent) || + *params.FacePersistency == uint64(mgmt.PersistencyPermanent)) { + persistency = mgmt.Persistency(*params.FacePersistency) } else if params.FacePersistency != nil { - core.LogWarn(f, "Unacceptable persistency ", face.Persistency(*params.FacePersistency), + core.LogWarn(f, "Unacceptable persistency ", mgmt.Persistency(*params.FacePersistency), " for UDP face specified in ControlParameters for ", interest.Name()) response = makeControlResponse(406, "Unacceptable persistency", nil) f.manager.sendResponse(response, interest, pitToken, inFace) @@ -372,16 +372,16 @@ func (f *FaceModule) update(interest *spec.Interest, pitToken []byte, inFace uin } if params.FacePersistency != nil { - if selectedFace.RemoteURI().Scheme() == "ether" && *params.FacePersistency != uint64(face.PersistencyPermanent) { + if selectedFace.RemoteURI().Scheme() == "ether" && *params.FacePersistency != uint64(mgmt.PersistencyPermanent) { responseParams["FacePersistency"] = uint64(*params.FacePersistency) areParamsValid = false } else if (selectedFace.RemoteURI().Scheme() == "udp4" || selectedFace.RemoteURI().Scheme() == "udp6") && - *params.FacePersistency != uint64(face.PersistencyPersistent) && - *params.FacePersistency != uint64(face.PersistencyPermanent) { + *params.FacePersistency != uint64(mgmt.PersistencyPersistent) && + *params.FacePersistency != uint64(mgmt.PersistencyPermanent) { responseParams["FacePersistency"] = uint64(*params.FacePersistency) areParamsValid = false } else if selectedFace.LocalURI().Scheme() == "unix" && - *params.FacePersistency != uint64(face.PersistencyPersistent) { + *params.FacePersistency != uint64(mgmt.PersistencyPersistent) { responseParams["FacePersistency"] = uint64(*params.FacePersistency) areParamsValid = false } @@ -408,7 +408,7 @@ func (f *FaceModule) update(interest *spec.Interest, pitToken []byte, inFace uin // Persistency if params.FacePersistency != nil { // Correctness of FacePersistency already validated - selectedFace.SetPersistency(face.Persistency(*params.FacePersistency)) + selectedFace.SetPersistency(mgmt.Persistency(*params.FacePersistency)) } options := selectedFace.(*face.NDNLPLinkService).Options() @@ -503,8 +503,8 @@ func (f *FaceModule) destroy(interest *spec.Interest, pitToken []byte, inFace ui return } - if face.FaceTable.Get(*params.FaceId) != nil { - face.FaceTable.Remove(*params.FaceId) + if link := face.FaceTable.Get(*params.FaceId); link != nil { + link.Close() core.LogInfo(f, "Destroyed face with FaceID=", *params.FaceId) } else { core.LogInfo(f, "Ignoring attempt to delete non-existent face with FaceID=", *params.FaceId) @@ -550,11 +550,27 @@ func (f *FaceModule) query(interest *spec.Interest, pitToken []byte, _ uint64) { return } filterV, err := mgmt.ParseFaceQueryFilter(enc.NewBufferReader(interest.NameV[f.manager.prefixLength()+2].Val), true) - if err != nil { + if err != nil || filterV == nil || filterV.Val == nil { return } filter := filterV.Val + // canonize URI if present in filter + var filterUri *defn.URI + if filter.Uri != nil { + filterUri = defn.DecodeURIString(*filter.Uri) + if filterUri == nil { + core.LogWarn(f, "Cannot decode URI in FaceQueryFilter ", filterUri) + return + } + err = filterUri.Canonize() + if err != nil { + core.LogWarn(f, "Cannot canonize URI in FaceQueryFilter ", filterUri) + return + } + } + + // filter all faces to match filter faces := face.FaceTable.GetAll() matchingFaces := make([]int, 0) for pos, face := range faces { @@ -568,7 +584,7 @@ func (f *FaceModule) query(interest *spec.Interest, pitToken []byte, _ uint64) { continue } - if filter.Uri != nil && *filter.Uri != face.RemoteURI().String() { + if filterUri != nil && filterUri.String() != face.RemoteURI().String() { continue } @@ -592,7 +608,7 @@ func (f *FaceModule) query(interest *spec.Interest, pitToken []byte, _ uint64) { } // We have to sort these or they appear in a strange order - //sort.Slice(matchingFaces, func(a int, b int) bool { return matchingFaces[a] < matchingFaces[b] }) + sort.Slice(matchingFaces, func(a int, b int) bool { return matchingFaces[a] < matchingFaces[b] }) dataset := &mgmt.FaceStatusMsg{} for _, pos := range matchingFaces { diff --git a/fw/mgmt/forwarder-status.go b/fw/mgmt/forwarder-status.go index c49d545a..0df3df2e 100644 --- a/fw/mgmt/forwarder-status.go +++ b/fw/mgmt/forwarder-status.go @@ -66,8 +66,8 @@ func (f *ForwarderStatusModule) general(interest *spec.Interest, pitToken []byte // Generate new dataset status := &mgmt.GeneralStatus{ NfdVersion: core.Version, - StartTimestamp: uint64(core.StartTimestamp.UnixNano() / 1000 / 1000), - CurrentTimestamp: uint64(time.Now().UnixNano() / 1000 / 1000), + StartTimestamp: time.Duration(core.StartTimestamp.UnixNano()), + CurrentTimestamp: time.Duration(time.Now().UnixNano()), NFibEntries: uint64(len(table.FibStrategyTable.GetAllFIBEntries())), } // Don't set NNameTreeEntries because we don't use a NameTree diff --git a/fw/mgmt/helpers.go b/fw/mgmt/helpers.go index 11ccfdd5..4dc86309 100644 --- a/fw/mgmt/helpers.go +++ b/fw/mgmt/helpers.go @@ -57,8 +57,8 @@ func makeControlResponse(statusCode uint64, statusText string, args map[string]a // Note: The old mgmt.MakeStatusDataset is clearly wrong as it is against the single-Interest-single-Data // principle. Thus, we simply assume that the data packet should always fit in one segment. func makeStatusDataset(name enc.Name, version uint64, dataset enc.Wire) enc.Wire { - // Split into 8000 byte segments and publish - if len(dataset) > 8000 { + // TODO: Split into 8000 byte segments and publish + if dataset.Length() > 8000 { core.LogError("mgmt", "Status dataset is too large") return nil } diff --git a/fw/mgmt/strategy-choice.go b/fw/mgmt/strategy-choice.go index 96d90274..991c800e 100644 --- a/fw/mgmt/strategy-choice.go +++ b/fw/mgmt/strategy-choice.go @@ -156,7 +156,7 @@ func (s *StrategyChoiceModule) set(interest *spec.Interest, pitToken []byte, inF } table.FibStrategyTable.SetStrategyEnc(params.Name, params.Strategy.Name) - core.LogInfo(s, "Set strategy for Name=", params.Name, " to Strategy=", params.Strategy) + core.LogInfo(s, "Set strategy for Name=", params.Name, " to Strategy=", params.Strategy.Name) responseParams := map[string]any{} responseParams["Name"] = params.Name responseParams["Strategy"] = params.Strategy diff --git a/fw/mgmt/thread.go b/fw/mgmt/thread.go index 3496816e..2953579f 100644 --- a/fw/mgmt/thread.go +++ b/fw/mgmt/thread.go @@ -66,7 +66,7 @@ func MakeMgmtThread() *Thread { } func (m *Thread) String() string { - return "Management" + return "Mgmt" } func (m *Thread) registerModule(name string, module Module) { @@ -115,7 +115,7 @@ func (m *Thread) sendResponse(response *mgmt.ControlResponse, interest *spec.Int // Run management thread func (m *Thread) Run() { - core.LogInfo(m, "Starting management") + core.LogInfo(m, "Starting management thread") // Create and register Internal transport m.face, m.transport = face.RegisterInternalTransport() diff --git a/fw/table/rib.go b/fw/table/rib.go index 25f12a1c..7bce86a4 100644 --- a/fw/table/rib.go +++ b/fw/table/rib.go @@ -9,6 +9,7 @@ package table import ( "container/list" + "sync" "time" enc "github.com/named-data/ndnd/std/encoding" @@ -16,7 +17,8 @@ import ( // RibTable represents the Routing Information Base (RIB). type RibTable struct { - RibEntry + root RibEntry + mutex sync.RWMutex } // RibEntry represents an entry in the RIB table. @@ -59,12 +61,12 @@ const ( // Rib is the Routing Information Base. var Rib = RibTable{ - RibEntry: RibEntry{ + root: RibEntry{ children: map[*RibEntry]bool{}, }, } -func (r *RibTable) fillTreeToPrefixEnc(name enc.Name) *RibEntry { +func (r *RibEntry) fillTreeToPrefixEnc(name enc.Name) *RibEntry { entry := r.findLongestPrefixEntryEnc(name) for depth := entry.depth + 1; depth <= len(name); depth++ { child := &RibEntry{ @@ -149,8 +151,11 @@ func (r *RibEntry) updateNexthopsEnc() { // AddRoute adds or updates a RIB entry for the specified prefix. func (r *RibTable) AddEncRoute(name enc.Name, route *Route) { + r.mutex.Lock() + defer r.mutex.Unlock() + name = name.Clone() - node := r.fillTreeToPrefixEnc(name) + node := r.root.fillTreeToPrefixEnc(name) if node.Name == nil { node.Name = name } @@ -172,10 +177,13 @@ func (r *RibTable) AddEncRoute(name enc.Name, route *Route) { // GetAllEntries returns all routes in the RIB. func (r *RibTable) GetAllEntries() []*RibEntry { + r.mutex.RLock() + defer r.mutex.RUnlock() + entries := make([]*RibEntry, 0) // Walk tree in-order queue := list.New() - queue.PushBack(&r.RibEntry) + queue.PushBack(&r.root) for queue.Len() > 0 { ribEntry := queue.Front().Value.(*RibEntry) queue.Remove(queue.Front()) @@ -192,14 +200,12 @@ func (r *RibTable) GetAllEntries() []*RibEntry { return entries } -// GetRoutes returns all routes in the RIB entry. -func (r *RibEntry) GetRoutes() []*Route { - return r.routes -} - // RemoveRoute removes the specified route from the specified prefix. func (r *RibTable) RemoveRouteEnc(name enc.Name, faceID uint64, origin uint64) { - entry := r.findExactMatchEntryEnc(name) + r.mutex.Lock() + defer r.mutex.Unlock() + + entry := r.root.findExactMatchEntryEnc(name) if entry != nil { for i, route := range entry.routes { if route.FaceID == faceID && route.Origin == origin { @@ -216,11 +222,23 @@ func (r *RibTable) RemoveRouteEnc(name enc.Name, faceID uint64, origin uint64) { } } +func (r *RibTable) CleanUpFace(faceId uint64) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.root.cleanUpFace(faceId) +} + +// GetRoutes returns all routes in the RIB entry. +func (r *RibEntry) GetRoutes() []*Route { + return r.routes +} + // CleanUpFace removes the specified face from all entries. Used for clean-up after a face is destroyed. -func (r *RibEntry) CleanUpFace(faceId uint64) { +func (r *RibEntry) cleanUpFace(faceId uint64) { // Recursively clean children for child := range r.children { - child.CleanUpFace(faceId) + child.cleanUpFace(faceId) } if r.Name == nil { diff --git a/fw/yanfd.sample.yml b/fw/yanfd.sample.yml index c1ffc3d8..5161005b 100644 --- a/fw/yanfd.sample.yml +++ b/fw/yanfd.sample.yml @@ -14,6 +14,10 @@ faces: lock_threads_to_cores: false udp: + # Whether to enable unicast UDP listener + enabled_unicast: true + # Whether to enable multicast UDP listener + enabled_multicast: true # Port used for unicast UDP faces port_unicast: 6363 # Port used for multicast UDP faces diff --git a/std/engine/basic/engine.go b/std/engine/basic/engine.go index 53e78e20..12c0ee8d 100644 --- a/std/engine/basic/engine.go +++ b/std/engine/basic/engine.go @@ -445,10 +445,10 @@ func (e *Engine) Express(interest *ndn.EncodedInterest, callback ndn.ExpressCall return err } -func (e *Engine) ExecMgmtCmd(module string, cmd string, args any) error { +func (e *Engine) ExecMgmtCmd(module string, cmd string, args any) (any, error) { cmdArgs, ok := args.(*mgmt.ControlArgs) if !ok { - return ndn.ErrInvalidValue{Item: "args", Value: args} + return nil, ndn.ErrInvalidValue{Item: "args", Value: args} } intCfg := &ndn.InterestConfig{ @@ -457,53 +457,63 @@ func (e *Engine) ExecMgmtCmd(module string, cmd string, args any) error { } interest, err := e.mgmtConf.MakeCmd(module, cmd, cmdArgs, intCfg) if err != nil { - return err + return nil, err + } + + type mgmtResp struct { + err error + val *mgmt.ControlResponse } - ch := make(chan error) + respCh := make(chan *mgmtResp) + err = e.Express(interest, func(args ndn.ExpressCallbackArgs) { + resp := &mgmtResp{} + defer func() { + respCh <- resp + close(respCh) + }() + if args.Result == ndn.InterestResultNack { - ch <- fmt.Errorf("nack received: %v", args.NackReason) + resp.err = fmt.Errorf("nack received: %v", args.NackReason) } else if args.Result == ndn.InterestResultTimeout { - ch <- ndn.ErrDeadlineExceed + resp.err = ndn.ErrDeadlineExceed } else if args.Result == ndn.InterestResultData { data := args.Data valid := e.cmdChecker(data.Name(), args.SigCovered, data.Signature()) if !valid { - ch <- fmt.Errorf("command signature is not valid") + resp.err = fmt.Errorf("command signature is not valid") } else { ret, err := mgmt.ParseControlResponse(enc.NewWireReader(data.Content()), true) if err != nil { - ch <- err + resp.err = err } else { + resp.val = ret if ret.Val != nil { if ret.Val.StatusCode == 200 { - ch <- nil + return } else { - errText := ret.Val.StatusText - ch <- fmt.Errorf("command failed due to error %d: %s", ret.Val.StatusCode, errText) + resp.err = fmt.Errorf("command failed due to error %d: %s", + ret.Val.StatusCode, ret.Val.StatusText) } } else { - ch <- fmt.Errorf("improper response") + resp.err = fmt.Errorf("improper response") } } } } else { - ch <- fmt.Errorf("unknown result: %v", args.Result) + resp.err = fmt.Errorf("unknown result: %v", args.Result) } - close(ch) }) if err != nil { - return err + return nil, err } - err = <-ch - if err != nil { - return err - } - return nil + + resp := <-respCh + return resp.val, resp.err } func (e *Engine) RegisterRoute(prefix enc.Name) error { - err := e.ExecMgmtCmd("rib", "register", &mgmt.ControlArgs{Name: prefix}) + _, err := e.ExecMgmtCmd("rib", "register", &mgmt.ControlArgs{Name: prefix}) if err != nil { e.log.WithField("name", prefix.String()). Errorf("Failed to register prefix: %v", err) @@ -516,7 +526,7 @@ func (e *Engine) RegisterRoute(prefix enc.Name) error { } func (e *Engine) UnregisterRoute(prefix enc.Name) error { - err := e.ExecMgmtCmd("rib", "unregister", &mgmt.ControlArgs{Name: prefix}) + _, err := e.ExecMgmtCmd("rib", "unregister", &mgmt.ControlArgs{Name: prefix}) if err != nil { e.log.WithField("name", prefix.String()). Errorf("Failed to register prefix: %v", err) diff --git a/std/engine/client_conf.go b/std/engine/client_conf.go new file mode 100644 index 00000000..1a098b11 --- /dev/null +++ b/std/engine/client_conf.go @@ -0,0 +1,57 @@ +package engine + +import ( + "bufio" + "os" + "strings" +) + +type ClientConfig struct { + TransportUri string +} + +func GetClientConfig() ClientConfig { + // Default configuration + config := ClientConfig{ + TransportUri: "unix:///var/run/nfd/nfd.sock", + } + + // Order of increasing priority + configDirs := []string{ + "/etc/ndn", + "/usr/local/etc/ndn", + os.Getenv("HOME") + "/.ndn", + } + + // Read each config file that we can find + for _, dir := range configDirs { + filename := dir + "/client.conf" + + file, err := os.OpenFile(filename, os.O_RDONLY, 0) + if err != nil { + continue + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, ";") { // comment + continue + } + + transport := strings.TrimPrefix(line, "transport=") + if transport != line { + config.TransportUri = transport + } + } + } + + // Environment variable overrides config file + transportEnv := os.Getenv("NDN_CLIENT_TRANSPORT") + if transportEnv != "" { + config.TransportUri = transportEnv + } + + return config +} diff --git a/std/engine/factory.go b/std/engine/factory.go index cf92969d..e2dfc594 100644 --- a/std/engine/factory.go +++ b/std/engine/factory.go @@ -1,6 +1,10 @@ package engine import ( + "fmt" + "net/url" + "os" + enc "github.com/named-data/ndnd/std/encoding" "github.com/named-data/ndnd/std/engine/basic" "github.com/named-data/ndnd/std/engine/face" @@ -21,3 +25,27 @@ func NewBasicEngine(face face.Face) ndn.Engine { func NewUnixFace(addr string) face.Face { return face.NewStreamFace("unix", addr, true) } + +func NewDefaultFace() face.Face { + config := GetClientConfig() + + // Parse transport URI + uri, err := url.Parse(config.TransportUri) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to parse transport URI %s: %v (invalid client config)\n", uri, err) + os.Exit(1) + } + + if uri.Scheme == "unix" { + return NewUnixFace(uri.Path) + } + + if uri.Scheme == "tcp" || uri.Scheme == "tcp4" || uri.Scheme == "tcp6" { + return face.NewStreamFace(uri.Scheme, uri.Host, false) + } + + fmt.Fprintf(os.Stderr, "Unsupported transport URI: %s (invalid client config)\n", uri) + os.Exit(1) + + return nil +} diff --git a/std/examples/low-level/consumer/main.go b/std/examples/low-level/consumer/main.go index ebd77a03..d27fd115 100644 --- a/std/examples/low-level/consumer/main.go +++ b/std/examples/low-level/consumer/main.go @@ -15,8 +15,7 @@ func main() { log.SetLevel(log.InfoLevel) logger := log.WithField("module", "main") - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/low-level/producer/main.go b/std/examples/low-level/producer/main.go index 75c997ee..4611d6d3 100644 --- a/std/examples/low-level/producer/main.go +++ b/std/examples/low-level/producer/main.go @@ -56,8 +56,7 @@ func main() { log.SetLevel(log.InfoLevel) logger := log.WithField("module", "main") - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app = engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/low-level/svs/main.go b/std/examples/low-level/svs/main.go index b9c1f844..25ea9005 100644 --- a/std/examples/low-level/svs/main.go +++ b/std/examples/low-level/svs/main.go @@ -25,8 +25,7 @@ func main() { } // Create a new engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/chat/main.go b/std/examples/schema-test/chat/main.go index 08393b8d..c765cbe9 100644 --- a/std/examples/schema-test/chat/main.go +++ b/std/examples/schema-test/chat/main.go @@ -197,8 +197,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/encryption/consumer/main.go b/std/examples/schema-test/encryption/consumer/main.go index 05e6cec3..7a525016 100644 --- a/std/examples/schema-test/encryption/consumer/main.go +++ b/std/examples/schema-test/encryption/consumer/main.go @@ -84,8 +84,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/encryption/producer/main.go b/std/examples/schema-test/encryption/producer/main.go index 60f84442..5b6a54dd 100644 --- a/std/examples/schema-test/encryption/producer/main.go +++ b/std/examples/schema-test/encryption/producer/main.go @@ -76,8 +76,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/group-sig/consumer/main.go b/std/examples/schema-test/group-sig/consumer/main.go index e3c5bd85..d9733e9a 100644 --- a/std/examples/schema-test/group-sig/consumer/main.go +++ b/std/examples/schema-test/group-sig/consumer/main.go @@ -89,8 +89,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/group-sig/producer/main.go b/std/examples/schema-test/group-sig/producer/main.go index ba516996..2023e045 100644 --- a/std/examples/schema-test/group-sig/producer/main.go +++ b/std/examples/schema-test/group-sig/producer/main.go @@ -87,8 +87,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/rdr/catchunks/main.go b/std/examples/schema-test/rdr/catchunks/main.go index b8834a5f..addfc3a6 100644 --- a/std/examples/schema-test/rdr/catchunks/main.go +++ b/std/examples/schema-test/rdr/catchunks/main.go @@ -66,8 +66,7 @@ func main() { }) // Setup engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/rdr/putchunks/main.go b/std/examples/schema-test/rdr/putchunks/main.go index f4379fac..be3ed10e 100644 --- a/std/examples/schema-test/rdr/putchunks/main.go +++ b/std/examples/schema-test/rdr/putchunks/main.go @@ -75,8 +75,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/signed-by/consumer/main.go b/std/examples/schema-test/signed-by/consumer/main.go index 2f08069f..dcb0da73 100644 --- a/std/examples/schema-test/signed-by/consumer/main.go +++ b/std/examples/schema-test/signed-by/consumer/main.go @@ -139,8 +139,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/signed-by/producer/main.go b/std/examples/schema-test/signed-by/producer/main.go index 500309ca..ffb70ba9 100644 --- a/std/examples/schema-test/signed-by/producer/main.go +++ b/std/examples/schema-test/signed-by/producer/main.go @@ -153,8 +153,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/single-packet/consumer/main.go b/std/examples/schema-test/single-packet/consumer/main.go index e6adac18..8a0a1923 100644 --- a/std/examples/schema-test/single-packet/consumer/main.go +++ b/std/examples/schema-test/single-packet/consumer/main.go @@ -53,8 +53,7 @@ func main() { }) // Setup engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/single-packet/producer/main.go b/std/examples/schema-test/single-packet/producer/main.go index a43ae229..e78ebbb4 100644 --- a/std/examples/schema-test/single-packet/producer/main.go +++ b/std/examples/schema-test/single-packet/producer/main.go @@ -70,8 +70,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/storage/consumer/main.go b/std/examples/schema-test/storage/consumer/main.go index 32e1f63b..c4d4f695 100644 --- a/std/examples/schema-test/storage/consumer/main.go +++ b/std/examples/schema-test/storage/consumer/main.go @@ -66,8 +66,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err = app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/storage/producer/main.go b/std/examples/schema-test/storage/producer/main.go index a572ff20..e5a4a707 100644 --- a/std/examples/schema-test/storage/producer/main.go +++ b/std/examples/schema-test/storage/producer/main.go @@ -58,8 +58,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/examples/schema-test/sync/main.go b/std/examples/schema-test/sync/main.go index 7e6146f3..3105963b 100644 --- a/std/examples/schema-test/sync/main.go +++ b/std/examples/schema-test/sync/main.go @@ -84,8 +84,7 @@ func main() { }) // Start engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - app := engine.NewBasicEngine(face) + app := engine.NewBasicEngine(engine.NewDefaultFace()) err := app.Start() if err != nil { logger.Fatalf("Unable to start engine: %+v", err) diff --git a/std/ndn/engine.go b/std/ndn/engine.go index 6069ad20..869cc3db 100644 --- a/std/ndn/engine.go +++ b/std/ndn/engine.go @@ -38,8 +38,9 @@ type Engine interface { // UnregisterRoute unregisters a route of prefix to the local forwarder. UnregisterRoute(prefix enc.Name) error // ExecMgmtCmd executes a management command. - // args is a pointer to mgmt.ControlArgs - ExecMgmtCmd(module string, cmd string, args any) error + // args are the control arguments (*mgmt.ControlArgs) + // returns response and error if any (*mgmt.ControlResponse, error) + ExecMgmtCmd(module string, cmd string, args any) (any, error) } type Timer interface { diff --git a/std/ndn/mgmt_2022/definitions.go b/std/ndn/mgmt_2022/definitions.go index 2c6e8816..a390c2a2 100644 --- a/std/ndn/mgmt_2022/definitions.go +++ b/std/ndn/mgmt_2022/definitions.go @@ -2,6 +2,8 @@ package mgmt_2022 import ( + "time" + enc "github.com/named-data/ndnd/std/encoding" ) @@ -139,10 +141,10 @@ type FaceEventNotification struct { type GeneralStatus struct { //+field:string NfdVersion string `tlv:"0x80"` - //+field:natural - StartTimestamp uint64 `tlv:"0x81"` - //+field:natural - CurrentTimestamp uint64 `tlv:"0x82"` + //+field:time + StartTimestamp time.Duration `tlv:"0x81"` + //+field:time + CurrentTimestamp time.Duration `tlv:"0x82"` //+field:natural NNameTreeEntries uint64 `tlv:"0x83"` //+field:natural diff --git a/fw/face/persistency.go b/std/ndn/mgmt_2022/persistency.go similarity index 71% rename from fw/face/persistency.go rename to std/ndn/mgmt_2022/persistency.go index 294e1919..0460a1ed 100644 --- a/fw/face/persistency.go +++ b/std/ndn/mgmt_2022/persistency.go @@ -1,11 +1,4 @@ -/* YaNFD - Yet another NDN Forwarding Daemon - * - * Copyright (C) 2020-2021 Eric Newberry. - * - * This file is licensed under the terms of the MIT License, as found in LICENSE.md. - */ - -package face +package mgmt_2022 // Persistency represents the persistency of a face. type Persistency uint64 diff --git a/std/ndn/mgmt_2022/zz_generated.go b/std/ndn/mgmt_2022/zz_generated.go index 5e00a1d7..7a850667 100644 --- a/std/ndn/mgmt_2022/zz_generated.go +++ b/std/ndn/mgmt_2022/zz_generated.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "io" "strings" + "time" enc "github.com/named-data/ndnd/std/encoding" ) @@ -2917,7 +2918,7 @@ func (encoder *GeneralStatusEncoder) Init(value *GeneralStatus) { } l += uint(len(value.NfdVersion)) l += 1 - switch x := value.StartTimestamp; { + switch x := uint64(value.StartTimestamp / time.Millisecond); { case x <= 0xff: l += 2 case x <= 0xffff: @@ -2928,7 +2929,7 @@ func (encoder *GeneralStatusEncoder) Init(value *GeneralStatus) { l += 9 } l += 1 - switch x := value.CurrentTimestamp; { + switch x := uint64(value.CurrentTimestamp / time.Millisecond); { case x <= 0xff: l += 2 case x <= 0xffff: @@ -3233,7 +3234,7 @@ func (encoder *GeneralStatusEncoder) EncodeInto(value *GeneralStatus, buf []byte pos += uint(len(value.NfdVersion)) buf[pos] = byte(129) pos += 1 - switch x := value.StartTimestamp; { + switch x := uint64(value.StartTimestamp / time.Millisecond); { case x <= 0xff: buf[pos] = 1 buf[pos+1] = byte(x) @@ -3253,7 +3254,7 @@ func (encoder *GeneralStatusEncoder) EncodeInto(value *GeneralStatus, buf []byte } buf[pos] = byte(130) pos += 1 - switch x := value.CurrentTimestamp; { + switch x := uint64(value.CurrentTimestamp / time.Millisecond); { case x <= 0xff: buf[pos] = 1 buf[pos+1] = byte(x) @@ -3813,38 +3814,46 @@ func (context *GeneralStatusParsingContext) Parse(reader enc.ParseReader, ignore if true { handled = true handled_StartTimestamp = true - value.StartTimestamp = uint64(0) { - for i := 0; i < int(l); i++ { - x := byte(0) - x, err = reader.ReadByte() - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF + timeInt := uint64(0) + timeInt = uint64(0) + { + for i := 0; i < int(l); i++ { + x := byte(0) + x, err = reader.ReadByte() + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + break } - break + timeInt = uint64(timeInt<<8) | uint64(x) } - value.StartTimestamp = uint64(value.StartTimestamp<<8) | uint64(x) } + value.StartTimestamp = time.Duration(timeInt) * time.Millisecond } } case 130: if true { handled = true handled_CurrentTimestamp = true - value.CurrentTimestamp = uint64(0) { - for i := 0; i < int(l); i++ { - x := byte(0) - x, err = reader.ReadByte() - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF + timeInt := uint64(0) + timeInt = uint64(0) + { + for i := 0; i < int(l); i++ { + x := byte(0) + x, err = reader.ReadByte() + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + break } - break + timeInt = uint64(timeInt<<8) | uint64(x) } - value.CurrentTimestamp = uint64(value.CurrentTimestamp<<8) | uint64(x) } + value.CurrentTimestamp = time.Duration(timeInt) * time.Millisecond } } case 131: diff --git a/cmd/cmdtree.go b/std/utils/cmdtree.go similarity index 65% rename from cmd/cmdtree.go rename to std/utils/cmdtree.go index 48ef62f8..21e823b8 100644 --- a/cmd/cmdtree.go +++ b/std/utils/cmdtree.go @@ -1,4 +1,4 @@ -package cmd +package utils import ( "fmt" @@ -47,12 +47,31 @@ func (c *CmdTree) Execute(args []string) { // recursively search for subcommand for _, sub := range c.Sub { - if len(sub.Name) > 0 && args[1] == sub.Name { - name := args[0] + " " + args[1] - sargs := append([]string{name}, args[2:]...) - sub.Execute(sargs) - return + if len(sub.Name) == 0 { + continue // empty subcommand } + + subname := strings.Split(sub.Name, " ") + if len(args) < len(subname)+1 { + continue // not enough arguments + } + + matches := true + for i, s := range subname { + if args[i+1] != s { + matches = false + break + } + } + if !matches { + continue // no match + } + + // execute subcommand + name := args[0] + " " + sub.Name + sargs := append([]string{name}, args[1+len(subname):]...) + sub.Execute(sargs) + return } // command not found diff --git a/tools/catchunks.go b/tools/catchunks.go index 8a6b40b9..3eef19f4 100644 --- a/tools/catchunks.go +++ b/tools/catchunks.go @@ -41,17 +41,16 @@ func (cc *CatChunks) run() { } // start face and engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - engine := engine.NewBasicEngine(face) - err = engine.Start() + app := engine.NewBasicEngine(engine.NewDefaultFace()) + err = app.Start() if err != nil { log.Errorf("Unable to start engine: %+v", err) return } - defer engine.Stop() + defer app.Stop() // start object client - cli := object.NewClient(engine, object.NewMemoryStore()) + cli := object.NewClient(app, object.NewMemoryStore()) err = cli.Start() if err != nil { log.Errorf("Unable to start object client: %+v", err) diff --git a/tools/dvc/README.md b/tools/dvc/README.md new file mode 100644 index 00000000..a4c3fda2 --- /dev/null +++ b/tools/dvc/README.md @@ -0,0 +1,24 @@ +# DV Control Reference + +This is the detailed reference for the ndn-dv routing daemon control tool. + +## `ndnd dv link create` + +The link create command creates a new neighbor link. A new permanent face will be created for the neighbor if a matching face does not exist. + +```bash +# Create a UDP neighbor link +ndnd dv link create udp://suns.cs.ucla.edu + +# Create a TCP neighbor link +ndnd dv link create tcp4://hobo.cs.arizona.edu:6363 +``` + +## `ndnd dv link destroy` + +The link destroy command destroys a neighbor link. The face associated with the neighbor will be destroyed. + +```bash +# Destroy a neighbor link by URI +ndnd dv link destroy udp://suns.cs.ucla.edu +``` diff --git a/tools/dvc/dvc.go b/tools/dvc/dvc.go new file mode 100644 index 00000000..c2a140a6 --- /dev/null +++ b/tools/dvc/dvc.go @@ -0,0 +1,94 @@ +package dvc + +import ( + "fmt" + "os" + "time" + + dvtlv "github.com/named-data/ndnd/dv/tlv" + enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/engine" + "github.com/named-data/ndnd/std/ndn" + "github.com/named-data/ndnd/std/utils" +) + +func dvGetStatus() *dvtlv.Status { + app := engine.NewBasicEngine(engine.NewDefaultFace()) + err := app.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to start engine: %v\n", err) + os.Exit(1) + } + defer app.Stop() + + name, _ := enc.NameFromStr("/localhost/nlsr/status") + cfg := &ndn.InterestConfig{ + MustBeFresh: true, + Lifetime: utils.IdPtr(time.Second), + Nonce: utils.ConvertNonce(app.Timer().Nonce()), + } + + interest, err := app.Spec().MakeInterest(name, cfg, nil, nil) + if err != nil { + panic(err) + } + + ch := make(chan ndn.ExpressCallbackArgs) + err = app.Express(interest, func(args ndn.ExpressCallbackArgs) { ch <- args }) + if err != nil { + panic(err) + } + args := <-ch + + if args.Result != ndn.InterestResultData { + fmt.Fprintf(os.Stderr, "Failed to get router state. Is DV running?\n") + os.Exit(1) + } + + status, err := dvtlv.ParseStatus(enc.NewWireReader(args.Data.Content()), false) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to parse router state: %v\n", err) + os.Exit(1) + } + + return status +} + +func RunDvLinkCreate(nfdcTree *utils.CmdTree) func([]string) { + return func(args []string) { + if len(args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s \n", args[0]) + return + } + + status := dvGetStatus() // will panic if fail + + // /localhop//32=DV/32=ADS/32=ACT + name := enc.Name{enc.NewStringComponent(enc.TypeGenericNameComponent, "localhop")} + name = append(name, status.NetworkName.Name...) + name = append(name, + enc.NewStringComponent(enc.TypeKeywordNameComponent, "DV"), + enc.NewStringComponent(enc.TypeKeywordNameComponent, "ADS"), + enc.NewStringComponent(enc.TypeKeywordNameComponent, "ACT"), + ) + + nfdcTree.Execute([]string{ + "nfdc", "route", "add", + "persistency=permanent", + "face=" + args[1], + "prefix=" + name.String(), + }) + } +} + +func RunDvLinkDestroy(nfdcTree *utils.CmdTree) func([]string) { + return func(args []string) { + if len(args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s \n", args[0]) + return + } + + // just destroy the face assuming we created it + nfdcTree.Execute([]string{"nfdc", "face", "destroy", "face=" + args[1]}) + } +} diff --git a/tools/nfdc/README.md b/tools/nfdc/README.md new file mode 100644 index 00000000..34cad28b --- /dev/null +++ b/tools/nfdc/README.md @@ -0,0 +1,129 @@ +# Forwarder Control Reference + +This is the detailed reference for the NDNd forwarder control tool, and implements the NFD Management Protocol. + +You can also use the [nfdc](https://docs.named-data.net/NFD/24.07/manpages/nfdc.html) tool from the NFD project to manage the NDNd forwarder. + +## `ndnd fw status` + +The status command shows general status of the forwarder, including its version, uptime, data structure counters, and global packet counters. + +## `ndnd fw face list` + +The face list command prints the face table, which contains information about faces. + +## `ndnd fw face create` + +The face create command creates a new face. The supported arguments are: + +- `remote=`: The remote URI of the face. +- `local=`: The local URI of the face. +- `cost=`: The cost of the face. +- `persistency=`: The persistency of the face (`persistent` or `permanent`). +- `mtu=`: The MTU of the face in bytes. + +```bash +# Create a UDP face with the default port +ndnd fw face create remote=udp://suns.cs.ucla.edu + +# Create a TCP face over IPv4 +ndnd fw face create remote=tcp4://suns.cs.ucla.edu:6363 + +# Create a peramanent TCP face with a cost of 10 +ndnd fw face create remote=tcp://suns.cs.ucla.edu cost=10 persistency=permanent +``` + +## `ndnd fw face destroy` + +The face destroy command destroys a face. The supported arguments are: + +- `face=|`: The face ID or remote URI of the face to destroy. + +```bash +# Destroy a face by ID +ndnd fw face destroy face=6 + +# Destroy a face by remote URI +ndnd fw face destroy face=tcp://suns.cs.ucla.edu +``` + +## `ndnd fw route list` + +The route list command prints the existing RIB routes. + +## `ndnd fw route add` + +The route add command adds a route to the RIB. The supported arguments are: + +- `prefix=`: The name prefix of the route. +- `face=|`: The next hop face ID to forward packets to. +- `cost=`: The cost of the route. +- `origin=`: The origin of the route (default=255). +- `expires=`: The expiration time of the route in milliseconds. + +If a face URI is specified and the face does not exist, it will be created. + +```bash +# Add a route to forward packets to a new or existing UDP face +ndnd fw route add prefix=/ndn face=udp://suns.cs.ucla.edu + +# Add a route with a permanent TCP face (face options must appear before "face=") +ndnd fw route add prefix=/ndn persistency=permanent face=tcp://suns.cs.ucla.edu + +# Add a route to forward packets to face 6 +ndnd fw route add prefix=/example face=6 + +# Add a route with a cost of 10 and origin of "client" +ndnd fw route add prefix=/example face=6 cost=10 origin=65 +``` + +## `ndnd fw route remove` + +The route remove command removes a route from the RIB. The supported arguments are: + +- `prefix=`: The name prefix of the route. +- `face=|`: The next hop face ID of the route. +- `origin=`: The origin of the route (default=255). + +```bash +# Remove a route by prefix, face and origin +ndnd fw route remove prefix=/example face=6 origin=65 +``` + +## `ndnd fw fib list` + +The fib list command prints the existing FIB entries. + +## `ndnd fw cs info` + +The cs info command prints information about the content store. + +## `ndnd fw strategy list` + +The strategy list command prints the currently selected forwarding strategies. + +## `ndnd fw strategy set` + +The strategy set command sets a forwarding strategy for a name prefix. The supported arguments are: + +- `prefix=`: The name prefix to set the strategy for. +- `strategy=`: The forwarding strategy to set. + +```bash +# Set the strategy for /example to "multicast" +ndnd fw strategy set prefix=/example strategy=/localhost/nfd/strategy/multicast/v=1 + +# Set the strategy for /example to "best-route" +ndnd fw strategy set prefix=/example strategy=/localhost/nfd/strategy/best-route/v=1 +``` + +## `ndnd fw strategy unset` + +The strategy unset command unsets a forwarding strategy for a name prefix. The supported arguments are: + +- `prefix=`: The name prefix to unset the strategy for. + +```bash +# Unset the strategy for /example +ndnd fw strategy unset prefix=/example +``` diff --git a/tools/nfdc/nfdc.go b/tools/nfdc/nfdc.go new file mode 100644 index 00000000..467a3eb5 --- /dev/null +++ b/tools/nfdc/nfdc.go @@ -0,0 +1,113 @@ +package nfdc + +import ( + "fmt" + "os" + + enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/engine" + "github.com/named-data/ndnd/std/ndn" + "github.com/named-data/ndnd/std/utils" +) + +func GetNfdcCmdTree() utils.CmdTree { + nfdc := &Nfdc{} + cmd := func(mod string, cmd string, defaults []string) func([]string) { + return func(args []string) { + nfdc.ExecCmd(mod, cmd, args, defaults) + } + } + start := func(fun func([]string)) func([]string) { + return func(args []string) { + nfdc.Start() + defer nfdc.Stop() + fun(args) + } + } + + return utils.CmdTree{ + Name: "nfdc", + Help: "NDNd Forwarder Control", + Sub: []*utils.CmdTree{{ + Name: "status", + Help: "Print general status", + Fun: start(nfdc.ExecStatusGeneral), + }, { + Name: "face list", + Help: "Print face table", + Fun: start(nfdc.ExecFaceList), + }, { + Name: "face create", + Help: "Create a face", + Fun: start(cmd("faces", "create", []string{ + "persistency=persistent", + })), + }, { + Name: "face destroy", + Help: "Destroy a face", + Fun: start(cmd("faces", "destroy", []string{})), + }, { + Name: "route list", + Help: "Print RIB routes", + Fun: start(nfdc.ExecRouteList), + }, { + Name: "route add", + Help: "Add a route to the RIB", + Fun: start(cmd("rib", "register", []string{ + "cost=0", "origin=255", + })), + }, { + Name: "route remove", + Help: "Remove a route from the RIB", + Fun: start(cmd("rib", "unregister", []string{ + "origin=255", + })), + }, { + Name: "fib list", + Help: "Print FIB entries", + Fun: start(nfdc.ExecFibList), + }, { + Name: "cs info", + Help: "Print content store info", + Fun: start(nfdc.ExecCsInfo), + }, { + Name: "strategy list", + Help: "Print strategy choices", + Fun: start(nfdc.ExecStrategyList), + }, { + Name: "strategy set", + Help: "Set strategy choice", + Fun: start(cmd("strategy-choice", "set", []string{})), + }, { + Name: "strategy unset", + Help: "Unset strategy choice", + Fun: start(cmd("strategy-choice", "unset", []string{})), + }}, + } +} + +type Nfdc struct { + engine ndn.Engine + statusPadding int +} + +func (n *Nfdc) Start() { + n.engine = engine.NewBasicEngine(engine.NewDefaultFace()) + + err := n.engine.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to start engine: %+v\n", err) + return + } +} + +func (n *Nfdc) Stop() { + n.engine.Stop() +} + +func (n *Nfdc) GetPrefix() enc.Name { + return enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "localhost"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "nfd"), + } +} diff --git a/tools/nfdc/nfdc_cmd.go b/tools/nfdc/nfdc_cmd.go new file mode 100644 index 00000000..e0fa7c09 --- /dev/null +++ b/tools/nfdc/nfdc_cmd.go @@ -0,0 +1,226 @@ +package nfdc + +import ( + "fmt" + "os" + "sort" + "strconv" + "strings" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" + "github.com/named-data/ndnd/std/utils" +) + +func (n *Nfdc) ExecCmd(mod string, cmd string, args []string, defaults []string) { + // parse command arguments + ctrlArgs := mgmt.ControlArgs{} + + // set default values first, then user-provided values + for _, arg := range append(defaults, args[1:]...) { + kv := strings.SplitN(arg, "=", 2) + if len(kv) != 2 { + fmt.Fprintf(os.Stderr, "Invalid argument: %s (should be key=value)\n", arg) + return + } + + key, val := n.preprocessArg(&ctrlArgs, mod, cmd, kv[0], kv[1]) + n.convCmdArg(&ctrlArgs, key, val) + } + + // execute command + raw, execErr := n.engine.ExecMgmtCmd(mod, cmd, &ctrlArgs) + if raw == nil { + fmt.Fprintf(os.Stderr, "Error executing command: %+v\n", execErr) + return + } + + // parse response + res, ok := raw.(*mgmt.ControlResponse) + if !ok || res == nil || res.Val == nil || res.Val.Params == nil { + fmt.Fprintf(os.Stderr, "Invalid or empty response type: %T\n", raw) + return + } + n.printCtrlResponse(res) + + if execErr != nil { + os.Exit(1) + } +} + +func (n *Nfdc) preprocessArg( + ctrlArgs *mgmt.ControlArgs, + mod string, cmd string, + key string, val string, +) (string, string) { + // convert face from URI to face ID + if key == "face" && strings.Contains(val, "://") { + // query the existing face (without attempting to create a new one) + // for faces/create, we require specifying "remote" and/or "local" instead + if (mod == "faces" && cmd == "destroy") || + (mod == "rib" && cmd == "unregister") { + + filter := mgmt.FaceQueryFilter{ + Val: &mgmt.FaceQueryFilterValue{Uri: utils.IdPtr(val)}, + } + + dataset, err := n.fetchStatusDataset(enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "faces"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "query"), + enc.NewBytesComponent(enc.TypeGenericNameComponent, filter.Encode().Join()), + }) + if dataset == nil { + fmt.Fprintf(os.Stderr, "Error fetching face status dataset: %+v\n", err) + os.Exit(1) + } + + status, err := mgmt.ParseFaceStatusMsg(enc.NewWireReader(dataset), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing face status: %+v\n", err) + os.Exit(1) + } + + // face needs to exist, otherwise no point in continuing + if len(status.Vals) == 0 { + fmt.Fprintf(os.Stderr, "Face not found for URI: %s\n", val) + os.Exit(9) + } else if len(status.Vals) > 1 { + fmt.Fprintf(os.Stderr, "Multiple faces found for URI: %s\n", val) + os.Exit(9) + } + + // found the face we need + return key, fmt.Sprintf("%d", status.Vals[0].FaceId) + } + + // only for rib/register, create a new face if it doesn't exist + if mod == "rib" && cmd == "register" { + // copy over any face arguments that are already set + faceArgs := mgmt.ControlArgs{Uri: utils.IdPtr(val)} + if ctrlArgs.LocalUri != nil { + faceArgs.LocalUri = ctrlArgs.LocalUri + ctrlArgs.LocalUri = nil + } + if ctrlArgs.Mtu != nil { + faceArgs.Mtu = ctrlArgs.Mtu + ctrlArgs.Mtu = nil + } + if ctrlArgs.FacePersistency != nil { + faceArgs.FacePersistency = ctrlArgs.FacePersistency + ctrlArgs.FacePersistency = nil + } + + // create or use existing face + raw, execErr := n.engine.ExecMgmtCmd("faces", "create", &faceArgs) + if raw == nil { + fmt.Fprintf(os.Stderr, "Error creating face: %+v\n", execErr) + os.Exit(1) + } + + res, ok := raw.(*mgmt.ControlResponse) + if !ok { + fmt.Fprintf(os.Stderr, "Invalid or empty response type: %T\n", raw) + os.Exit(1) + } + n.printCtrlResponse(res) + if res.Val == nil || res.Val.Params == nil || res.Val.Params.FaceId == nil { + fmt.Fprintf(os.Stderr, "Failed to create face for route\n") + os.Exit(1) + } + + return key, fmt.Sprintf("%d", *res.Val.Params.FaceId) + } + } + + return key, val +} + +func (n *Nfdc) convCmdArg(ctrlArgs *mgmt.ControlArgs, key string, val string) { + // helper function to parse uint64 values + parseUint := func(val string) uint64 { + v, err := strconv.ParseUint(val, 10, 64) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid value for %s: %s\n", key, val) + os.Exit(9) + } + return v + } + + // helper function to parse name values + parseName := func(val string) enc.Name { + name, err := enc.NameFromStr(val) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid name for %s: %s\n", key, val) + os.Exit(9) + } + return name + } + + // convert key-value pairs to command arguments + switch key { + // face arguments + case "face": + ctrlArgs.FaceId = utils.IdPtr(parseUint(val)) + case "remote": + ctrlArgs.Uri = utils.IdPtr(val) + case "local": + ctrlArgs.LocalUri = utils.IdPtr(val) + case "mtu": + ctrlArgs.Mtu = utils.IdPtr(parseUint(val)) + case "persistency": + switch val { + case "permanent": + ctrlArgs.FacePersistency = utils.IdPtr(uint64(mgmt.PersistencyPermanent)) + case "persistent": + ctrlArgs.FacePersistency = utils.IdPtr(uint64(mgmt.PersistencyPersistent)) + default: + fmt.Fprintf(os.Stderr, "Invalid persistency: %s\n", val) + os.Exit(9) + } + + // route arguments + case "prefix": + ctrlArgs.Name = parseName(val) + case "cost": + ctrlArgs.Cost = utils.IdPtr(parseUint(val)) + case "origin": + ctrlArgs.Origin = utils.IdPtr(parseUint(val)) + case "expires": + ctrlArgs.ExpirationPeriod = utils.IdPtr(parseUint(val)) + + // strategy arguments + case "strategy": + ctrlArgs.Strategy = &mgmt.Strategy{Name: parseName(val)} + + // unknown argument + default: + fmt.Fprintf(os.Stderr, "Unknown command argument key: %s\n", key) + os.Exit(9) + } +} + +func (n *Nfdc) printCtrlResponse(res *mgmt.ControlResponse) { + // print status code and text + fmt.Printf("Status=%d (%s)\n", res.Val.StatusCode, res.Val.StatusText) + + // iterate over parameters in sorted order + params := res.Val.Params.ToDict() + keys := make([]string, 0, len(params)) + for key := range params { + keys = append(keys, key) + } + sort.Strings(keys) + + // print parameters + for _, key := range keys { + val := params[key] + + // convert some values to human-readable form + switch key { + case "FacePersistency": + val = mgmt.Persistency(val.(uint64)).String() + } + + fmt.Printf(" %s=%v\n", key, val) + } +} diff --git a/tools/nfdc/nfdc_cs.go b/tools/nfdc/nfdc_cs.go new file mode 100644 index 00000000..a3e24a54 --- /dev/null +++ b/tools/nfdc/nfdc_cs.go @@ -0,0 +1,39 @@ +package nfdc + +import ( + "fmt" + "os" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" +) + +func (n *Nfdc) ExecCsInfo(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "cs"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "info"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseCsInfoMsg(enc.NewWireReader(data), true) + if err != nil || status.CsInfo == nil { + fmt.Fprintf(os.Stderr, "Error parsing CS info: %+v\n", err) + return + } + + info := status.CsInfo + + fmt.Println("CS information:") + n.statusPadding = 10 + n.printStatusLine("capacity", info.Capacity) + n.printStatusLine("admit", info.Flags&mgmt.CsEnableAdmit != 0) + n.printStatusLine("serve", info.Flags&mgmt.CsEnableServe != 0) + n.printStatusLine("nEntries", info.NCsEntries) + n.printStatusLine("nHits", info.NHits) + n.printStatusLine("nMisses", info.NMisses) +} diff --git a/tools/nfdc/nfdc_dataset.go b/tools/nfdc/nfdc_dataset.go new file mode 100644 index 00000000..3bc825e5 --- /dev/null +++ b/tools/nfdc/nfdc_dataset.go @@ -0,0 +1,41 @@ +package nfdc + +import ( + "fmt" + "time" + + enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/ndn" + "github.com/named-data/ndnd/std/utils" +) + +func (n *Nfdc) fetchStatusDataset(suffix enc.Name) (enc.Wire, error) { + // TODO: segmented fetch once supported by fw/mgmt + name := append(n.GetPrefix(), suffix...) + config := &ndn.InterestConfig{ + MustBeFresh: true, + CanBePrefix: true, + Lifetime: utils.IdPtr(time.Second), + Nonce: utils.ConvertNonce(n.engine.Timer().Nonce()), + } + interest, err := n.engine.Spec().MakeInterest(name, config, nil, nil) + if err != nil { + return nil, err + } + + ch := make(chan ndn.ExpressCallbackArgs) + err = n.engine.Express(interest, func(args ndn.ExpressCallbackArgs) { + ch <- args + close(ch) + }) + if err != nil { + return nil, err + } + + res := <-ch + if res.Result != ndn.InterestResultData { + return nil, fmt.Errorf("interest failed: %d", res.Result) + } + + return res.Data.Content(), nil +} diff --git a/tools/nfdc/nfdc_face.go b/tools/nfdc/nfdc_face.go new file mode 100644 index 00000000..159ad53f --- /dev/null +++ b/tools/nfdc/nfdc_face.go @@ -0,0 +1,63 @@ +package nfdc + +import ( + "fmt" + "os" + "strings" + "time" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" +) + +func (n *Nfdc) ExecFaceList(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "faces"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "list"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseFaceStatusMsg(enc.NewWireReader(data), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing face status: %+v\n", err) + return + } + + for _, entry := range status.Vals { + info := []string{} + + info = append(info, fmt.Sprintf("faceid=%d", entry.FaceId)) + info = append(info, fmt.Sprintf("remote=%s", entry.Uri)) + info = append(info, fmt.Sprintf("local=%s", entry.LocalUri)) + + congestion := []string{} + if entry.BaseCongestionMarkInterval != nil { + congestion = append(congestion, fmt.Sprintf("base-marking-interval=%s", time.Duration(*entry.BaseCongestionMarkInterval)*time.Nanosecond)) + } + if entry.DefaultCongestionThreshold != nil { + congestion = append(congestion, fmt.Sprintf("default-threshold=%dB", *entry.DefaultCongestionThreshold)) + } + if len(congestion) > 0 { + info = append(info, fmt.Sprintf("congestion={%s}", strings.Join(congestion, " "))) + } + + if entry.Mtu != nil { + info = append(info, fmt.Sprintf("mtu=%dB", *entry.Mtu)) + } + + info = append(info, fmt.Sprintf("counters={in={%di %dd %dn %dB} out={%di %dd %dn %dB}}", + entry.NInInterests, entry.NInData, entry.NInNacks, entry.NInBytes, + entry.NOutInterests, entry.NOutData, entry.NOutNacks, entry.NOutBytes)) + + flags := []string{} + flags = append(flags, strings.ToLower(mgmt.Persistency(entry.FacePersistency).String())) + info = append(info, fmt.Sprintf("flags={%s}", strings.Join(flags, " "))) + + fmt.Printf("%s\n", strings.Join(info, " ")) + } +} diff --git a/tools/nfdc/nfdc_route.go b/tools/nfdc/nfdc_route.go new file mode 100644 index 00000000..0e8e2848 --- /dev/null +++ b/tools/nfdc/nfdc_route.go @@ -0,0 +1,71 @@ +package nfdc + +import ( + "fmt" + "os" + "strings" + "time" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" +) + +func (n *Nfdc) ExecRouteList(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "rib"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "list"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseRibStatus(enc.NewWireReader(data), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing RIB status: %+v\n", err) + return + } + + for _, entry := range status.Entries { + for _, route := range entry.Routes { + expiry := "never" + if route.ExpirationPeriod != nil { + expiry = (time.Duration(*route.ExpirationPeriod) * time.Millisecond).String() + } + + // TODO: convert origin, flags to string + fmt.Printf("prefix=%s nexthop=%d origin=%d cost=%d flags=%d expires=%s\n", + entry.Name, route.FaceId, route.Origin, route.Cost, route.Flags, expiry) + } + } +} + +func (n *Nfdc) ExecFibList(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "fib"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "list"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseFibStatus(enc.NewWireReader(data), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing FIB status: %+v\n", err) + return + } + + fmt.Println("FIB:") + for _, entry := range status.Entries { + nexthops := make([]string, 0) + for _, record := range entry.NextHopRecords { + nexthops = append(nexthops, fmt.Sprintf("faceid=%d (cost=%d)", record.FaceId, record.Cost)) + } + fmt.Printf(" %s nexthops={%s}\n", entry.Name, strings.Join(nexthops, ", ")) + } +} diff --git a/tools/nfdc/nfdc_status.go b/tools/nfdc/nfdc_status.go new file mode 100644 index 00000000..c3bbbaea --- /dev/null +++ b/tools/nfdc/nfdc_status.go @@ -0,0 +1,55 @@ +package nfdc + +import ( + "fmt" + "os" + "strings" + "time" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" +) + +func (n *Nfdc) ExecStatusGeneral(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "status"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "general"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseGeneralStatus(enc.NewWireReader(data), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing general status: %+v\n", err) + return + } + + fmt.Println("General NFD status:") + n.statusPadding = 24 + n.printStatusLine("version", status.NfdVersion) + n.printStatusLine("startTime", time.Unix(0, int64(status.StartTimestamp))) + n.printStatusLine("currentTime", time.Unix(0, int64(status.CurrentTimestamp))) + n.printStatusLine("uptime", (status.CurrentTimestamp - status.StartTimestamp)) + n.printStatusLine("nNameTreeEntries", status.NNameTreeEntries) + n.printStatusLine("nFibEntries", status.NFibEntries) + n.printStatusLine("nPitEntries", status.NCsEntries) + n.printStatusLine("nMeasurementsEntries", status.NMeasurementsEntries) + n.printStatusLine("nCsEntries", status.NCsEntries) + n.printStatusLine("nInInterests", status.NInInterests) + n.printStatusLine("nOutInterests", status.NOutInterests) + n.printStatusLine("nInData", status.NInData) + n.printStatusLine("nOutData", status.NOutData) + n.printStatusLine("nInNacks", status.NInNacks) + n.printStatusLine("nOutNacks", status.NOutNacks) + n.printStatusLine("nSatisfiedInterests", status.NSatisfiedInterests) + n.printStatusLine("nUnsatisfiedInterests", status.NUnsatisfiedInterests) +} + +func (n *Nfdc) printStatusLine(key string, value any) { + padding := n.statusPadding - len(key) + fmt.Printf("%s%s=%v\n", strings.Repeat(" ", padding), key, value) +} diff --git a/tools/nfdc/nfdc_strategy.go b/tools/nfdc/nfdc_strategy.go new file mode 100644 index 00000000..be802b70 --- /dev/null +++ b/tools/nfdc/nfdc_strategy.go @@ -0,0 +1,34 @@ +package nfdc + +import ( + "fmt" + "os" + + enc "github.com/named-data/ndnd/std/encoding" + mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" +) + +func (n *Nfdc) ExecStrategyList(args []string) { + suffix := enc.Name{ + enc.NewStringComponent(enc.TypeGenericNameComponent, "strategy-choice"), + enc.NewStringComponent(enc.TypeGenericNameComponent, "list"), + } + + data, err := n.fetchStatusDataset(suffix) + if err != nil { + fmt.Fprintf(os.Stderr, "Error fetching status dataset: %+v\n", err) + return + } + + status, err := mgmt.ParseStrategyChoiceMsg(enc.NewWireReader(data), true) + if err != nil { + fmt.Fprintf(os.Stderr, "Error parsing strategy list: %+v\n", err) + return + } + + for _, entry := range status.StrategyChoices { + if entry.Strategy != nil { + fmt.Printf("prefix=%s strategy=%s\n", entry.Name, entry.Strategy.Name) + } + } +} diff --git a/tools/pingclient.go b/tools/pingclient.go index ce823b65..e0d4ae9c 100644 --- a/tools/pingclient.go +++ b/tools/pingclient.go @@ -157,8 +157,7 @@ func (pc *PingClient) run() { } // start the engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - pc.app = engine.NewBasicEngine(face) + pc.app = engine.NewBasicEngine(engine.NewDefaultFace()) err = pc.app.Start() if err != nil { log.Fatalf("Unable to start engine: %+v", err) diff --git a/tools/pingserver.go b/tools/pingserver.go index 38637c2d..eeb678ac 100644 --- a/tools/pingserver.go +++ b/tools/pingserver.go @@ -51,8 +51,7 @@ func (ps *PingServer) run() { ps.name = append(prefix, enc.NewStringComponent(enc.TypeGenericNameComponent, "ping")) - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - ps.app = engine.NewBasicEngine(face) + ps.app = engine.NewBasicEngine(engine.NewDefaultFace()) err = ps.app.Start() if err != nil { log.Fatalf("Unable to start engine: %+v", err) diff --git a/tools/putchunks.go b/tools/putchunks.go index 74d4f188..858b8b5f 100644 --- a/tools/putchunks.go +++ b/tools/putchunks.go @@ -41,17 +41,16 @@ func (pc *PutChunks) run() { } // start face and engine - face := engine.NewUnixFace("/var/run/nfd/nfd.sock") - engine := engine.NewBasicEngine(face) - err = engine.Start() + app := engine.NewBasicEngine(engine.NewDefaultFace()) + err = app.Start() if err != nil { log.Errorf("Unable to start engine: %+v", err) return } - defer engine.Stop() + defer app.Stop() // start object client - cli := object.NewClient(engine, object.NewMemoryStore()) + cli := object.NewClient(app, object.NewMemoryStore()) err = cli.Start() if err != nil { log.Errorf("Unable to start object client: %+v", err) @@ -86,7 +85,7 @@ func (pc *PutChunks) run() { log.Infof("Object produced: %s", vname) // register route to the object - err = engine.RegisterRoute(name) + err = app.RegisterRoute(name) if err != nil { log.Fatalf("Unable to register route: %+v", err) return