mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
+116
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user