diff --git a/constants.go b/constants.go index efac4e3..6350bba 100644 --- a/constants.go +++ b/constants.go @@ -54,6 +54,13 @@ const ( ipvsCmdAttrTimeoutUDP ) +// Attributes used to describe an info +const ( + ipvsCmdAttrInfoUnspec int = iota + ipvsCmdAttrInfoVersion + ipvsCmdAttrInfoConnTableSize +) + // Attributes used to describe a service. Used inside nested attribute // ipvsCmdAttrService const ( diff --git a/ipvs.go b/ipvs.go index 61b6f0a..9507aa7 100644 --- a/ipvs.go +++ b/ipvs.go @@ -74,6 +74,24 @@ type Config struct { TimeoutUDP time.Duration } +// Info defines IPVS info +type Info struct { + Version *Version + ConnTableSize uint32 +} + +// Version defines IPVS version +type Version struct { + Major uint + Minor uint + Patch uint +} + +// String returns a string of IPVS version +func (v *Version) String() string { + return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) +} + // Handle provides a namespace specific ipvs handle to program ipvs // rules. type Handle struct { @@ -204,3 +222,44 @@ func (i *Handle) GetConfig() (*Config, error) { func (i *Handle) SetConfig(c *Config) error { return i.doSetConfigCmd(c) } + +func toVersion(v uint) *Version { + return &Version{ + Major: (v >> 16) & 0xff, + Minor: (v >> 8) & 0xff, + Patch: v & 0xff, + } +} + +// GetInfo returns info details from IPVS +func (i *Handle) GetInfo() (*Info, error) { + res, err := i.doGetInfoCmd() + if err != nil { + return nil, err + } + + return &Info{ + Version: toVersion(uint(res.version)), + ConnTableSize: res.connTableSize, + }, nil +} + +// GetVersion returns version from IPVS +func (i *Handle) GetVersion() (*Version, error) { + res, err := i.doGetInfoCmd() + if err != nil { + return nil, err + } + + return toVersion(uint(res.version)), nil +} + +// GetConnectionTableSize returns connection table size from IPVS +func (i *Handle) GetConnectionTableSize() (uint32, error) { + res, err := i.doGetInfoCmd() + if err != nil { + return 0, err + } + + return res.connTableSize, nil +} diff --git a/ipvs_test.go b/ipvs_test.go index 4054f6d..5a3656e 100644 --- a/ipvs_test.go +++ b/ipvs_test.go @@ -4,6 +4,7 @@ package ipvs import ( "net" + "regexp" "runtime" "syscall" "testing" @@ -44,6 +45,8 @@ var ( "Tunnel", "Route", } + + verRegexp = regexp.MustCompile(`^\d\.\d\.\d$`) ) func lookupFwMethod(fwMethod uint32) string { @@ -371,6 +374,41 @@ func TestTimeouts(t *testing.T) { assert.DeepEqual(t, *c3, Config{77 * time.Second, 66 * time.Second, 77 * time.Second}) } +func TestInfo(t *testing.T) { + defer setupTestOSContext(t) + + i, err := New("") + assert.NilError(t, err) + + info, err := i.GetInfo() + assert.NilError(t, err) + assert.Check(t, info.Version != nil) + assert.Assert(t, info.Version.String() != "") + assert.Assert(t, info.ConnTableSize > 0) +} + +func TestVersion(t *testing.T) { + defer setupTestOSContext(t) + + i, err := New("") + assert.NilError(t, err) + + ver, err := i.GetVersion() + assert.NilError(t, err) + assert.Assert(t, verRegexp.MatchString(ver.String())) +} + +func TestConnTableSize(t *testing.T) { + defer setupTestOSContext(t) + + i, err := New("") + assert.NilError(t, err) + + size, err := i.GetConnectionTableSize() + assert.NilError(t, err) + assert.Assert(t, size > 0) +} + // setupTestOSContext joins a new network namespace, and returns its associated // teardown function. // diff --git a/netlink.go b/netlink.go index 1a822da..cc65419 100644 --- a/netlink.go +++ b/netlink.go @@ -38,6 +38,11 @@ type ipvsFlags struct { mask uint32 } +type ipvsInfo struct { + version uint32 + connTableSize uint32 +} + func deserializeGenlMsg(b []byte) (hdr *genlMsgHdr) { return (*genlMsgHdr)(unsafe.Pointer(&b[0:unsafe.Sizeof(*hdr)][0])) } @@ -573,6 +578,44 @@ func (i *Handle) doSetConfigCmd(c *Config) error { return err } +// parseInfo given a ipvs netlink response this function will respond with a valid info entry, an error otherwise +func (i *Handle) parseInfo(msg []byte) (*ipvsInfo, error) { + var info ipvsInfo + + hdr := deserializeGenlMsg(msg) + attrs, err := nl.ParseRouteAttr(msg[hdr.Len():]) + if err != nil { + return nil, err + } + + for _, attr := range attrs { + attrType := int(attr.Attr.Type) + switch attrType { + case ipvsCmdAttrInfoVersion: + info.version = native.Uint32(attr.Value) + case ipvsCmdAttrInfoConnTableSize: + info.connTableSize = native.Uint32(attr.Value) + } + } + + return &info, nil +} + +// doGetInfoCmd a wrapper function to be used by GetInfo +func (i *Handle) doGetInfoCmd() (*ipvsInfo, error) { + msg, err := i.doCmdWithoutAttr(ipvsCmdGetInfo) + if err != nil { + return nil, err + } + + res, err := i.parseInfo(msg[0]) + if err != nil { + return nil, err + } + + return res, nil +} + // IPVS related netlink message format explained /* EACH NETLINK MSG is of the below format, this is what we will receive from execute() api.