pkg/tcp_metrics/parser/parse.go (231 lines of code) (raw):

package parser import ( "fmt" "net" "time" "unsafe" "github.com/GoogleCloudPlatform/netd/pkg/tcp_metrics/inetdiag" "github.com/GoogleCloudPlatform/netd/pkg/tcp_metrics/tcp" ) // Snapshot contains all info gathered through netlink library. type Snapshot struct { // Timestamp of batch of messages containing this message. Timestamp time.Time // Bit field indicating whether each message type was observed. Observed uint32 // Bit field indicating whether any message type was NOT fully parsed. // TODO - populate this field if any message is ignored, or not fully parsed. NotFullyParsed uint32 // Info from struct inet_diag_msg, including socket_id; InetDiagMsg *inetdiag.InetDiagMsg SockInfo *inetdiag.SockID // From INET_DIAG_CONG message. CongestionAlgorithm string // See https://tools.ietf.org/html/rfc3168 // TODO Do we need to record whether these are present and zero, vs absent? TOS uint8 TClass uint8 ClassID uint8 // TODO Do we need to record present and zero, vs absent? Shutdown uint8 // From INET_DIAG_PROTOCOL message. // TODO Do we need to record present and zero, vs absent? Protocol inetdiag.Protocol Mark uint32 // TCPInfo contains data from struct tcp_info. TCPInfo *tcp.LinuxTCPInfo // Data obtained from INET_DIAG_MEMINFO. MemInfo *inetdiag.MemInfo // Data obtained from INET_DIAG_SKMEMINFO. SocketMem *inetdiag.SocketMemInfo VegasInfo *inetdiag.VegasInfo DCTCPInfo *inetdiag.DCTCPInfo BBRInfo *inetdiag.BBRInfo } // ParseRouteAttr parses a byte array into slice of NetlinkRouteAttr struct. // Derived from "github.com/vishvananda/netlink/nl/nl_linux.go" func ParseRouteAttr(b []byte) ([]NetlinkRouteAttr, error) { var attrs []NetlinkRouteAttr for len(b) >= SizeofRtAttr { a, vbuf, alen, err := netlinkRouteAttrAndValue(b) if err != nil { return nil, err } ra := NetlinkRouteAttr{Attr: RtAttr(*a), Value: vbuf[:int(a.Len)-SizeofRtAttr]} attrs = append(attrs, ra) b = b[alen:] } return attrs, nil } // MakeArchivalRecord parses the NetlinkMessage into a ArchivalRecord. If skipLocal is true, it will return nil for // loopback, local unicast, multicast, and unspecified connections. // Note that Parse does not populate the Timestamp field, so caller should do so. func MakeSnapShot(msg *NetlinkMessage, skipLocal bool) (*Snapshot, error) { if msg.Header.Type != 20 { return nil, ErrNotType20 } raw, attrBytes := inetdiag.SplitInetDiagMsg(msg.Data) if raw == nil { return nil, ErrParseFailed } if skipLocal { idm, err := raw.Parse() if err != nil { return nil, err } if isLocal(idm.ID.SrcIP()) || isLocal(idm.ID.DstIP()) { return nil, nil } } attrsUnsafe, err := ParseRouteAttr(attrBytes) if err != nil { return nil, err } maxAttrType := uint16(0) for _, a := range attrsUnsafe { t := a.Attr.Type if t > maxAttrType { maxAttrType = t } } if maxAttrType > 2*inetdiag.INET_DIAG_MAX { maxAttrType = 2 * inetdiag.INET_DIAG_MAX } attributes := make([][]byte, maxAttrType+1, maxAttrType+1) for _, a := range attrsUnsafe { t := a.Attr.Type if t > maxAttrType { fmt.Println("Error!! Received RouteAttr with very large Type:", t) continue } if attributes[t] != nil { // TODO - add metric so we can alert on these. fmt.Println("Parse error - Attribute appears more than once:", t) } attributes[t] = a.Value } snp, err := decode(&raw, attributes) if err != nil { return nil, err } sockInfo := snp.InetDiagMsg.ID.GetSockID() snp.SockInfo = &sockInfo return snp, nil } func isLocal(addr net.IP) bool { return addr.IsLoopback() || addr.IsLinkLocalUnicast() || addr.IsMulticast() || addr.IsUnspecified() } // Parse returns the InetDiagMsg itself // Modified from original to also return attribute data array. func Parse(raw inetdiag.RawInetDiagMsg) (*inetdiag.InetDiagMsg, error) { // TODO - why using rtaAlign on InetDiagMsg ??? align := rtaAlignOf(int(unsafe.Sizeof(inetdiag.InetDiagMsg{}))) if len(raw) < align { return nil, ErrParseFailed } return (*inetdiag.InetDiagMsg)(unsafe.Pointer(&raw[0])), nil } func decode(rawIDM *inetdiag.RawInetDiagMsg, attributes [][]byte) (*Snapshot, error) { var err error result := Snapshot{} if rawIDM != nil { result.InetDiagMsg, err = rawIDM.Parse() if err != nil { fmt.Println("Error decoding RawIDM:", err) return nil, err } } for t, raw := range attributes { if raw == nil { continue } rta := RouteAttrValue(raw) ok := false switch t { case inetdiag.INET_DIAG_MEMINFO: result.MemInfo, ok = rta.toMemInfo() case inetdiag.INET_DIAG_INFO: result.TCPInfo, ok = rta.toLinuxTCPInfo() case inetdiag.INET_DIAG_VEGASINFO: result.VegasInfo, ok = rta.toVegasInfo() case inetdiag.INET_DIAG_CONG: result.CongestionAlgorithm, ok = rta.CongestionAlgorithm() case inetdiag.INET_DIAG_TOS: result.TOS, ok = rta.toTOS() case inetdiag.INET_DIAG_TCLASS: result.TClass, ok = rta.toTCLASS() case inetdiag.INET_DIAG_SKMEMINFO: result.SocketMem, ok = rta.toSockMemInfo() case inetdiag.INET_DIAG_SHUTDOWN: result.Shutdown, ok = rta.toShutdown() case inetdiag.INET_DIAG_DCTCPINFO: result.DCTCPInfo, ok = rta.toDCTCPInfo() case inetdiag.INET_DIAG_PROTOCOL: result.Protocol, ok = rta.toProtocol() case inetdiag.INET_DIAG_MARK: result.Mark, ok = rta.toMark() case inetdiag.INET_DIAG_BBRINFO: result.BBRInfo, ok = rta.toBBRInfo() case inetdiag.INET_DIAG_CLASS_ID: result.ClassID, ok = rta.toClassID() } bit := uint32(1) << uint8(t-1) result.Observed |= bit if !ok { result.NotFullyParsed |= bit } } return &result, nil } /*********************************************************************************************/ /* Conversions from RouteAttr.Value to various tcp and inetdiag structs */ /*********************************************************************************************/ // RouteAttrValue is the type of RouteAttr.Value type RouteAttrValue []byte // maybeCopy checks whether the src is the full size of the intended struct size. // If so, it just returns the pointer, otherwise it copies the content to an // appropriately sized new byte slice, and returns pointer to that. func maybeCopy(src []byte, size int, msgType string) (unsafe.Pointer, bool) { if len(src) < size { data := make([]byte, size) copy(data, src) return unsafe.Pointer(&data[0]), true } return unsafe.Pointer(&src[0]), len(src) == size } // toMemInfo maps the raw RouteAttrValue onto a MemInfo. func (raw RouteAttrValue) toMemInfo() (*inetdiag.MemInfo, bool) { structSize := (int)(unsafe.Sizeof(inetdiag.MemInfo{})) data, ok := maybeCopy(raw, structSize, "MemInfo") if !ok { fmt.Println("memInfo data is larger than struct") } return (*inetdiag.MemInfo)(data), ok } // toLinuxTCPInfo maps the raw RouteAttrValue into a LinuxTCPInfo struct. // For older data, it may have to copy the bytes. func (raw RouteAttrValue) toLinuxTCPInfo() (*tcp.LinuxTCPInfo, bool) { structSize := (int)(unsafe.Sizeof(tcp.LinuxTCPInfo{})) data, ok := maybeCopy(raw, structSize, "TCPInfo") if !ok { fmt.Println("tcpinfo data is larger than struct") } return (*tcp.LinuxTCPInfo)(data), ok } // toVegasInfo maps the raw RouteAttrValue onto a VegasInfo. // For older data, it may have to copy the bytes. func (raw RouteAttrValue) toVegasInfo() (*inetdiag.VegasInfo, bool) { structSize := (int)(unsafe.Sizeof(inetdiag.VegasInfo{})) data, ok := maybeCopy(raw, structSize, "VegasInfo") return (*inetdiag.VegasInfo)(data), ok } // CongestionAlgorithm returns the congestion algorithm string // INET_DIAG_CONG func (raw RouteAttrValue) CongestionAlgorithm() (string, bool) { // This is sometimes empty, but that is valid, so we return true. return string(raw[:len(raw)-1]), true } func (raw RouteAttrValue) toUint8() (uint8, bool) { if len(raw) < 1 { return 0, false } return uint8(raw[0]), true } // toTOS marshals the TCP Type Of Service field. See https://tools.ietf.org/html/rfc3168 func (raw RouteAttrValue) toTOS() (uint8, bool) { return raw.toUint8() } // toTCLASS marshals the TCP Traffic Class octet. See https://tools.ietf.org/html/rfc3168 func (raw RouteAttrValue) toTCLASS() (uint8, bool) { return raw.toUint8() } // toTCLASS marshals the TCP Traffic Class octet. See https://tools.ietf.org/html/rfc3168 func (raw RouteAttrValue) toClassID() (uint8, bool) { return raw.toUint8() } // toSockMemInfo maps the raw RouteAttrValue onto a SockMemInfo. // For older data, it may have to copy the bytes. func (raw RouteAttrValue) toSockMemInfo() (*inetdiag.SocketMemInfo, bool) { structSize := (int)(unsafe.Sizeof(inetdiag.SocketMemInfo{})) data, ok := maybeCopy(raw, structSize, "SockMemInfo") return (*inetdiag.SocketMemInfo)(data), ok } func (raw RouteAttrValue) toShutdown() (uint8, bool) { return raw.toUint8() } // toVegasInfo maps the raw RouteAttrValue onto a VegasInfo. // For older data, it may have to copy the bytes. func (raw RouteAttrValue) toDCTCPInfo() (*inetdiag.DCTCPInfo, bool) { structSize := (int)(unsafe.Sizeof(inetdiag.DCTCPInfo{})) data, ok := maybeCopy(raw, structSize, "DCTCPInfo") return (*inetdiag.DCTCPInfo)(data), ok } func (raw RouteAttrValue) toProtocol() (inetdiag.Protocol, bool) { p, ok := raw.toUint8() return inetdiag.Protocol(p), ok } func (raw RouteAttrValue) toMark() (uint32, bool) { if raw == nil || len(raw) != 4 { return 0, false } return *(*uint32)(unsafe.Pointer(&raw[0])), true } // toBBRInfo maps the raw RouteAttrValue onto a BBRInfo. // For older data, it may have to copy the bytes. func (raw RouteAttrValue) toBBRInfo() (*inetdiag.BBRInfo, bool) { structSize := (int)(unsafe.Sizeof(inetdiag.BBRInfo{})) data, ok := maybeCopy(raw, structSize, "BBRInfo") return (*inetdiag.BBRInfo)(data), ok }