Songmin Li d61e7e5e1f fix(dhcp): can not renew an ip address
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>
2024-10-14 17:42:30 +02:00

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
}