Files
gVisor bot 205a9bb5f9 Internal change.
PiperOrigin-RevId: 966349089
2026-08-17 21:15:14 -07:00

767 lines
20 KiB
Go

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package route provides a NETLINK_ROUTE socket protocol.
package route
import (
"bytes"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/syserr"
)
// commandKind describes the operational class of a message type.
//
// The route message types use the lower 2 bits of the type to describe class
// of command.
type commandKind int
const (
kindNew commandKind = 0x0
kindDel commandKind = 0x1
kindGet commandKind = 0x2
kindSet commandKind = 0x3
)
func typeKind(typ uint16) commandKind {
return commandKind(typ & 0x3)
}
// Protocol implements netlink.Protocol.
//
// +stateify savable
type Protocol struct{}
var _ netlink.Protocol = (*Protocol)(nil)
// FallbackNonHostRoutes controls whether route lookup falls back to non-host routes
// and synthesized interface routes when no default route or longest prefix matches.
// This is intended for MNCC sidecar usage only.
var FallbackNonHostRoutes = false
// NewProtocol creates a NETLINK_ROUTE netlink.Protocol.
func NewProtocol(t *kernel.Task) (netlink.Protocol, *syserr.Error) {
return &Protocol{}, nil
}
// Protocol implements netlink.Protocol.Protocol.
func (p *Protocol) Protocol() int {
return linux.NETLINK_ROUTE
}
// CanSend implements netlink.Protocol.CanSend.
func (p *Protocol) CanSend() bool {
return true
}
// Receive implements netlink.Protocol.Receive.
// From net/core/rtnetlink.c:rtnetlink_rcv.
func (p *Protocol) Receive(ctx context.Context, s *netlink.Socket, buf []byte) *syserr.Error {
return s.ProcessMessages(ctx, buf)
}
// dumpLinks handles RTM_GETLINK dump requests.
func (p *Protocol) dumpLinks(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
// NLM_F_DUMP + RTM_GETLINK messages are supposed to include an
// ifinfomsg. However, Linux <3.9 only checked for rtgenmsg, and some
// userspace applications (including glibc) still include rtgenmsg.
// Linux has a workaround based on the total message length.
//
// We don't bother to check for either, since we don't support any
// extra attributes that may be included anyways.
//
// The message may also contain netlink attribute IFLA_EXT_MASK, which
// we don't support.
// The RTM_GETLINK dump response is a set of messages each containing
// an InterfaceInfoMessage followed by a set of netlink attributes.
// We always send back an NLMSG_DONE.
ms.Multi = true
stack := s.Stack()
if stack == nil {
// No network devices.
return nil
}
ifaces := stack.Interfaces()
for _, idx := range stack.InterfaceIDs() {
p.AddNewLinkMessage(ms, idx, ifaces[idx])
}
return nil
}
// getLinks handles RTM_GETLINK requests.
func (p *Protocol) getLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network devices.
return nil
}
// Parse message.
var ifi linux.InterfaceInfoMessage
attrs, ok := msg.GetData(&ifi)
if !ok {
return syserr.ErrInvalidArgument
}
// Parse attributes.
var byName []byte
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
switch ahdr.Type {
case linux.IFLA_IFNAME:
if len(value) < 1 {
return syserr.ErrInvalidArgument
}
byName = value[:len(value)-1]
// TODO(gvisor.dev/issue/578): Support IFLA_EXT_MASK.
}
}
found := false
for idx, i := range stack.Interfaces() {
switch {
case ifi.Index > 0:
if idx != ifi.Index {
continue
}
case byName != nil:
if string(byName) != i.Name {
continue
}
default:
// Criteria not specified.
return syserr.ErrInvalidArgument
}
p.AddNewLinkMessage(ms, idx, i)
found = true
break
}
if !found {
return syserr.ErrNoDevice
}
return nil
}
// newLink handles RTM_NEWLINK requests.
func (p *Protocol) newLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
return stack.SetInterface(ctx, msg)
}
// setLink handles RTM_SETLINK requests.
func (p *Protocol) setLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
if msg.Header().Flags&linux.NLM_F_CREATE == linux.NLM_F_CREATE {
return syserr.ErrInvalidArgument
}
return stack.SetInterface(ctx, msg)
}
// delLink handles RTM_DELLINK requests.
func (p *Protocol) delLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
var ifinfomsg linux.InterfaceInfoMessage
attrs, ok := msg.GetData(&ifinfomsg)
if !ok {
return syserr.ErrInvalidArgument
}
if ifinfomsg.Index == 0 {
// The index is unspecified, search by the interface name.
ahdr, value, _, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
switch ahdr.Type {
case linux.IFLA_IFNAME:
if len(value) < 1 {
return syserr.ErrInvalidArgument
}
ifname := string(value[:len(value)-1])
for idx, ifa := range stack.Interfaces() {
if ifname == ifa.Name {
ifinfomsg.Index = idx
break
}
}
default:
return syserr.ErrInvalidArgument
}
if ifinfomsg.Index == 0 {
return syserr.ErrNoDevice
}
}
return syserr.FromError(stack.RemoveInterface(ctx, ifinfomsg.Index))
}
func writeLinkInfo(m *nlmsg.Message, idx int32, i inet.Interface) {
m.Put(&linux.InterfaceInfoMessage{
Family: linux.AF_UNSPEC,
Type: i.DeviceType,
Index: idx,
Flags: i.Flags,
})
m.PutAttrString(linux.IFLA_IFNAME, i.Name)
m.PutAttr(linux.IFLA_MTU, primitive.AllocateUint32(i.MTU))
mac := make([]byte, 6)
brd := mac
if len(i.Addr) > 0 {
mac = i.Addr
brd = bytes.Repeat([]byte{0xff}, len(i.Addr))
}
m.PutAttr(linux.IFLA_ADDRESS, primitive.AsByteSlice(mac))
m.PutAttr(linux.IFLA_BROADCAST, primitive.AsByteSlice(brd))
if i.Master != 0 {
m.PutAttr(linux.IFLA_MASTER, primitive.AllocateUint32(i.Master))
}
if i.Kind != "" {
var nestedAttrs nlmsg.NestedAttr
nestedAttrs.PutAttrString(linux.IFLA_INFO_KIND, i.Kind)
m.PutNestedAttr(linux.IFLA_LINKINFO, nestedAttrs)
}
// TODO(gvisor.dev/issue/578): There are many more attributes.
}
// AddNewLinkMessage appends an RTM_NEWLINK message for the given interface into
// the message set.
// AddNewLinkMessage implements netlink.RouteProtocol.AddNewLinkMessage.
func (p *Protocol) AddNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWLINK,
})
writeLinkInfo(m, idx, i)
}
// AddDelLinkMessage appends an RTM_DELLINK message for the given interface into
// the message set.
// AddDelLinkMessage implements netlink.RouteProtocol.AddDelLinkMessage.
func (p *Protocol) AddDelLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_DELLINK,
})
writeLinkInfo(m, idx, i)
}
// dumpAddrs handles RTM_GETADDR dump requests.
func (p *Protocol) dumpAddrs(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
// RTM_GETADDR dump requests need not contain anything more than the
// netlink header and 1 byte protocol family common to all
// NETLINK_ROUTE requests.
//
// TODO(b/68878065): Filter output by passed protocol family.
// The RTM_GETADDR dump response is a set of RTM_NEWADDR messages each
// containing an InterfaceAddrMessage followed by a set of netlink
// attributes.
// We always send back an NLMSG_DONE.
ms.Multi = true
stack := s.Stack()
if stack == nil {
// No network devices.
return nil
}
ifAddrs := stack.InterfaceAddrs()
interfaces := stack.Interfaces()
for _, id := range stack.InterfaceIDs() {
iface := interfaces[id]
scope := linux.RT_SCOPE_UNIVERSE
if (iface.Flags & linux.IFF_LOOPBACK) != 0 {
scope = linux.RT_SCOPE_HOST
}
for _, a := range ifAddrs[id] {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWADDR,
})
m.Put(&linux.InterfaceAddrMessage{
Family: a.Family,
PrefixLen: a.PrefixLen,
Scope: uint8(scope),
Index: uint32(id),
})
addr := primitive.ByteSlice([]byte(a.Addr))
m.PutAttr(linux.IFA_LOCAL, &addr)
m.PutAttr(linux.IFA_ADDRESS, &addr)
if iface.Name != "" {
m.PutAttrString(linux.IFA_LABEL, iface.Name)
}
// TODO(gvisor.dev/issue/578): There are many more attributes.
}
}
return nil
}
// commonPrefixLen reports the length of the longest IP address prefix.
// This is a simplified version from Golang's src/net/addrselect.go.
func commonPrefixLen(a, b []byte) (cpl int) {
for len(a) > 0 {
if a[0] == b[0] {
cpl += 8
a = a[1:]
b = b[1:]
continue
}
bits := 8
ab, bb := a[0], b[0]
for {
ab >>= 1
bb >>= 1
bits--
if ab == bb {
cpl += bits
return
}
}
}
return
}
// fillRoute returns the Route using LPM algorithm. Refer to Linux's
// net/ipv4/route.c:rt_fill_info().
func fillRoute(routes []inet.Route, addr []byte) (inet.Route, *syserr.Error) {
family := uint8(linux.AF_INET)
if len(addr) != 4 {
family = linux.AF_INET6
}
idx := -1 // Index of the Route rule to be returned.
idxDef := -1 // Index of the default route rule.
prefix := 0 // Current longest prefix.
for i, route := range routes {
if route.Family != family {
continue
}
if len(route.GatewayAddr) > 0 && route.DstLen == 0 {
idxDef = i
continue
}
cpl := commonPrefixLen(addr, route.DstAddr)
if cpl < int(route.DstLen) {
continue
}
cpl = int(route.DstLen)
if cpl > prefix {
idx = i
prefix = cpl
}
}
if idx == -1 {
idx = idxDef
}
if FallbackNonHostRoutes && idx == -1 {
for i, route := range routes {
if route.Family == family && route.Scope != linux.RT_SCOPE_HOST {
idx = i
break
}
}
if idx == -1 && len(routes) > 0 {
idx = 0
}
}
if idx == -1 {
if FallbackNonHostRoutes {
return inet.Route{}, syserr.ErrHostUnreachable
}
return inet.Route{}, syserr.ErrNetworkUnreachable
}
route := routes[idx]
if family == linux.AF_INET {
route.DstLen = 32
} else {
route.DstLen = 128
}
route.DstAddr = addr
route.Flags |= linux.RTM_F_CLONED // This route is cloned.
return route, nil
}
// parseForDestination parses a message as format of RouteMessage-RtAttr-dst.
func parseForDestination(msg *nlmsg.Message) ([]byte, *syserr.Error) {
var rtMsg linux.RouteMessage
attrs, ok := msg.GetData(&rtMsg)
if !ok {
return nil, syserr.ErrInvalidArgument
}
for !attrs.Empty() {
hdr, value, rest, ok := attrs.ParseFirst()
if !ok {
break
}
attrs = rest
if hdr.Type == linux.RTA_DST {
return value, nil
}
}
return nil, syserr.ErrInvalidArgument
}
// newRoute handles RTM_NEWROUTE requests.
func (p *Protocol) newRoute(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network routes.
return syserr.ErrProtocolNotSupported
}
if msg.Header().Flags&linux.NLM_F_REQUEST != linux.NLM_F_REQUEST {
return syserr.ErrProtocolNotSupported
}
return stack.NewRoute(ctx, msg)
}
// deleteRoute handles RTM_DELROUTE requests.
func (p *Protocol) deleteRoute(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
return syserr.ErrNoNet
}
if msg.Header().Flags&linux.NLM_F_REQUEST != linux.NLM_F_REQUEST {
return syserr.ErrProtocolNotSupported
}
return stack.RemoveRoute(ctx, msg)
}
// dumpRoutes handles RTM_GETROUTE requests.
func (p *Protocol) dumpRoutes(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
// RTM_GETROUTE dump requests need not contain anything more than the
// netlink header and 1 byte protocol family common to all
// NETLINK_ROUTE requests.
stack := s.Stack()
if stack == nil {
// No network routes.
return nil
}
hdr := msg.Header()
routeTables := stack.RouteTable()
if hdr.Flags&(linux.NLM_F_DUMP|linux.NLM_F_ROOT) != 0 {
// We always send back an NLMSG_DONE.
ms.Multi = true
} else if hdr.Flags&linux.NLM_F_REQUEST != 0 {
dst, err := parseForDestination(msg)
if err != nil {
return err
}
route, err := fillRoute(routeTables, dst)
if FallbackNonHostRoutes && err != nil {
for _, id := range stack.InterfaceIDs() {
iface := stack.Interfaces()[id]
if (iface.Flags & linux.IFF_LOOPBACK) == 0 {
for _, a := range stack.InterfaceAddrs()[id] {
family := linux.AF_INET
if len(dst) == 16 {
family = linux.AF_INET6
}
if a.Family == uint8(family) {
route = inet.Route{
Family: uint8(family),
OutputInterface: id,
Scope: linux.RT_SCOPE_UNIVERSE,
Type: linux.RTN_UNICAST,
Protocol: linux.RTPROT_BOOT,
Table: linux.RT_TABLE_MAIN,
DstAddr: dst,
DstLen: uint8(len(dst) * 8),
Flags: linux.RTM_F_CLONED,
}
err = nil
break
}
}
if err == nil {
break
}
}
}
}
if err != nil {
if FallbackNonHostRoutes {
return syserr.ErrHostUnreachable
}
return err
}
routeTables = append([]inet.Route{}, route)
} else {
// TODO(b/68878065): Only above cases are supported.
return syserr.ErrNotSupported
}
for _, rt := range routeTables {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWROUTE,
})
m.Put(&linux.RouteMessage{
Family: rt.Family,
DstLen: rt.DstLen,
SrcLen: rt.SrcLen,
TOS: rt.TOS,
// Always return the main table since we don't have multiple
// routing tables.
Table: linux.RT_TABLE_MAIN,
Protocol: rt.Protocol,
Scope: rt.Scope,
Type: rt.Type,
Flags: rt.Flags,
})
if rt.DstLen > 0 {
m.PutAttr(linux.RTA_DST, primitive.AsByteSlice(rt.DstAddr))
}
if rt.SrcLen > 0 {
m.PutAttr(linux.RTA_SRC, primitive.AsByteSlice(rt.SrcAddr))
}
if rt.OutputInterface != 0 {
m.PutAttr(linux.RTA_OIF, primitive.AllocateInt32(rt.OutputInterface))
if !ms.Multi || (rt.Flags&linux.RTM_F_CLONED) != 0 {
for _, a := range stack.InterfaceAddrs()[rt.OutputInterface] {
if a.Family == rt.Family {
m.PutAttr(linux.RTA_PREFSRC, primitive.AsByteSlice(a.Addr))
break
}
}
}
}
if len(rt.GatewayAddr) > 0 {
m.PutAttr(linux.RTA_GATEWAY, primitive.AsByteSlice(rt.GatewayAddr))
}
// TODO(gvisor.dev/issue/578): There are many more attributes.
}
return nil
}
// newAddr handles RTM_NEWADDR requests.
func (p *Protocol) newAddr(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
var ifa linux.InterfaceAddrMessage
attrs, ok := msg.GetData(&ifa)
if !ok {
return syserr.ErrInvalidArgument
}
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
// NOTE: A netlink message will contain multiple header attributes.
// Both the IFA_ADDRESS and IFA_LOCAL attributes are typically sent
// with IFA_ADDRESS being a prefix address and IFA_LOCAL being the
// local interface address. We add the local interface address here
// and ignore the IFA_ADDRESS.
switch ahdr.Type {
case linux.IFA_LOCAL:
err := stack.AddInterfaceAddr(int32(ifa.Index), inet.InterfaceAddr{
Family: ifa.Family,
PrefixLen: ifa.PrefixLen,
Flags: ifa.Flags,
Addr: value,
})
if linuxerr.Equals(linuxerr.EEXIST, err) {
flags := msg.Header().Flags
if flags&linux.NLM_F_EXCL != 0 {
return syserr.ErrExists
}
} else if err != nil {
return syserr.ErrInvalidArgument
}
case linux.IFA_ADDRESS:
case linux.IFA_BROADCAST:
// TODO(b/340929168): support IFA_BROADCAST. The standard
// broadcast address (the last IP address of the subnet) is
// used by default.
default:
ctx.Warningf("Unknown attribute: %v", ahdr.Type)
return syserr.ErrNotSupported
}
}
return nil
}
// delAddr handles RTM_DELADDR requests.
func (p *Protocol) delAddr(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
stack := s.Stack()
if stack == nil {
// No network stack.
return syserr.ErrProtocolNotSupported
}
var ifa linux.InterfaceAddrMessage
attrs, ok := msg.GetData(&ifa)
if !ok {
return syserr.ErrInvalidArgument
}
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return syserr.ErrInvalidArgument
}
attrs = rest
// NOTE: A netlink message will contain multiple header attributes.
// Both the IFA_ADDRESS and IFA_LOCAL attributes are typically sent
// with IFA_ADDRESS being a prefix address and IFA_LOCAL being the
// local interface address. We use the local interface address to
// remove the address and ignore the IFA_ADDRESS.
switch ahdr.Type {
case linux.IFA_LOCAL:
err := stack.RemoveInterfaceAddr(int32(ifa.Index), inet.InterfaceAddr{
Family: ifa.Family,
PrefixLen: ifa.PrefixLen,
Flags: ifa.Flags,
Addr: value,
})
if err != nil {
return syserr.ErrBadLocalAddress
}
case linux.IFA_ADDRESS:
default:
return syserr.ErrNotSupported
}
}
return nil
}
// ProcessMessage implements netlink.Protocol.ProcessMessage.
func (p *Protocol) ProcessMessage(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
hdr := msg.Header()
// All messages start with a 1 byte protocol family.
var family primitive.Uint8
if _, ok := msg.GetData(&family); !ok {
// Linux ignores messages missing the protocol family. See
// net/core/rtnetlink.c:rtnetlink_rcv_msg.
return nil
}
// Non-GET message types require CAP_NET_ADMIN.
if typeKind(hdr.Type) != kindGet {
if !s.NetworkNamespace().HasCapability(ctx, linux.CAP_NET_ADMIN) {
return syserr.ErrNotPermittedNet
}
}
if typeKind(hdr.Type) == kindGet && hdr.Flags&(linux.NLM_F_DUMP|linux.NLM_F_ROOT) != 0 {
// TODO(b/68878065): Only the dump variant of the types below are
// supported.
switch hdr.Type {
case linux.RTM_GETLINK:
return p.dumpLinks(ctx, s, msg, ms)
case linux.RTM_GETADDR:
return p.dumpAddrs(ctx, s, msg, ms)
case linux.RTM_GETROUTE:
return p.dumpRoutes(ctx, s, msg, ms)
default:
return syserr.ErrNotSupported
}
} else if hdr.Flags&linux.NLM_F_REQUEST != 0 {
switch hdr.Type {
case linux.RTM_NEWLINK:
return p.newLink(ctx, s, msg, ms)
case linux.RTM_GETLINK:
return p.getLink(ctx, s, msg, ms)
case linux.RTM_DELLINK:
return p.delLink(ctx, s, msg, ms)
case linux.RTM_SETLINK:
// RTM_NEWLINK is backward compatible to RTM_SETLINK.
return p.setLink(ctx, s, msg, ms)
case linux.RTM_NEWROUTE:
return p.newRoute(ctx, s, msg, ms)
case linux.RTM_GETROUTE:
return p.dumpRoutes(ctx, s, msg, ms)
case linux.RTM_DELROUTE:
return p.deleteRoute(ctx, s, msg, ms)
case linux.RTM_GETADDR:
return p.dumpAddrs(ctx, s, msg, ms)
case linux.RTM_NEWADDR:
return p.newAddr(ctx, s, msg, ms)
case linux.RTM_DELADDR:
return p.delAddr(ctx, s, msg, ms)
default:
return syserr.ErrNotSupported
}
}
return syserr.ErrNotSupported
}
// init registers the NETLINK_ROUTE provider.
func init() {
netlink.RegisterProvider(linux.NETLINK_ROUTE, NewProtocol)
}