All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m52s
Test / ShareFS (push) Successful in 4m47s
Test / Hpkg (push) Successful in 5m10s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (push) Successful in 5m48s
Test / Hakurei (race detector) (push) Successful in 7m39s
Test / Flake checks (push) Successful in 1m42s
This enables loopback networking when owning the net namespace. Signed-off-by: Ophestra <cat@gensokyo.uk>
270 lines
6.3 KiB
Go
270 lines
6.3 KiB
Go
package container
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
. "syscall"
|
|
"unsafe"
|
|
|
|
"hakurei.app/container/std"
|
|
"hakurei.app/message"
|
|
)
|
|
|
|
// rtnetlink represents a NETLINK_ROUTE socket.
|
|
type rtnetlink struct {
|
|
// Sent as part of rtnetlink messages.
|
|
pid uint32
|
|
// AF_NETLINK socket.
|
|
fd int
|
|
// Whether the socket is open.
|
|
ok bool
|
|
// Message sequence number.
|
|
seq uint32
|
|
}
|
|
|
|
// open creates the underlying NETLINK_ROUTE socket.
|
|
func (s *rtnetlink) open() (err error) {
|
|
if s.ok || s.fd < 0 {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
s.pid = uint32(Getpid())
|
|
if s.fd, err = Socket(
|
|
AF_NETLINK,
|
|
SOCK_RAW|SOCK_CLOEXEC,
|
|
NETLINK_ROUTE,
|
|
); err != nil {
|
|
return os.NewSyscallError("socket", err)
|
|
} else if err = Bind(s.fd, &SockaddrNetlink{
|
|
Family: AF_NETLINK,
|
|
Pid: s.pid,
|
|
}); err != nil {
|
|
_ = s.close()
|
|
return os.NewSyscallError("bind", err)
|
|
} else {
|
|
s.ok = true
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// close closes the underlying NETLINK_ROUTE socket.
|
|
func (s *rtnetlink) close() error {
|
|
if !s.ok {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
s.ok = false
|
|
err := Close(s.fd)
|
|
s.fd = -1
|
|
return err
|
|
}
|
|
|
|
// roundtrip sends a netlink message and handles the reply.
|
|
func (s *rtnetlink) roundtrip(data []byte) error {
|
|
if !s.ok {
|
|
return os.ErrInvalid
|
|
}
|
|
|
|
defer func() { s.seq++ }()
|
|
|
|
if err := Sendto(s.fd, data, 0, &SockaddrNetlink{
|
|
Family: AF_NETLINK,
|
|
}); err != nil {
|
|
return os.NewSyscallError("sendto", err)
|
|
}
|
|
buf := make([]byte, Getpagesize())
|
|
|
|
done:
|
|
for {
|
|
p := buf
|
|
if n, _, err := Recvfrom(s.fd, p, 0); err != nil {
|
|
return os.NewSyscallError("recvfrom", err)
|
|
} else if n < NLMSG_HDRLEN {
|
|
return errors.ErrUnsupported
|
|
} else {
|
|
p = p[:n]
|
|
}
|
|
|
|
if msgs, err := ParseNetlinkMessage(p); err != nil {
|
|
return err
|
|
} else {
|
|
for _, m := range msgs {
|
|
if m.Header.Seq != s.seq || m.Header.Pid != s.pid {
|
|
return errors.ErrUnsupported
|
|
}
|
|
if m.Header.Type == NLMSG_DONE {
|
|
break done
|
|
}
|
|
if m.Header.Type == NLMSG_ERROR {
|
|
if len(m.Data) >= 4 {
|
|
errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data)))
|
|
if errno == 0 {
|
|
return nil
|
|
}
|
|
return errno
|
|
}
|
|
return errors.ErrUnsupported
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// mustRoundtrip calls roundtrip and terminates via msg for a non-nil error.
|
|
func (s *rtnetlink) mustRoundtrip(msg message.Msg, data []byte) {
|
|
err := s.roundtrip(data)
|
|
if err == nil {
|
|
return
|
|
}
|
|
if closeErr := Close(s.fd); closeErr != nil {
|
|
msg.Verbosef("cannot close: %v", err)
|
|
}
|
|
|
|
switch err.(type) {
|
|
case *os.SyscallError:
|
|
msg.GetLogger().Fatalf("cannot %v", err)
|
|
|
|
case Errno:
|
|
msg.GetLogger().Fatalf("RTNETLINK answers: %v", err)
|
|
|
|
default:
|
|
msg.GetLogger().Fatalln("RTNETLINK answers with unexpected message")
|
|
}
|
|
}
|
|
|
|
// newaddrLo represents a RTM_NEWADDR message with two addresses.
|
|
type newaddrLo struct {
|
|
header NlMsghdr
|
|
data IfAddrmsg
|
|
|
|
r0 RtAttr
|
|
a0 [4]byte // in_addr
|
|
r1 RtAttr
|
|
a1 [4]byte // in_addr
|
|
}
|
|
|
|
// sizeofNewaddrLo is the expected size of newaddrLo.
|
|
const sizeofNewaddrLo = NLMSG_HDRLEN + SizeofIfAddrmsg + (SizeofRtAttr+4)*2
|
|
|
|
// newaddrLo returns the address of a populated newaddrLo.
|
|
func (s *rtnetlink) newaddrLo(lo int) *newaddrLo {
|
|
return &newaddrLo{NlMsghdr{
|
|
Len: sizeofNewaddrLo,
|
|
Type: RTM_NEWADDR,
|
|
Flags: NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL,
|
|
Seq: s.seq,
|
|
Pid: s.pid,
|
|
}, IfAddrmsg{
|
|
Family: AF_INET,
|
|
Prefixlen: 8,
|
|
Flags: IFA_F_PERMANENT,
|
|
Scope: RT_SCOPE_HOST,
|
|
Index: uint32(lo),
|
|
}, RtAttr{
|
|
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a0)),
|
|
Type: IFA_LOCAL,
|
|
}, [4]byte{127, 0, 0, 1}, RtAttr{
|
|
Len: uint16(SizeofRtAttr + len(newaddrLo{}.a1)),
|
|
Type: IFA_ADDRESS,
|
|
}, [4]byte{127, 0, 0, 1}}
|
|
}
|
|
|
|
func (msg *newaddrLo) toWireFormat() []byte {
|
|
var buf [sizeofNewaddrLo]byte
|
|
|
|
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
|
|
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
|
|
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
|
|
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
|
|
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
|
|
|
|
buf[16] = msg.data.Family
|
|
buf[17] = msg.data.Prefixlen
|
|
buf[18] = msg.data.Flags
|
|
buf[19] = msg.data.Scope
|
|
*(*uint32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
|
|
|
|
*(*uint16)(unsafe.Pointer(&buf[24:26][0])) = msg.r0.Len
|
|
*(*uint16)(unsafe.Pointer(&buf[26:28][0])) = msg.r0.Type
|
|
copy(buf[28:32], msg.a0[:])
|
|
*(*uint16)(unsafe.Pointer(&buf[32:34][0])) = msg.r1.Len
|
|
*(*uint16)(unsafe.Pointer(&buf[34:36][0])) = msg.r1.Type
|
|
copy(buf[36:40], msg.a1[:])
|
|
|
|
return buf[:]
|
|
}
|
|
|
|
// newlinkLo represents a RTM_NEWLINK message.
|
|
type newlinkLo struct {
|
|
header NlMsghdr
|
|
data IfInfomsg
|
|
}
|
|
|
|
// sizeofNewlinkLo is the expected size of newlinkLo.
|
|
const sizeofNewlinkLo = NLMSG_HDRLEN + SizeofIfInfomsg
|
|
|
|
// newlinkLo returns the address of a populated newlinkLo.
|
|
func (s *rtnetlink) newlinkLo(lo int) *newlinkLo {
|
|
return &newlinkLo{NlMsghdr{
|
|
Len: sizeofNewlinkLo,
|
|
Type: RTM_NEWLINK,
|
|
Flags: NLM_F_REQUEST | NLM_F_ACK,
|
|
Seq: s.seq,
|
|
Pid: s.pid,
|
|
}, IfInfomsg{
|
|
Family: AF_UNSPEC,
|
|
Index: int32(lo),
|
|
Flags: IFF_UP,
|
|
Change: IFF_UP,
|
|
}}
|
|
}
|
|
|
|
func (msg *newlinkLo) toWireFormat() []byte {
|
|
var buf [sizeofNewlinkLo]byte
|
|
|
|
*(*uint32)(unsafe.Pointer(&buf[0:4][0])) = msg.header.Len
|
|
*(*uint16)(unsafe.Pointer(&buf[4:6][0])) = msg.header.Type
|
|
*(*uint16)(unsafe.Pointer(&buf[6:8][0])) = msg.header.Flags
|
|
*(*uint32)(unsafe.Pointer(&buf[8:12][0])) = msg.header.Seq
|
|
*(*uint32)(unsafe.Pointer(&buf[12:16][0])) = msg.header.Pid
|
|
|
|
buf[16] = msg.data.Family
|
|
*(*uint16)(unsafe.Pointer(&buf[18:20][0])) = msg.data.Type
|
|
*(*int32)(unsafe.Pointer(&buf[20:24][0])) = msg.data.Index
|
|
*(*uint32)(unsafe.Pointer(&buf[24:28][0])) = msg.data.Flags
|
|
*(*uint32)(unsafe.Pointer(&buf[28:32][0])) = msg.data.Change
|
|
|
|
return buf[:]
|
|
}
|
|
|
|
// mustLoopback creates the loopback address and brings the lo interface up.
|
|
// mustLoopback calls a fatal method of the underlying [log.Logger] of m with a
|
|
// user-facing error message if RTNETLINK behaves unexpectedly.
|
|
func mustLoopback(msg message.Msg) {
|
|
log := msg.GetLogger()
|
|
|
|
var lo int
|
|
if ifi, err := net.InterfaceByName("lo"); err != nil {
|
|
log.Fatalln(err)
|
|
} else {
|
|
lo = ifi.Index
|
|
}
|
|
|
|
var s rtnetlink
|
|
if err := s.open(); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
defer func() {
|
|
if err := s.close(); err != nil {
|
|
msg.Verbosef("cannot close netlink: %v", err)
|
|
}
|
|
}()
|
|
|
|
s.mustRoundtrip(msg, s.newaddrLo(lo).toWireFormat())
|
|
s.mustRoundtrip(msg, s.newlinkLo(lo).toWireFormat())
|
|
}
|