hostinet: support /proc/net/snmp and /proc/net/dev

For hostinet, we inherit the data from host procfs. To to that, we
cache the fds for these files for later reads.

Fixes #506

Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
Change-Id: I2f81215477455b9c59acf67e33f5b9af28ee0165
This commit is contained in:
Jianfeng Tan 2019-05-16 16:21:46 +08:00
parent b94505ecc0
commit dd7d1f825d
1 changed files with 105 additions and 1 deletions

View File

@ -16,8 +16,11 @@ package hostinet
import (
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"syscall"
@ -26,6 +29,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/syserror"
)
@ -51,6 +55,8 @@ type Stack struct {
tcpRecvBufSize inet.TCPBufferSize
tcpSendBufSize inet.TCPBufferSize
tcpSACKEnabled bool
netDevFile *os.File
netSNMPFile *os.File
}
// NewStack returns an empty Stack containing no configuration.
@ -98,6 +104,18 @@ func (s *Stack) Configure() error {
log.Warningf("Failed to read if TCP SACK if enabled, setting to true")
}
if f, err := os.Open("/proc/net/dev"); err != nil {
log.Warningf("Failed to open /proc/net/dev: %v", err)
} else {
s.netDevFile = f
}
if f, err := os.Open("/proc/net/snmp"); err != nil {
log.Warningf("Failed to open /proc/net/snmp: %v", err)
} else {
s.netSNMPFile = f
}
return nil
}
@ -326,9 +344,95 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error {
return syserror.EACCES
}
// getLine reads one line from proc file, with specified prefix.
// The last argument, withHeader, specifies if it contains line header.
func getLine(f *os.File, prefix string, withHeader bool) string {
data := make([]byte, 4096)
if _, err := f.Seek(0, 0); err != nil {
return ""
}
if _, err := io.ReadFull(f, data); err != io.ErrUnexpectedEOF {
return ""
}
prefix = prefix + ":"
lines := strings.Split(string(data), "\n")
for _, l := range lines {
l = strings.TrimSpace(l)
if strings.HasPrefix(l, prefix) {
if withHeader {
withHeader = false
continue
}
return l
}
}
return ""
}
func toSlice(i interface{}) []uint64 {
v := reflect.Indirect(reflect.ValueOf(i))
return v.Slice(0, v.Len()).Interface().([]uint64)
}
// Statistics implements inet.Stack.Statistics.
func (s *Stack) Statistics(stat interface{}, arg string) error {
return syserror.EOPNOTSUPP
var (
snmpTCP bool
rawLine string
sliceStat []uint64
)
switch stat.(type) {
case *inet.StatDev:
if s.netDevFile == nil {
return fmt.Errorf("/proc/net/dev is not opened for hostinet")
}
rawLine = getLine(s.netDevFile, arg, false /* with no header */)
case *inet.StatSNMPIP, *inet.StatSNMPICMP, *inet.StatSNMPICMPMSG, *inet.StatSNMPTCP, *inet.StatSNMPUDP, *inet.StatSNMPUDPLite:
if s.netSNMPFile == nil {
return fmt.Errorf("/proc/net/snmp is not opened for hostinet")
}
rawLine = getLine(s.netSNMPFile, arg, true)
default:
return syserr.ErrEndpointOperation.ToError()
}
if rawLine == "" {
return fmt.Errorf("Failed to get raw line")
}
parts := strings.SplitN(rawLine, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("Failed to get prefix from: %q", rawLine)
}
sliceStat = toSlice(stat)
fields := strings.Fields(strings.TrimSpace(parts[1]))
if len(fields) != len(sliceStat) {
return fmt.Errorf("Failed to parse fields: %q", rawLine)
}
if _, ok := stat.(*inet.StatSNMPTCP); ok {
snmpTCP = true
}
for i := 0; i < len(sliceStat); i++ {
var err error
if snmpTCP && i == 3 {
var tmp int64
// MaxConn field is signed, RFC 2012.
tmp, err = strconv.ParseInt(fields[i], 10, 64)
sliceStat[i] = uint64(tmp) // Convert back to int before use.
} else {
sliceStat[i], err = strconv.ParseUint(fields[i], 10, 64)
}
if err != nil {
return fmt.Errorf("Failed to parse field %d from: %q, %v", i, rawLine, err)
}
}
return nil
}
// RouteTable implements inet.Stack.RouteTable.