blob: 6d051ee656f65c36d1d5fe17d14b28e8ee5764a4 [file] [log] [blame]
Lorenz Brun56a7ae62020-10-29 11:03:30 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package transport
18
19import (
20 "errors"
21 "fmt"
22 "math"
23 "net"
24 "time"
25
26 "github.com/google/gopacket"
27 "github.com/google/gopacket/layers"
28 "github.com/insomniacslk/dhcp/dhcpv4"
29 "github.com/mdlayher/raw"
30 "golang.org/x/net/bpf"
31)
32
33const (
Serge Bazanski216fe7b2021-05-21 18:36:16 +020034 // RFC2474 Section 4.2.2.1 with reference to RFC791 Section 3.1 (Network
35 // Control Precedence)
Lorenz Brun56a7ae62020-10-29 11:03:30 +010036 dscpCS7 = 0x7 << 3
37
38 // IPv4 MTU
39 maxIPv4MTU = math.MaxUint16 // IPv4 "Total Length" field is an unsigned 16 bit integer
40)
41
42// mustAssemble calls bpf.Assemble and panics if it retuns an error.
43func mustAssemble(insns []bpf.Instruction) []bpf.RawInstruction {
44 rawInsns, err := bpf.Assemble(insns)
45 if err != nil {
46 panic("mustAssemble failed to assemble BPF: " + err.Error())
47 }
48 return rawInsns
49}
50
51// BPF filter for UDP in IPv4 with destination port 68 (DHCP Client)
52//
Serge Bazanski216fe7b2021-05-21 18:36:16 +020053// This is used to make the kernel drop non-DHCP traffic for us so that we
54// don't have to handle excessive unrelated traffic flowing on a given link
55// which might overwhelm the single-threaded receiver.
Lorenz Brun56a7ae62020-10-29 11:03:30 +010056var bpfFilterInstructions = []bpf.Instruction{
57 // Check IP protocol version equals 4 (first 4 bits of the first byte)
Serge Bazanski216fe7b2021-05-21 18:36:16 +020058 // With Ethernet II framing, this is more of a sanity check. We already
59 // request the kernel to only return EtherType 0x0800 (IPv4) frames.
Lorenz Brun56a7ae62020-10-29 11:03:30 +010060 bpf.LoadAbsolute{Off: 0, Size: 1},
61 bpf.ALUOpConstant{Op: bpf.ALUOpAnd, Val: 0xf0}, // SubnetMask second 4 bits
62 bpf.JumpIf{Cond: bpf.JumpEqual, Val: 4 << 4, SkipTrue: 1},
63 bpf.RetConstant{Val: 0}, // Discard
64
65 // Check IPv4 Protocol byte (offset 9) equals UDP
66 bpf.LoadAbsolute{Off: 9, Size: 1},
67 bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(layers.IPProtocolUDP), SkipTrue: 1},
68 bpf.RetConstant{Val: 0}, // Discard
69
70 // Check if IPv4 fragment offset is all-zero (this is not a fragment)
71 bpf.LoadAbsolute{Off: 6, Size: 2},
72 bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipFalse: 1},
73 bpf.RetConstant{Val: 0}, // Discard
74
75 // Load IPv4 header size from offset zero and store it into X
76 bpf.LoadMemShift{Off: 0},
77
78 // Check if UDP header destination port equals 68
79 bpf.LoadIndirect{Off: 2, Size: 2}, // Offset relative to header size in register X
80 bpf.JumpIf{Cond: bpf.JumpEqual, Val: 68, SkipTrue: 1},
81 bpf.RetConstant{Val: 0}, // Discard
82
83 // Accept packet and pass through up maximum IP packet size
84 bpf.RetConstant{Val: maxIPv4MTU},
85}
86
87var bpfFilter = mustAssemble(bpfFilterInstructions)
88
Serge Bazanski216fe7b2021-05-21 18:36:16 +020089// BroadcastTransport implements a DHCP transport based on a custom IP/UDP
90// stack fulfilling the specific requirements for broadcasting DHCP packets
91// (like all-zero source address, no ARP, ...)
Lorenz Brun56a7ae62020-10-29 11:03:30 +010092type BroadcastTransport struct {
93 rawConn *raw.Conn
94 iface *net.Interface
95}
96
97func NewBroadcastTransport(iface *net.Interface) *BroadcastTransport {
98 return &BroadcastTransport{iface: iface}
99}
100
101func (t *BroadcastTransport) Open() error {
102 if t.rawConn != nil {
103 return errors.New("broadcast transport already open")
104 }
105 rawConn, err := raw.ListenPacket(t.iface, uint16(layers.EthernetTypeIPv4), &raw.Config{
106 LinuxSockDGRAM: true,
107 Filter: bpfFilter,
108 })
109 if err != nil {
110 return fmt.Errorf("failed to create raw listener: %w", err)
111 }
112 t.rawConn = rawConn
113 return nil
114}
115
116func (t *BroadcastTransport) Send(payload *dhcpv4.DHCPv4) error {
117 if t.rawConn == nil {
118 return errors.New("broadcast transport closed")
119 }
120 packet := gopacket.NewSerializeBuffer()
121 opts := gopacket.SerializeOptions{
122 ComputeChecksums: true,
123 FixLengths: true,
124 }
125
126 ipLayer := &layers.IPv4{
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200127 Version: 4,
128 // Shift left of ECN field
129 TOS: dscpCS7 << 2,
130 // These packets should never be routed (their IP headers contain
131 // garbage)
132 TTL: 1,
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100133 Protocol: layers.IPProtocolUDP,
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200134 // Most DHCP servers don't support fragmented packets.
135 Flags: layers.IPv4DontFragment,
136 DstIP: net.IPv4bcast,
137 SrcIP: net.IPv4zero,
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100138 }
139 udpLayer := &layers.UDP{
140 DstPort: 67,
141 SrcPort: 68,
142 }
143 if err := udpLayer.SetNetworkLayerForChecksum(ipLayer); err != nil {
144 panic("Invalid layer stackup encountered")
145 }
146
147 err := gopacket.SerializeLayers(packet, opts,
148 ipLayer,
149 udpLayer,
150 gopacket.Payload(payload.ToBytes()))
151
152 if err != nil {
153 return fmt.Errorf("failed to assemble packet: %w", err)
154 }
155
156 _, err = t.rawConn.WriteTo(packet.Bytes(), &raw.Addr{HardwareAddr: layers.EthernetBroadcast})
157 if err != nil {
158 return fmt.Errorf("failed to transmit broadcast packet: %w", err)
159 }
160 return nil
161}
162
163func (t *BroadcastTransport) Receive() (*dhcpv4.DHCPv4, error) {
164 if t.rawConn == nil {
165 return nil, errors.New("broadcast transport closed")
166 }
167 buf := make([]byte, math.MaxUint16) // Maximum IP packet size
168 n, _, err := t.rawConn.ReadFrom(buf)
169 if err != nil {
170 return nil, deadlineFromTimeout(err)
171 }
172 respPacket := gopacket.NewPacket(buf[:n], layers.LayerTypeIPv4, gopacket.Default)
173 ipLayer := respPacket.Layer(layers.LayerTypeIPv4)
174 if ipLayer == nil {
175 return nil, NewInvalidMessageError(errors.New("got invalid IP packet"))
176 }
177 ip := ipLayer.(*layers.IPv4)
178 if ip.Flags&layers.IPv4MoreFragments != 0 {
179 return nil, NewInvalidMessageError(errors.New("got fragmented message"))
180 }
181
182 udpLayer := respPacket.Layer(layers.LayerTypeUDP)
183 if udpLayer == nil {
184 return nil, NewInvalidMessageError(errors.New("got non-UDP packet"))
185 }
186 udp := udpLayer.(*layers.UDP)
187 if udp.DstPort != 68 {
188 return nil, NewInvalidMessageError(errors.New("message not for DHCP client port"))
189 }
190 msg, err := dhcpv4.FromBytes(udp.Payload)
191 if err != nil {
192 return nil, NewInvalidMessageError(fmt.Errorf("failed to decode DHCPv4 message: %w", err))
193 }
194 return msg, nil
195}
196
197func (t *BroadcastTransport) Close() error {
198 if t.rawConn == nil {
199 return nil
200 }
201 if err := t.rawConn.Close(); err != nil {
202 return err
203 }
204 t.rawConn = nil
205 return nil
206}
207
208func (t *BroadcastTransport) SetReceiveDeadline(deadline time.Time) error {
209 if t.rawConn == nil {
210 return errors.New("broadcast transport closed")
211 }
212 return t.rawConn.SetReadDeadline(deadline)
213}