diff --git a/route.go b/route.go index 4766443..79ce50c 100644 --- a/route.go +++ b/route.go @@ -387,63 +387,43 @@ func (a *RouteAttributes) encodeMultipath() ([]byte, error) { return b, nil } +// parseMultipath consumes RTA_MULTIPATH data into RouteAttributes. func (a *RouteAttributes) parseMultipath(b []byte) error { - // check for truncated message - if len(b) <= unix.SizeofRtNexthop { - return errInvalidRouteMessageAttr - } + // We cannot retain b after the function returns, so make a copy of the + // bytes up front for the multipathParser. + buf := make([]byte, len(b)) + copy(buf, b) - // Iterate through the nested array of rtnexthop, unpacking each and appending them to mp - for i := 0; i <= len(b); { - // check for end of message - if len(b)-i < unix.SizeofRtNexthop { - return nil - } - - // Copy over the struct portion - var nh NextHop - var nhb [unix.SizeofRtNexthop]byte - copy(nhb[:], b[i:i+unix.SizeofRtNexthop]) - - copy( - (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(&nh.Hop)))[:], - (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(&nhb[0])))[:], - ) - - // check again for a truncated message - if int(nh.Hop.Length) > len(b) { - return errInvalidRouteMessageAttr - } - - // grab a new attributedecoder for the nested attributes - start := (i + unix.SizeofRtNexthop) - end := (i + int(nh.Hop.Length)) - - ad, err := netlink.NewAttributeDecoder(b[start:end]) - if err != nil { + // Iterate until no more bytes remain in the buffer or an error occurs. + mpp := &multipathParser{b: buf} + for mpp.Next() { + // Each iteration reads a fixed length RTNextHop structure immediately + // followed by its associated netlink attributes with optional data. + nh := NextHop{Hop: mpp.RTNextHop()} + if err := nh.decode(mpp.AttributeDecoder()); err != nil { return err } - // read in the nested attributes - if err := nh.decode(ad); err != nil { + // Stop iteration early if the data was malformed, or otherwise append + // this NextHop to the Multipath field. + if err := mpp.Err(); err != nil { return err } - // append this hop to the parent Multipath struct a.Multipath = append(a.Multipath, nh) - - // move forward to the next element in multipath.[]nexthop - i += int(nh.Hop.Length) } return nil } -// TODO: Implement func (mp *RTMultiPath) encode() - // rtnexthop payload is at least one nested attribute RTA_GATEWAY // possibly others? func (nh *NextHop) decode(ad *netlink.AttributeDecoder) error { + if ad == nil { + // Invalid decoder, do nothing. + return nil + } + for ad.Next() { switch ad.Type() { case unix.RTA_GATEWAY: @@ -458,3 +438,85 @@ func (nh *NextHop) decode(ad *netlink.AttributeDecoder) error { return ad.Err() } + +// A multipathParser parses packed RTNextHop and netlink attributes into +// multipath attributes for an rtnetlink route. +type multipathParser struct { + // Any errors which occurred during parsing. + err error + + // The underlying buffer and a pointer to the reading position. + b []byte + i int + + // The length of the next set of netlink attributes. + alen int +} + +// Next continues iteration until an error occurs or no bytes remain. +func (mpp *multipathParser) Next() bool { + if mpp.err != nil { + return false + } + + // Are there any bytes left to consume? + return len(mpp.b[mpp.i:]) > 0 +} + +// Err returns any errors encountered while parsing. +func (mpp *multipathParser) Err() error { return mpp.err } + +// RTNextHop parses the next RTNextHop structure from the buffer. +func (mpp *multipathParser) RTNextHop() RTNextHop { + if mpp.err != nil { + return RTNextHop{} + } + + if len(mpp.b)-mpp.i < unix.SizeofRtNexthop { + // Out of bounds access, not enough data for a valid RTNextHop. + return RTNextHop{} + } + + // Consume an RTNextHop from the buffer by copying its bytes into an output + // structure while also verifying that the size of each structure is equal + // to avoid any out-of-bounds unsafe memory access. + var rtnh RTNextHop + next := mpp.b[mpp.i : mpp.i+unix.SizeofRtNexthop] + + if unix.SizeofRtNexthop != len(next) { + panic("rtnetlink: invalid RTNextHop structure size, panicking to avoid out-of-bounds unsafe access") + } + + copy( + (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(&rtnh)))[:], + (*(*[unix.SizeofRtNexthop]byte)(unsafe.Pointer(&next[0])))[:], + ) + + // Compute the length of the next set of attributes using the Length value + // in the RTNextHop, minus the size of that fixed length structure itself. + // Then, advance the pointer to be ready to read those attributes. + mpp.alen = int(rtnh.Length) - unix.SizeofRtNexthop + mpp.i += unix.SizeofRtNexthop + + return rtnh +} + +// AttributeDecoder returns a netlink.AttributeDecoder pointed at the next set +// of netlink attributes from the buffer. +func (mpp *multipathParser) AttributeDecoder() *netlink.AttributeDecoder { + if mpp.err != nil { + return nil + } + + // Consume the next set of netlink attributes from the buffer and advance + // the pointer to the next RTNextHop or EOF once that is complete. + ad, err := netlink.NewAttributeDecoder(mpp.b[mpp.i : mpp.i+mpp.alen]) + if err != nil { + mpp.err = err + return nil + } + + mpp.i += mpp.alen + + return ad +}