support /proc/net/snmp

This proc file contains statistics according to [1].

[1] https://tools.ietf.org/html/rfc2013

Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
Change-Id: I9662132085edd8a7783d356ce4237d7ac0800d94
This commit is contained in:
Jianfeng Tan
2019-10-15 16:38:40 +00:00
parent bfa0bb24dd
commit e3d4a67739
4 changed files with 349 additions and 2 deletions
+116 -1
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"io"
"reflect"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -33,6 +34,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/socket/unix"
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/syserror"
)
// newNet creates a new proc net entry.
@@ -40,7 +42,8 @@ func (p *proc) newNetDir(ctx context.Context, k *kernel.Kernel, msrc *fs.MountSo
var contents map[string]*fs.Inode
if s := p.k.NetworkStack(); s != nil {
contents = map[string]*fs.Inode{
"dev": seqfile.NewSeqFileInode(ctx, &netDev{s: s}, msrc),
"dev": seqfile.NewSeqFileInode(ctx, &netDev{s: s}, msrc),
"snmp": seqfile.NewSeqFileInode(ctx, &netSnmp{s: s}, msrc),
// The following files are simple stubs until they are
// implemented in netstack, if the file contains a
@@ -195,6 +198,118 @@ func (n *netDev) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
return data, 0
}
// netSnmp implements seqfile.SeqSource for /proc/net/snmp.
//
// +stateify savable
type netSnmp struct {
s inet.Stack
}
// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate.
func (n *netSnmp) NeedsUpdate(generation int64) bool {
return true
}
type snmpLine struct {
prefix string
header string
}
var snmp = []snmpLine{
{
prefix: "Ip",
header: "Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates",
},
{
prefix: "Icmp",
header: "InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps",
},
{
prefix: "IcmpMsg",
},
{
prefix: "Tcp",
header: "RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors",
},
{
prefix: "Udp",
header: "InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti",
},
{
prefix: "UdpLite",
header: "InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti",
},
}
func toSlice(a interface{}) []uint64 {
v := reflect.Indirect(reflect.ValueOf(a))
return v.Slice(0, v.Len()).Interface().([]uint64)
}
func sprintSlice(s []uint64) string {
if len(s) == 0 {
return ""
}
r := fmt.Sprint(s)
return r[1 : len(r)-1] // Remove "[]" introduced by fmt of slice.
}
// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. See Linux's
// net/core/net-procfs.c:dev_seq_show.
func (n *netSnmp) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
if h != nil {
return nil, 0
}
contents := make([]string, 0, len(snmp)*2)
types := []interface{}{
&inet.StatSNMPIP{},
&inet.StatSNMPICMP{},
nil, // TODO(gvisor.dev/issue/628): Support IcmpMsg stats.
&inet.StatSNMPTCP{},
&inet.StatSNMPUDP{},
&inet.StatSNMPUDPLite{},
}
for i, stat := range types {
line := snmp[i]
if stat == nil {
contents = append(
contents,
fmt.Sprintf("%s:\n", line.prefix),
fmt.Sprintf("%s:\n", line.prefix),
)
continue
}
if err := n.s.Statistics(stat, line.prefix); err != nil {
if err == syserror.EOPNOTSUPP {
log.Infof("Failed to retrieve %s of /proc/net/snmp: %v", line.prefix, err)
} else {
log.Warningf("Failed to retrieve %s of /proc/net/snmp: %v", line.prefix, err)
}
}
var values string
if line.prefix == "Tcp" {
tcp := stat.(*inet.StatSNMPTCP)
// "Tcp" needs special processing because MaxConn is signed. RFC 2012.
values = fmt.Sprintf("%s %d %s", sprintSlice(tcp[:3]), int64(tcp[3]), sprintSlice(tcp[4:]))
} else {
values = sprintSlice(toSlice(stat))
}
contents = append(
contents,
fmt.Sprintf("%s: %s\n", line.prefix, line.header),
fmt.Sprintf("%s: %s\n", line.prefix, values),
)
}
data := make([]seqfile.SeqData, 0, len(snmp)*2)
for _, l := range contents {
data = append(data, seqfile.SeqData{Buf: []byte(l), Handle: (*netSnmp)(nil)})
}
return data, 0
}
// netUnix implements seqfile.SeqSource for /proc/net/unix.
//
// +stateify savable
+20
View File
@@ -153,3 +153,23 @@ type Route struct {
// GatewayAddr is the route gateway address (RTA_GATEWAY).
GatewayAddr []byte
}
// Below SNMP metrics are from Linux/usr/include/linux/snmp.h.
// StatSNMPIP describes Ip line of /proc/net/snmp.
type StatSNMPIP [19]uint64
// StatSNMPICMP describes Icmp line of /proc/net/snmp.
type StatSNMPICMP [27]uint64
// StatSNMPICMPMSG describes IcmpMsg line of /proc/net/snmp.
type StatSNMPICMPMSG [512]uint64
// StatSNMPTCP describes Tcp line of /proc/net/snmp.
type StatSNMPTCP [15]uint64
// StatSNMPUDP describes Udp line of /proc/net/snmp.
type StatSNMPUDP [8]uint64
// StatSNMPUDPLite describes UdpLite line of /proc/net/snmp.
type StatSNMPUDPLite [8]uint64
+1
View File
@@ -1552,6 +1552,7 @@ cc_binary(
srcs = ["proc_net.cc"],
linkstatic = 1,
deps = [
":socket_test_util",
"//test/util:capability_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
+212 -1
View File
@@ -12,9 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "gtest/gtest.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include "absl/strings/str_split.h"
#include "gtest/gtest.h"
#include "test/util/capability_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/test_util.h"
@@ -57,6 +65,209 @@ TEST(ProcSysNetIpv4Sack, CanReadAndWrite) {
EXPECT_EQ(buf, to_write);
}
PosixErrorOr<uint64_t> GetSNMPMetricFromProc(const std::string snmp,
const std::string &type,
const std::string &item) {
std::vector<std::string> snmp_vec = absl::StrSplit(snmp, '\n');
// /proc/net/snmp prints a line of headers followed by a line of metrics.
// Only search the headers.
for (unsigned i = 0; i < snmp_vec.size(); i = i + 2) {
if (!absl::StartsWith(snmp_vec[i], type)) continue;
std::vector<std::string> fields =
absl::StrSplit(snmp_vec[i], ' ', absl::SkipWhitespace());
EXPECT_TRUE((i + 1) < snmp_vec.size());
std::vector<std::string> values =
absl::StrSplit(snmp_vec[i + 1], ' ', absl::SkipWhitespace());
EXPECT_TRUE(!fields.empty() && fields.size() == values.size());
// Metrics start at the first index.
for (unsigned j = 1; j < fields.size(); j++) {
if (fields[j] == item) {
uint64_t val;
if (!absl::SimpleAtoi(values[j], &val)) {
return PosixError(EINVAL,
absl::StrCat("field is not a number: ", values[j]));
}
return val;
}
}
}
// We should never get here.
return PosixError(
EINVAL, absl::StrCat("failed to find ", type, "/", item, " in:", snmp));
}
TEST(ProcNetSnmp, TcpReset) {
// TODO(gvisor.dev/issue/866): epsocket metrics are not savable.
const DisableSave ds;
uint64_t oldAttemptFails;
uint64_t oldActiveOpens;
uint64_t oldOutRsts;
auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
oldActiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens"));
oldOutRsts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "OutRsts"));
oldAttemptFails = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails"));
FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0));
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(1234),
};
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
ASSERT_THAT(connect(s.get(), (struct sockaddr *)&sin, sizeof(sin)),
SyscallFailsWithErrno(ECONNREFUSED));
uint64_t newAttemptFails;
uint64_t newActiveOpens;
uint64_t newOutRsts;
snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
newActiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens"));
newOutRsts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "OutRsts"));
newAttemptFails = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "AttemptFails"));
EXPECT_EQ(oldActiveOpens, newActiveOpens - 1);
EXPECT_EQ(oldOutRsts, newOutRsts - 1);
EXPECT_EQ(oldAttemptFails, newAttemptFails - 1);
}
TEST(ProcNetSnmp, TcpEstab) {
// TODO(gvisor.dev/issue/866): epsocket metrics are not savable.
const DisableSave ds;
uint64_t oldEstabResets;
uint64_t oldActiveOpens;
uint64_t oldPassiveOpens;
uint64_t oldCurrEstab;
auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
oldActiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens"));
oldPassiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "PassiveOpens"));
oldCurrEstab = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab"));
oldEstabResets = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "EstabResets"));
FileDescriptor s_listen =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0));
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(1234),
};
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
ASSERT_THAT(bind(s_listen.get(), (struct sockaddr *)&sin, sizeof(sin)),
SyscallSucceeds());
ASSERT_THAT(listen(s_listen.get(), 1), SyscallSucceeds());
FileDescriptor s_connect =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_STREAM, 0));
ASSERT_THAT(connect(s_connect.get(), (struct sockaddr *)&sin, sizeof(sin)),
SyscallSucceeds());
auto s_accept =
ASSERT_NO_ERRNO_AND_VALUE(Accept(s_listen.get(), nullptr, nullptr));
uint64_t newEstabResets;
uint64_t newActiveOpens;
uint64_t newPassiveOpens;
uint64_t newCurrEstab;
snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
newActiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "ActiveOpens"));
newPassiveOpens = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "PassiveOpens"));
newCurrEstab = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab"));
EXPECT_EQ(oldActiveOpens, newActiveOpens - 1);
EXPECT_EQ(oldPassiveOpens, newPassiveOpens - 1);
EXPECT_EQ(oldCurrEstab, newCurrEstab - 2);
ASSERT_THAT(send(s_connect.get(), "a", 1, 0), SyscallSucceedsWithValue(1));
s_accept.reset(-1);
s_connect.reset(-1);
snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
newCurrEstab = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "CurrEstab"));
newEstabResets = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Tcp", "EstabResets"));
EXPECT_EQ(oldCurrEstab, newCurrEstab);
EXPECT_EQ(oldEstabResets, newEstabResets - 2);
}
TEST(ProcNetSnmp, UdpNoPorts) {
// TODO(gvisor.dev/issue/866): epsocket metrics are not savable.
const DisableSave ds;
uint64_t oldOutDatagrams;
uint64_t oldNoPorts;
auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
oldOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams"));
oldNoPorts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts"));
FileDescriptor s =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(1234),
};
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
ASSERT_THAT(sendto(s.get(), "a", 1, 0, (struct sockaddr *)&sin, sizeof(sin)),
SyscallSucceedsWithValue(1));
uint64_t newOutDatagrams;
uint64_t newNoPorts;
snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
newOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams"));
newNoPorts = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "NoPorts"));
EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1);
EXPECT_EQ(oldNoPorts, newNoPorts - 1);
}
TEST(ProcNetSnmp, UdpIn) {
// TODO(gvisor.dev/issue/866): epsocket metrics are not savable.
const DisableSave ds;
uint64_t oldOutDatagrams;
uint64_t oldInDatagrams;
auto snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
oldOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams"));
oldInDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams"));
FileDescriptor server =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(1234),
};
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
ASSERT_THAT(bind(server.get(), (struct sockaddr *)&sin, sizeof(sin)),
SyscallSucceeds());
FileDescriptor client =
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, 0));
ASSERT_THAT(sendto(client.get(), "a", 1, 0, (struct sockaddr *)&sin,
sizeof(sin)), SyscallSucceedsWithValue(1));
char buf[128];
ASSERT_THAT(recvfrom(server.get(), buf, sizeof(buf), 0, NULL, NULL),
SyscallSucceedsWithValue(1));
uint64_t newOutDatagrams;
uint64_t newInDatagrams;
snmp = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/snmp"));
newOutDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "OutDatagrams"));
newInDatagrams = ASSERT_NO_ERRNO_AND_VALUE(GetSNMPMetricFromProc(snmp, "Udp", "InDatagrams"));
EXPECT_EQ(oldOutDatagrams, newOutDatagrams - 1);
EXPECT_EQ(oldInDatagrams, newInDatagrams - 1);
}
} // namespace
} // namespace testing
} // namespace gvisor