Files
cloudpods/pkg/util/dhcp/server.go
T
2025-07-12 12:10:18 +08:00

321 lines
10 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dhcp
import (
"net"
"runtime/debug"
"golang.org/x/net/bpf"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
)
type DHCPServer struct {
Address string
Port int
conn *Conn
}
type DHCP6Server struct {
DHCPServer
}
// udp unicast socket
func NewDHCPServer3(address string, port int) (*DHCPServer, error) {
dhcpConn, err := NewSocketConn(address, port)
if err != nil {
return nil, errors.Wrap(err, "New DHCP connection")
}
return &DHCPServer{
Address: address,
Port: port,
conn: dhcpConn,
}, nil
}
// udp unicast socket
func NewDHCP6Server3(address string, port int) (*DHCP6Server, error) {
dhcpConn, err := NewSocketConn(address, port)
if err != nil {
return nil, errors.Wrap(err, "New DHCP6 connection")
}
return &DHCP6Server{
DHCPServer{
Address: address,
Port: port,
conn: dhcpConn,
},
}, nil
}
const (
bpfContinue uint8 = 0
bpfProcDhcpResp uint8 = 11
bpfProcDhcpResp6 uint8 = 8
bpfProcICMPv6 uint8 = 11
bpfProcReadPkt6 uint8 = 17
bpfProcEnd6 uint8 = 18
bpfProcReadPkt4 uint8 = 14
bpfProcEnd4 uint8 = 15
udpProtocol uint32 = 17
icmpProtocol uint32 = 58
)
var (
v4bpf = []bpf.Instruction{
/* 0 load ethernet type, 2bytes */
&bpf.LoadAbsolute{Off: 12, Size: 2},
/* 1 if ether_type != 0x0800(IPv4) then jump to [end] else continue */
&bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0800, SkipTrue: bpfGoto(2, bpfProcEnd4), SkipFalse: bpfContinue},
/* 2 load ipv4 protocol, offset 23, 1 byte */
&bpf.LoadAbsolute{Off: 23, Size: 1},
/* 3 if ip_proto != UDP then jump to [end] else continue */
&bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: udpProtocol, SkipTrue: bpfGoto(4, bpfProcEnd4), SkipFalse: bpfContinue},
/* 4 if load ip flags & fragment_offset */
&bpf.LoadAbsolute{Off: 20, Size: 2},
/* 5 if ip_fragment_offset != 0 then jump to end/25(6+19) else continue */
bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1fff, SkipTrue: bpfGoto(6, bpfProcEnd4), SkipFalse: bpfContinue},
/* 6 store ip_header_length*4 in register X */
bpf.LoadMemShift{Off: 14},
/* 7 load 14 + ip_header_len + 0 (UDP src port), 2 bytes */
bpf.LoadIndirect{Off: 14, Size: 2},
/* 8 if udp_src_port != 67 then jump to dhcp_resp/11(9+2) else continue */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 67, SkipTrue: bpfGoto(9, bpfProcDhcpResp), SkipFalse: bpfContinue},
/* 9 load 14 + ip_header_len + 2 (udp dst port), 2 bytes */
bpf.LoadIndirect{Off: 16, Size: 2},
/* 10 if udp_dst_port == 68 then jump to read/24(11+13) else jump to end/25(11+14) */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 68, SkipTrue: bpfGoto(11, bpfProcReadPkt4), SkipFalse: bpfGoto(11, bpfProcEnd4)},
/* 11 [dhcp_resp] if udp_src_port != 68 then jump to end/25(12+13) else continue, which means a UDP response */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 68, SkipTrue: bpfGoto(12, bpfProcEnd4), SkipFalse: bpfContinue},
/* 12 load 14 + ip_header_len + 2 (udp dst port), 2 bytes */
bpf.LoadIndirect{Off: 16, Size: 2},
/* 13 if udp_dst_port != 67 then jump to end/25(14+11) else continue */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 67, SkipTrue: bpfGoto(14, bpfProcEnd4), SkipFalse: bpfContinue},
/* 14 [read] return the whole packet */
bpf.RetConstant{Val: 0x40000},
/* 15 [end] return none */
bpf.RetConstant{Val: 0x0},
}
v6bpf = []bpf.Instruction{
/* 0 load ethernet type, 2bytes */
&bpf.LoadAbsolute{Off: 12, Size: 2},
/* 1 [ipv6] if ether_type != 0x86dd(IPv6) then jump to end/25(15 + 10) else continue */
&bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x86dd, SkipTrue: bpfGoto(2, bpfProcEnd6), SkipFalse: bpfContinue},
/* 2 load ipv6 next header, offset 20, 1 byte */
&bpf.LoadAbsolute{Off: 20, Size: 1},
/* 3 [udp6] if ip_proto != UDP then jump to [icmp6]/24(17 + 7) else continue */
&bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: udpProtocol, SkipTrue: bpfGoto(4, bpfProcICMPv6), SkipFalse: bpfContinue},
/* 4 load 14 + 40 + 0 (UDP src port), 2 bytes */
bpf.LoadAbsolute{Off: 54, Size: 2},
/* 5 if udp_src_port != 546 then jump to ipv6_resp/21(19+2) else continue */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 546, SkipTrue: bpfGoto(6, bpfProcDhcpResp), SkipFalse: bpfContinue},
/* 6 load 14 + 40 + 2 (udp dst port), 2 bytes */
bpf.LoadAbsolute{Off: 56, Size: 2},
/* 7 if udp_dst_port == 547 then jump to readPkt6 else jump to end */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 547, SkipTrue: bpfGoto(8, bpfProcReadPkt6), SkipFalse: bpfGoto(8, bpfProcEnd6)},
/* 8 [dhcpv6_resp] if udp_src_port != 547 then jump to end else continue, which means a DHCPv6 response */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 547, SkipTrue: bpfGoto(9, bpfProcEnd6), SkipFalse: bpfContinue},
/* 9 load 14 + 40 + 2 (udp dst port), 2 bytes */
bpf.LoadAbsolute{Off: 56, Size: 2},
/* 10 if udp_dst_port != 546 then jump to end else jump to readPkt6 */
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 546, SkipTrue: bpfGoto(11, bpfProcEnd6), SkipFalse: bpfGoto(11, bpfProcReadPkt6)},
/* 11 [icmp6] if ip_proto != ICMP then jump to [end] else continue */
&bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: icmpProtocol, SkipTrue: bpfGoto(12, bpfProcEnd6), SkipFalse: bpfContinue},
/* 12 [icmp6 type] load 14 + 40 + 0 (ICMPv6 type), 1 bytes */
bpf.LoadAbsolute{Off: 54, Size: 1},
/* 13 [Router Solicitation] if icmp6_type == 133 then jump to [read] else [continue] */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 133, SkipTrue: bpfGoto(14, bpfProcReadPkt6), SkipFalse: bpfContinue},
/* 14 [Router Advertisement] if icmp6_type == 134 then jump to [read] else [continue] */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 134, SkipTrue: bpfGoto(15, bpfProcReadPkt6), SkipFalse: bpfContinue},
/* 15 [Neightbor Solicitation] if icmp6_type == 135 then jump to [read] else [continue] */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 135, SkipTrue: bpfGoto(16, bpfProcReadPkt6), SkipFalse: bpfContinue},
/* 16 [Neighbor Advertisement] if icmp6_type == 136 then jump to [read] else [end] */
bpf.JumpIf{Cond: bpf.JumpEqual, Val: 136, SkipTrue: bpfGoto(17, bpfProcReadPkt6), SkipFalse: bpfGoto(17, bpfProcEnd6)},
/* 17 [read] return the whole packet */
bpf.RetConstant{Val: 0x40000},
/* 18 [end] return none */
bpf.RetConstant{Val: 0x0},
}
)
func bpfGoto(current uint8, jumpTo uint8) uint8 {
return jumpTo - current
}
func getRawInstructions(obpf []bpf.Instruction) ([]bpf.RawInstruction, error) {
bpf := make([]bpf.RawInstruction, 0, len(obpf))
for i := range obpf {
inst, err := obpf[i].Assemble()
if err != nil {
return nil, errors.Wrap(err, "invalid eBPF instruction")
}
bpf = append(bpf, inst)
}
return bpf, nil
}
// raw socket
func NewDHCPServer2(iface string, port uint16) (*DHCPServer, *Conn, error) {
bpf, err := getRawInstructions(v4bpf)
if err != nil {
return nil, nil, errors.Wrap(err, "getRawInstructions")
}
conn, err := NewRawSocketConn(iface, bpf, port)
if err != nil {
return nil, nil, err
}
return &DHCPServer{
conn: conn,
}, conn, nil
}
func NewDHCP6Server2(iface string, port uint16) (*DHCP6Server, *Conn, error) {
bpf, err := getRawInstructions(v6bpf)
if err != nil {
return nil, nil, errors.Wrap(err, "getRawInstructions")
}
conn, err := NewRawSocketConn6(iface, bpf, port)
if err != nil {
return nil, nil, err
}
return &DHCP6Server{
DHCPServer{
conn: conn,
},
}, conn, nil
}
func (s *DHCPServer) ListenAndServe(handler DHCPHandler) error {
defer s.conn.Close()
return s.serveDHCP(handler)
}
func (s *DHCP6Server) ListenAndServe(handler DHCP6Handler) error {
defer s.conn.Close()
return s.serveDHCP(handler)
}
func (s *DHCPServer) GetConn() *Conn {
return s.conn
}
func (s *DHCPServer) serveDHCP(handler DHCPHandler) error {
for {
pkt, addr, mac, err := s.conn.RecvDHCP()
if err != nil {
log.Errorf("Receiving DHCP packet: %s", err)
continue
}
go func() {
defer func() {
if r := recover(); r != nil {
log.Errorf("Serve panic error: %v", r)
debug.PrintStack()
}
}()
resp, targets, err := handler.ServeDHCP(pkt, mac, addr)
if err != nil {
log.Warningf("[DHCP] handler serve error: %v", err)
return
}
if resp == nil {
// log.Warningf("[DHCP] hander response null packet")
return
}
if len(targets) > 0 {
targetUdpAddr := *addr
for _, target := range targets {
log.Debugf("[DHCP] Send packet back to %s", target)
targetUdpAddr.IP = net.ParseIP(target)
resp.SetGIAddr(targetUdpAddr.IP)
if err = s.conn.SendDHCP(resp, &targetUdpAddr, mac); err != nil {
log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err)
}
}
return
}
//log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf)
if err = s.conn.SendDHCP(resp, addr, mac); err != nil {
log.Errorf("[DHCP] failed to response packet for %s: %v", pkt.CHAddr(), err)
return
}
}()
}
}
func (s *DHCP6Server) serveDHCP(handler DHCP6Handler) error {
for {
pkt, addr, mac, err := s.conn.RecvDHCP6()
if err != nil {
log.Errorf("Receiving DHCP packet: %s", err)
continue
}
go func() {
defer func() {
if r := recover(); r != nil {
log.Errorf("Serve panic error: %v", r)
debug.PrintStack()
}
}()
if addr.Port == icmpRAFakePort {
// receive a RA solication
handler.OnRecvICMP6(pkt)
return
}
resp, targets, err := handler.ServeDHCP(pkt, mac, addr)
if err != nil {
log.Warningf("[DHCP6] handler serve error: %s", err)
return
}
if resp == nil {
log.Warningf("[DHCP6] hander response null packet")
return
}
if len(targets) > 0 {
targetUdpAddr := *addr
for _, target := range targets {
log.Debugf("[DHCP6] Send packet back to %s", target)
targetUdpAddr.IP = net.ParseIP(target)
if err = s.conn.SendDHCP6(resp, &targetUdpAddr, mac); err != nil {
log.Errorf("[DHCP6] failed to response packet for %s: %v", pkt.CHAddr(), err)
}
}
return
}
//log.Debugf("[DHCP] send response packet: %s to interface: %#v", resp.DebugString(), intf)
if err = s.conn.SendDHCP6(resp, addr, mac); err != nil {
log.Errorf("[DHCP6] failed to response packet for %s: %v", pkt.CHAddr(), err)
return
}
}()
}
}