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()) }