blob: b76e37c50a01173ef4c153ecf46a471251410b82 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun56a7ae62020-10-29 11:03:30 +01002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun56a7ae62020-10-29 11:03:30 +01003
4package transport
5
6import (
7 "errors"
8 "fmt"
9 "math"
10 "net"
11 "os"
Lorenz Brun56a7ae62020-10-29 11:03:30 +010012 "time"
13
14 "github.com/insomniacslk/dhcp/dhcpv4"
15 "golang.org/x/sys/unix"
16)
17
Serge Bazanski216fe7b2021-05-21 18:36:16 +020018// UnicastTransport implements a DHCP transport based on a normal Linux UDP
19// socket with some custom socket options to influence DSCP and routing.
Lorenz Brun56a7ae62020-10-29 11:03:30 +010020type UnicastTransport struct {
21 udpConn *net.UDPConn
22 targetIP net.IP
23 iface *net.Interface
24}
25
26func NewUnicastTransport(iface *net.Interface) *UnicastTransport {
27 return &UnicastTransport{
28 iface: iface,
29 }
30}
31
32func (t *UnicastTransport) Open(serverIP, bindIP net.IP) error {
33 if t.udpConn != nil {
34 return errors.New("unicast transport already open")
35 }
36 rawFd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
37 if err != nil {
38 return fmt.Errorf("failed to get socket: %w", err)
39 }
40 if err := unix.BindToDevice(rawFd, t.iface.Name); err != nil {
41 return fmt.Errorf("failed to bind UDP interface to device: %w", err)
42 }
43 if err := unix.SetsockoptByte(rawFd, unix.SOL_IP, unix.IP_TOS, dscpCS7<<2); err != nil {
44 return fmt.Errorf("failed to set DSCP CS7: %w", err)
45 }
46 var addr [4]byte
47 copy(addr[:], bindIP.To4())
48 if err := unix.Bind(rawFd, &unix.SockaddrInet4{Addr: addr, Port: 68}); err != nil {
49 return fmt.Errorf("failed to bind UDP unicast interface: %w", err)
50 }
51 filePtr := os.NewFile(uintptr(rawFd), "dhcp-udp")
52 defer filePtr.Close()
53 conn, err := net.FileConn(filePtr)
54 if err != nil {
55 return fmt.Errorf("failed to initialize runtime-supported UDP connection: %w", err)
56 }
57 realConn, ok := conn.(*net.UDPConn)
58 if !ok {
59 panic("UDP socket imported into Go runtime is no longer a UDP socket")
60 }
61 t.udpConn = realConn
62 t.targetIP = serverIP
63 return nil
64}
65
66func (t *UnicastTransport) Send(payload *dhcpv4.DHCPv4) error {
67 if t.udpConn == nil {
68 return errors.New("unicast transport closed")
69 }
70 _, _, err := t.udpConn.WriteMsgUDP(payload.ToBytes(), []byte{}, &net.UDPAddr{
71 IP: t.targetIP,
72 Port: 67,
73 })
74 return err
75}
76
77func (t *UnicastTransport) SetReceiveDeadline(deadline time.Time) error {
78 return t.udpConn.SetReadDeadline(deadline)
79}
80
81func (t *UnicastTransport) Receive() (*dhcpv4.DHCPv4, error) {
82 if t.udpConn == nil {
83 return nil, errors.New("unicast transport closed")
84 }
85 receiveBuf := make([]byte, math.MaxUint16)
86 _, _, err := t.udpConn.ReadFromUDP(receiveBuf)
87 if err != nil {
88 return nil, deadlineFromTimeout(err)
89 }
90 msg, err := dhcpv4.FromBytes(receiveBuf)
91 if err != nil {
92 return nil, NewInvalidMessageError(err)
93 }
94 return msg, nil
95}
96
97func (t *UnicastTransport) Close() error {
98 if t.udpConn == nil {
99 return nil
100 }
101 err := t.udpConn.Close()
102 t.udpConn = nil
Lorenz Brun07e7f0d2024-04-22 10:38:47 +0000103 if err != nil && errors.Is(err, net.ErrClosed) {
Tim Windelschmidt6e5b8a52024-04-17 02:34:07 +0200104 //nolint:returnerrcheck
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100105 return nil
106 }
107 return err
108}