mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-19 02:37:24 +08:00
321 lines
10 KiB
Go
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
|
|
}
|
|
}()
|
|
}
|
|
}
|