mirror of
https://github.com/danderson/netboot.git
synced 2025-10-17 02:21:20 +02:00
Checkpoint some further hacking on AF_PACKET, before bailing.
AF_PACKET is poorly suited to be the main receiving socket of this implementation, for a variety of reasons: it bypasses netfilter, you receive duplicate packets when bridges and aggregated links are involved, and more generally it's a lot of pain just to get a MAC address out of the link layer. Next up, I'm going to try using an AF_INET/SOCK_RAW socket as the main listening socket, which loses a bit of precision in acquiring packets, but puts more of the kernel and its desirable semantics between us and the wire. We'll still have to use an AF_PACKET socket to transmit to unconfigured clients (although the spec gives us an option to sidestep that as well if we want).
This commit is contained in:
parent
9c43c0a5c9
commit
78dd971886
@ -27,10 +27,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
protoAll = int(unix.ETH_P_ALL)
|
||||
byteOrder = binary.ByteOrder(binary.BigEndian)
|
||||
macBroadcast = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
ipBroadcast = []byte{0xff, 0xff, 0xff, 0xff}
|
||||
protoAll = int(unix.ETH_P_ALL)
|
||||
macbcast = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -42,7 +40,6 @@ func init() {
|
||||
b := *(*byte)(unsafe.Pointer(&i))
|
||||
if b == 1 {
|
||||
protoAll = protoAll << 8
|
||||
byteOrder = binary.LittleEndian
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +50,7 @@ func init() {
|
||||
// services on the same machine. However, using it requires
|
||||
// CAP_NET_RAW, whereas PortableConn doesn't.
|
||||
type LinuxConn struct {
|
||||
port uint16
|
||||
port int
|
||||
ethernetFd int // AF_PACKET SOCK_RAW socket
|
||||
ipFd int // AF_INET SOCK_RAW IPPROTO_RAW socket
|
||||
}
|
||||
@ -69,44 +66,48 @@ func closeLinuxConn(c *LinuxConn) {
|
||||
}
|
||||
}
|
||||
|
||||
// NewLinuxConn creates a LinuxConn that receives DHCP packets on the
|
||||
// given UDP port (should typically be 67)
|
||||
func NewLinuxConn(port uint16) (*LinuxConn, error) { // TODO: support for interface binding
|
||||
// NewLinuxConn creates a LinuxConn that receives DHCP packets. TODO better
|
||||
func NewLinuxConn(addr string) (*LinuxConn, error) {
|
||||
if addr == "" {
|
||||
addr = ":67"
|
||||
}
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
udpAddr.IP = udpAddr.IP.To4()
|
||||
|
||||
eth, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, protoAll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := []unix.SockFilter{
|
||||
{0x28, 0, 0, 12}, // Load ethernet frame type
|
||||
{0x15, 0, 8, 0x0800}, // Is IPv4?
|
||||
{0x30, 0, 0, 23}, // Load IP packet type
|
||||
{0x15, 0, 6, 17}, // Is UDP?
|
||||
{0x28, 0, 0, 20}, // Load fragment offset
|
||||
{0x45, 4, 0, 0x1fff}, // Is first/only fragment?
|
||||
{0xb1, 0, 0, 14}, // Jump to start of UDP header
|
||||
{0x48, 0, 0, 16}, // Load destination port
|
||||
{0x15, 0, 1, uint32(port)}, // Is correct port?
|
||||
{0x6, 0, 0, 0x40000}, // Yes, receive packet
|
||||
{0x6, 0, 0, 0}, // No, ignore packet
|
||||
filter := filterPortOnly(udpAddr.Port)
|
||||
if udpAddr.IP != nil {
|
||||
filter = filterIPAndPort(udpAddr.IP, udpAddr.Port)
|
||||
}
|
||||
filterProg := &unix.SockFprog{
|
||||
Len: uint16(len(filter)),
|
||||
Filter: &filter[0],
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall6(unix.SYS_SETSOCKOPT, uintptr(eth), uintptr(unix.SOL_SOCKET), uintptr(unix.SO_ATTACH_FILTER), uintptr(unsafe.Pointer(filterProg)), uintptr(unsafe.Sizeof(*filterProg)), 0)
|
||||
if errno != 0 {
|
||||
if err = attachFilter(eth, filter); err != nil {
|
||||
unix.Close(eth)
|
||||
return nil, errno
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
unix.Close(eth)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &LinuxConn{port, eth, ip}
|
||||
// if err = attachFilter(ip, filterNoPackets()); err != nil {
|
||||
// unix.Close(eth)
|
||||
// unix.Close(ip)
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
ret := &LinuxConn{
|
||||
port: udpAddr.Port,
|
||||
ethernetFd: eth,
|
||||
ipFd: ip,
|
||||
}
|
||||
runtime.SetFinalizer(ret, closeLinuxConn)
|
||||
return ret, nil
|
||||
}
|
||||
@ -167,7 +168,7 @@ func (c *LinuxConn) SendDHCP(pkt *Packet, intf *net.Interface) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bs := assemblePacket(intf.HardwareAddr, macBroadcast, srcIP, ipBroadcast, c.port, 68, payload)
|
||||
bs := assemblePacket(intf.HardwareAddr, macbcast, srcIP, net.IPv4bcast, c.port, 68, payload)
|
||||
addr := &unix.SockaddrLinklayer{
|
||||
Ifindex: intf.Index,
|
||||
Halen: 6,
|
||||
@ -180,6 +181,7 @@ func (c *LinuxConn) SendDHCP(pkt *Packet, intf *net.Interface) error {
|
||||
bs := assemblePacket(nil, nil, nil, pkt.RelayAddr, c.port, 67, payload)
|
||||
bs = bs[14:] // Skip the ethernet header
|
||||
addr := &unix.SockaddrInet4{}
|
||||
copy(addr.Addr[:], pkt.RelayAddr.To4())
|
||||
if err = unix.Sendto(c.ipFd, bs, 0, addr); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -265,7 +267,7 @@ func interfaceIP(intf *net.Interface) (net.IP, error) {
|
||||
return nil, fmt.Errorf("interface %s has no unicast address usable as a DHCP packet source", intf.Name)
|
||||
}
|
||||
|
||||
func assemblePacket(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, srcPort, dstPort uint16, payload []byte) []byte {
|
||||
func assemblePacket(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, srcPort, dstPort int, payload []byte) []byte {
|
||||
buf := make([]byte, 42, 42+len(payload))
|
||||
|
||||
// Ethernet header
|
||||
@ -280,8 +282,9 @@ func assemblePacket(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, srcPor
|
||||
binary.BigEndian.PutUint32(buf[18:22], 0x4000) // ID=0, frag_off=0, dont_fragment=1
|
||||
buf[22] = 64 // TTL
|
||||
buf[23] = 17 // Inner protocol: UDP
|
||||
copy(buf[26:30], srcIP)
|
||||
copy(buf[30:34], dstIP)
|
||||
copy(buf[26:30], srcIP.To4())
|
||||
copy(buf[30:34], dstIP.To4())
|
||||
fmt.Println(buf[30:34])
|
||||
|
||||
var cksum uint32
|
||||
for i := 14; i < 34; i += 2 {
|
||||
@ -291,9 +294,75 @@ func assemblePacket(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, srcPor
|
||||
binary.BigEndian.PutUint16(buf[24:26], ^uint16(cksum))
|
||||
|
||||
// UDP header
|
||||
binary.BigEndian.PutUint16(buf[34:36], srcPort) // Source port
|
||||
binary.BigEndian.PutUint16(buf[36:38], dstPort) // Destination port
|
||||
binary.BigEndian.PutUint16(buf[34:36], uint16(srcPort)) // Source port
|
||||
binary.BigEndian.PutUint16(buf[36:38], uint16(dstPort)) // Destination port
|
||||
binary.BigEndian.PutUint16(buf[38:40], uint16(8+len(payload))) // UDP length
|
||||
|
||||
return append(buf, payload...)
|
||||
}
|
||||
|
||||
func attachFilter(fd int, filter *unix.SockFprog) error {
|
||||
_, _, errno := unix.Syscall6(unix.SYS_SETSOCKOPT, uintptr(fd), uintptr(unix.SOL_SOCKET), uintptr(unix.SO_ATTACH_FILTER), uintptr(unsafe.Pointer(filter)), uintptr(unsafe.Sizeof(*filter)), 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterPortOnly(port int) *unix.SockFprog {
|
||||
// This filter comes from:
|
||||
// tcpdump -dd 'ip and udp dst port 68'
|
||||
filter := []unix.SockFilter{
|
||||
{0x28, 0, 0, 12}, // Load ethernet frame type
|
||||
{0x15, 0, 8, 0x0800}, // Is IPv4?
|
||||
{0x30, 0, 0, 23}, // Load IP packet type
|
||||
{0x15, 0, 6, 17}, // Is UDP?
|
||||
{0x28, 0, 0, 20}, // Load fragment offset
|
||||
{0x45, 4, 0, 0x1fff}, // Is first/only fragment?
|
||||
{0xb1, 0, 0, 14}, // Jump to start of UDP header
|
||||
{0x48, 0, 0, 16}, // Load destination port
|
||||
{0x15, 0, 1, uint32(port)}, // Is correct port?
|
||||
{0x6, 0, 0, 0x40000}, // Yes, receive packet
|
||||
{0x6, 0, 0, 0}, // No, ignore packet
|
||||
}
|
||||
return &unix.SockFprog{
|
||||
Len: uint16(len(filter)),
|
||||
Filter: &filter[0],
|
||||
}
|
||||
}
|
||||
|
||||
func filterIPAndPort(dstIP net.IP, port int) *unix.SockFprog {
|
||||
d := binary.BigEndian.Uint32([]byte(dstIP.To4()))
|
||||
// This filter comes from:
|
||||
// tcpdump -dd 'ip and udp and (dst 192.168.2.2 or dst 255.255.255.255) and dst port 68'
|
||||
filter := []unix.SockFilter{
|
||||
{0x28, 0, 0, 12}, // Load ethernet frame type
|
||||
{0x15, 0, 11, 0x0800}, // Is IPv4?
|
||||
{0x30, 0, 0, 23}, // Load IP packet type
|
||||
{0x15, 0, 9, 17}, // Is UDP?
|
||||
{0x20, 0, 0, 30}, // Load destination IP
|
||||
{0x15, 1, 0, d}, // Is target IP?
|
||||
{0x15, 0, 6, 0xffffffff}, // Is Broadcast?
|
||||
{0x28, 0, 0, 20}, // Load fragment offset
|
||||
{0x45, 4, 0, 0x1fff}, // Is first/only fragment?
|
||||
{0xb1, 0, 0, 14}, // Jump to start of UDP header
|
||||
{0x48, 0, 0, 16}, // Load destination port
|
||||
{0x15, 0, 1, uint32(port)}, // Is correct port?
|
||||
{0x6, 0, 0, 0x40000}, // Yes, receive packet
|
||||
{0x6, 0, 0, 0}, // No, ignore packet
|
||||
}
|
||||
return &unix.SockFprog{
|
||||
Len: uint16(len(filter)),
|
||||
Filter: &filter[0],
|
||||
}
|
||||
}
|
||||
|
||||
func filterNoPackets() *unix.SockFprog {
|
||||
filter := []unix.SockFilter{
|
||||
{0x6, 0, 0, 0}, // ignore packet
|
||||
}
|
||||
return &unix.SockFprog{
|
||||
Len: uint16(len(filter)),
|
||||
Filter: &filter[0],
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user