
The dhcp server is systemd-networkd, and the dhcp plugin can request an ip but can not renew it. The systemd-networkd just ignore the renew request. ``` 2024/09/14 21:46:00 no DHCP packet received within 10s 2024/09/14 21:46:00 retrying in 31.529038 seconds 2024/09/14 21:46:42 no DHCP packet received within 10s 2024/09/14 21:46:42 retrying in 63.150490 seconds 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: no more tries 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: renewal time expired, rebinding 2024/09/14 21:47:45 Link "eth1" down. Attempting to set up 2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: lease rebound, expiration is 2024-09-14 22:47:45.309270751 +0800 CST m=+11730.048516519 ``` Follow the https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.6, following options must not be sent in renew - Requested IP Address - Server Identifier Since the upstream code has been inactive for 6 years, we should switch to another dhcpv4 library. The new selected one is https://github.com/insomniacslk/dhcp. Signed-off-by: Songmin Li <lisongmin@protonmail.com>
249 lines
6.6 KiB
Go
249 lines
6.6 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package packet
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
|
|
"github.com/josharian/native"
|
|
"github.com/mdlayher/socket"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// A conn is the net.PacketConn implementation for packet sockets. We can use
|
|
// socket.Conn directly on Linux to implement most of the necessary methods.
|
|
type conn = socket.Conn
|
|
|
|
// readFrom implements the net.PacketConn ReadFrom method using recvfrom(2).
|
|
func (c *Conn) readFrom(b []byte) (int, net.Addr, error) {
|
|
// From net.PacketConn documentation:
|
|
//
|
|
// "[ReadFrom] returns the number of bytes read (0 <= n <= len(p)) and any
|
|
// error encountered. Callers should always process the n > 0 bytes returned
|
|
// before considering the error err."
|
|
//
|
|
// c.opError will return nil if no error, but either way we return all the
|
|
// information that we have.
|
|
n, sa, err := c.c.Recvfrom(context.Background(), b, 0)
|
|
return n, fromSockaddr(sa), c.opError(opRead, err)
|
|
}
|
|
|
|
// writeTo implements the net.PacketConn WriteTo method.
|
|
func (c *Conn) writeTo(b []byte, addr net.Addr) (int, error) {
|
|
sa, err := c.toSockaddr("sendto", addr)
|
|
if err != nil {
|
|
return 0, c.opError(opWrite, err)
|
|
}
|
|
|
|
// TODO(mdlayher): it's curious that unix.Sendto does not return the number
|
|
// of bytes actually sent. Fake it for now, but investigate upstream.
|
|
if err := c.c.Sendto(context.Background(), b, 0, sa); err != nil {
|
|
return 0, c.opError(opWrite, err)
|
|
}
|
|
|
|
return len(b), nil
|
|
}
|
|
|
|
// setPromiscuous wraps setsockopt(2) for the unix.PACKET_MR_PROMISC option.
|
|
func (c *Conn) setPromiscuous(enable bool) error {
|
|
mreq := unix.PacketMreq{
|
|
Ifindex: int32(c.ifIndex),
|
|
Type: unix.PACKET_MR_PROMISC,
|
|
}
|
|
|
|
membership := unix.PACKET_DROP_MEMBERSHIP
|
|
if enable {
|
|
membership = unix.PACKET_ADD_MEMBERSHIP
|
|
}
|
|
|
|
return c.opError(
|
|
opSetsockopt,
|
|
c.c.SetsockoptPacketMreq(unix.SOL_PACKET, membership, &mreq),
|
|
)
|
|
}
|
|
|
|
// stats wraps getsockopt(2) for tpacket_stats* types.
|
|
func (c *Conn) stats() (*Stats, error) {
|
|
const (
|
|
level = unix.SOL_PACKET
|
|
name = unix.PACKET_STATISTICS
|
|
)
|
|
|
|
// Try to fetch V3 statistics first, they contain more detailed information.
|
|
if stats, err := c.c.GetsockoptTpacketStatsV3(level, name); err == nil {
|
|
return &Stats{
|
|
Packets: stats.Packets,
|
|
Drops: stats.Drops,
|
|
FreezeQueueCount: stats.Freeze_q_cnt,
|
|
}, nil
|
|
}
|
|
|
|
// There was an error fetching v3 stats, try to fall back.
|
|
stats, err := c.c.GetsockoptTpacketStats(level, name)
|
|
if err != nil {
|
|
return nil, c.opError(opGetsockopt, err)
|
|
}
|
|
|
|
return &Stats{
|
|
Packets: stats.Packets,
|
|
Drops: stats.Drops,
|
|
// FreezeQueueCount is not present.
|
|
}, nil
|
|
}
|
|
|
|
// listen is the entry point for Listen on Linux.
|
|
func listen(ifi *net.Interface, socketType Type, protocol int, cfg *Config) (*Conn, error) {
|
|
if cfg == nil {
|
|
// Default configuration.
|
|
cfg = &Config{}
|
|
}
|
|
|
|
// Convert Type to the matching SOCK_* constant.
|
|
var typ int
|
|
switch socketType {
|
|
case Raw:
|
|
typ = unix.SOCK_RAW
|
|
case Datagram:
|
|
typ = unix.SOCK_DGRAM
|
|
default:
|
|
return nil, errors.New("packet: invalid Type value")
|
|
}
|
|
|
|
// Protocol is intentionally zero in call to socket(2); we can set it on
|
|
// bind(2) instead. Package raw notes: "Do not specify a protocol to avoid
|
|
// capturing packets which to not match cfg.Filter."
|
|
c, err := socket.Socket(unix.AF_PACKET, typ, 0, network, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := bind(c, ifi.Index, protocol, cfg)
|
|
if err != nil {
|
|
_ = c.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
// bind binds the *socket.Conn to finalize *Conn setup.
|
|
func bind(c *socket.Conn, ifIndex, protocol int, cfg *Config) (*Conn, error) {
|
|
if len(cfg.Filter) > 0 {
|
|
// The caller wants to apply a BPF filter before bind(2).
|
|
if err := c.SetBPF(cfg.Filter); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// packet(7) says we sll_protocol must be in network byte order.
|
|
pnet, err := htons(protocol)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(mdlayher): investigate the possibility of sll_ifindex = 0 because we
|
|
// could bind to any interface.
|
|
err = c.Bind(&unix.SockaddrLinklayer{
|
|
Protocol: pnet,
|
|
Ifindex: ifIndex,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lsa, err := c.Getsockname()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse the physical layer address; sll_halen tells us how many bytes of
|
|
// sll_addr we should treat as valid.
|
|
lsall := lsa.(*unix.SockaddrLinklayer)
|
|
addr := make(net.HardwareAddr, lsall.Halen)
|
|
copy(addr, lsall.Addr[:])
|
|
|
|
return &Conn{
|
|
c: c,
|
|
|
|
addr: &Addr{HardwareAddr: addr},
|
|
ifIndex: ifIndex,
|
|
protocol: pnet,
|
|
}, nil
|
|
}
|
|
|
|
// fromSockaddr converts an opaque unix.Sockaddr to *Addr. If sa is nil, it
|
|
// returns nil. It panics if sa is not of type *unix.SockaddrLinklayer.
|
|
func fromSockaddr(sa unix.Sockaddr) *Addr {
|
|
if sa == nil {
|
|
return nil
|
|
}
|
|
|
|
sall := sa.(*unix.SockaddrLinklayer)
|
|
|
|
return &Addr{
|
|
// The syscall already allocated sa; just slice into it with the
|
|
// appropriate length and type conversion rather than making a copy.
|
|
HardwareAddr: net.HardwareAddr(sall.Addr[:sall.Halen]),
|
|
}
|
|
}
|
|
|
|
// toSockaddr converts a net.Addr to an opaque unix.Sockaddr. It returns an
|
|
// error if the fields cannot be packed into a *unix.SockaddrLinklayer.
|
|
func (c *Conn) toSockaddr(
|
|
op string,
|
|
addr net.Addr,
|
|
) (unix.Sockaddr, error) {
|
|
// The typical error convention for net.Conn types is
|
|
// net.OpError(os.SyscallError(syscall.Errno)), so all calls here should
|
|
// return os.SyscallError(syscall.Errno) so the caller can apply the final
|
|
// net.OpError wrapper.
|
|
|
|
// Ensure the correct Addr type.
|
|
a, ok := addr.(*Addr)
|
|
if !ok || a.HardwareAddr == nil {
|
|
return nil, os.NewSyscallError(op, unix.EINVAL)
|
|
}
|
|
|
|
// Pack Addr and Conn metadata into the appropriate sockaddr fields. From
|
|
// packet(7):
|
|
//
|
|
// "When you send packets it is enough to specify sll_family, sll_addr,
|
|
// sll_halen, sll_ifindex, and sll_protocol. The other fields should be 0."
|
|
//
|
|
// sll_family is set on the conversion to unix.RawSockaddrLinklayer.
|
|
sa := unix.SockaddrLinklayer{
|
|
Ifindex: c.ifIndex,
|
|
Protocol: c.protocol,
|
|
}
|
|
|
|
// Ensure the input address does not exceed the amount of space available;
|
|
// for example an IPoIB address is 20 bytes.
|
|
if len(a.HardwareAddr) > len(sa.Addr) {
|
|
return nil, os.NewSyscallError(op, unix.EINVAL)
|
|
}
|
|
|
|
sa.Halen = uint8(len(a.HardwareAddr))
|
|
copy(sa.Addr[:], a.HardwareAddr)
|
|
|
|
return &sa, nil
|
|
}
|
|
|
|
// htons converts a short (uint16) from host-to-network byte order.
|
|
func htons(i int) (uint16, error) {
|
|
if i < 0 || i > math.MaxUint16 {
|
|
return 0, errors.New("packet: protocol value out of range")
|
|
}
|
|
|
|
// Store as big endian, retrieve as native endian.
|
|
var b [2]byte
|
|
binary.BigEndian.PutUint16(b[:], uint16(i))
|
|
|
|
return native.Endian.Uint16(b[:]), nil
|
|
}
|