399 lines
10 KiB
Go
399 lines
10 KiB
Go
package haza
|
|
|
|
import "encoding/binary"
|
|
import "fmt"
|
|
import "net"
|
|
import "sync"
|
|
|
|
import "golang.org/x/net/bpf"
|
|
import "golang.org/x/sys/unix"
|
|
|
|
type Dhcp4UdpSock struct {
|
|
addr *net.UDPAddr
|
|
fd int
|
|
}
|
|
|
|
type Dhcp4Conn struct {
|
|
fd int
|
|
iface string
|
|
|
|
udp_socks []*Dhcp4UdpSock
|
|
mtx sync.Mutex
|
|
}
|
|
|
|
func NewDhcp4Conn(iface string, addr *net.UDPAddr) (*Dhcp4Conn, error) {
|
|
var fd int
|
|
var fd2 int
|
|
var proto uint16
|
|
var d *Dhcp4Conn
|
|
var udp_socks []*Dhcp4UdpSock
|
|
var ip net.IP
|
|
var err error
|
|
|
|
fd = -1
|
|
fd2 = -1
|
|
|
|
ip = addr.IP.To4()
|
|
if ip == nil {
|
|
err = fmt.Errorf("invalid address - %v", addr)
|
|
goto oops
|
|
}
|
|
|
|
// Eventfd is linux specific. TODO: port it to other OSes.
|
|
proto = Hton16(unix.ETH_P_IP)
|
|
//proto = Hton16(unix.ETH_P_ALL)
|
|
fd, err = unix.Socket(unix.AF_PACKET, unix.SOCK_RAW | unix.SOCK_CLOEXEC, int(proto))
|
|
if err != nil { goto oops }
|
|
|
|
fd2, err = open_udp4_socket(addr)
|
|
if err != nil { goto oops }
|
|
|
|
if iface != "" {
|
|
var sll *unix.SockaddrLinklayer
|
|
var nif *net.Interface
|
|
|
|
nif, err = net.InterfaceByName(iface)
|
|
if err != nil { goto oops }
|
|
|
|
// TODO: if the interface is not an ethernet device, fail it.
|
|
/*
|
|
tun0
|
|
Interface type -> Encapsulation Type: Raw IP(7)
|
|
|
|
eth0
|
|
Encapsulation type: Ethernet(1)
|
|
|
|
unix.SockaddrLinklayer -> Hatype
|
|
|
|
/sys/class/net/tun0/type
|
|
-> 65534 (ARPHRD_NONE)
|
|
|
|
/sys/class/net/eth0/type
|
|
-> 1 (ARPHRD_ETHER)
|
|
|
|
#define ARPHRD_NETROM 0 * from KA9Q: NETROM pseudo *
|
|
#define ARPHRD_ETHER 1 * Ethernet 10Mbps *
|
|
#define ARPHRD_EETHER 2 * Experimental Ethernet *
|
|
#define ARPHRD_AX25 3 * AX.25 Level 2 *
|
|
#define ARPHRD_PRONET 4 * PROnet token ring *
|
|
#define ARPHRD_CHAOS 5 * Chaosnet *
|
|
#define ARPHRD_IEEE802 6 * IEEE 802.2 EthernetTRTB *
|
|
#define ARPHRD_ARCNET 7 * ARCnet *
|
|
#define ARPHRD_APPLETLK 8 * APPLEtalk *
|
|
#define ARPHRD_DLCI 15 * Frame Relay DLCI *
|
|
#define ARPHRD_ATM 19 * ATM *
|
|
#define ARPHRD_METRICOM 23 * Metricom STRIP (new IANA id) *
|
|
#define ARPHRD_IEEE1394 24 * IEEE 1394 IPv4 - RFC 2734 *
|
|
#define ARPHRD_EUI64 27 * EUI-64 *
|
|
#define ARPHRD_INFINIBAND 32 * InfiniBand *
|
|
|
|
....
|
|
#define ARPHRD_VOID 0xFFFF * Void type, nothing is known *
|
|
#define ARPHRD_NONE 0xFFFE * zero header length *
|
|
*/
|
|
|
|
sll = &unix.SockaddrLinklayer{
|
|
Protocol: proto,
|
|
Ifindex: nif.Index,
|
|
}
|
|
err = unix.Bind(fd, sll)
|
|
if err != nil { goto oops }
|
|
}
|
|
|
|
udp_socks = make([]*Dhcp4UdpSock, 1)
|
|
udp_socks[0] = &Dhcp4UdpSock{ fd: fd2, addr: addr }
|
|
|
|
err = attach_dhcp4_filter(fd, iface, udp_socks)
|
|
if err != nil { goto oops }
|
|
|
|
d = &Dhcp4Conn{fd: fd, iface: iface, udp_socks: udp_socks}
|
|
return d, nil
|
|
|
|
oops:
|
|
if fd2 >= 0 { unix.Close(fd2) }
|
|
if fd >= 0 { unix.Close(fd) }
|
|
return nil, err
|
|
}
|
|
|
|
func (d *Dhcp4Conn) Close() {
|
|
var us *Dhcp4UdpSock
|
|
|
|
for _, us = range d.udp_socks {
|
|
if us.fd >= 0 {
|
|
unix.Close(us.fd)
|
|
us.fd = -1
|
|
}
|
|
}
|
|
|
|
if d.fd >= 0 {
|
|
unix.Close(d.fd)
|
|
d.fd = -1
|
|
}
|
|
}
|
|
|
|
func (d *Dhcp4Conn) ForEachUdpSockFd(handler func (fd int)) {
|
|
var us *Dhcp4UdpSock
|
|
|
|
for _, us = range d.udp_socks {
|
|
if us.fd >= 0 {
|
|
// the handler must not close the fd
|
|
handler(us.fd)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *Dhcp4Conn) AddAddr(addr *net.UDPAddr) error {
|
|
var fd int
|
|
var udp_socks []*Dhcp4UdpSock
|
|
var err error
|
|
|
|
fd, err = open_udp4_socket(addr)
|
|
if err != nil { return err }
|
|
|
|
err = attach_discard_filter(fd)
|
|
if err != nil { return err }
|
|
|
|
d.mtx.Lock()
|
|
defer d.mtx.Unlock()
|
|
|
|
udp_socks = make([]*Dhcp4UdpSock, len(d.udp_socks) + 1)
|
|
copy(udp_socks, d.udp_socks)
|
|
udp_socks[len(d.udp_socks)] = &Dhcp4UdpSock{fd: fd, addr: addr}
|
|
|
|
err = attach_dhcp4_filter(d.fd, d.iface, udp_socks)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return err
|
|
}
|
|
|
|
d.udp_socks = udp_socks
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
func (d *Dhcp4Conn) DelAddr(addr *net.UDPAddr) error {
|
|
return nil
|
|
}*/
|
|
|
|
func sockaddrToString(sa unix.Sockaddr) string {
|
|
switch v := sa.(type) {
|
|
case *unix.SockaddrInet4:
|
|
ip := net.IPv4(v.Addr[0], v.Addr[1], v.Addr[2], v.Addr[3])
|
|
return fmt.Sprintf("%s:%d", ip.String(), v.Port)
|
|
case *unix.SockaddrInet6:
|
|
ip := net.IP(v.Addr[:])
|
|
return fmt.Sprintf("[%s]:%d", ip.String(), v.Port)
|
|
case *unix.SockaddrUnix:
|
|
return v.Name
|
|
case *unix.SockaddrLinklayer:
|
|
return fmt.Sprintf("ifindex=%d, proto=0x%04x addr % x", v.Ifindex, Ntoh16(v.Protocol), v.Addr[:v.Halen])
|
|
default:
|
|
return fmt.Sprintf("unknown sockaddr type: %T", v)
|
|
}
|
|
}
|
|
|
|
func patch_jump_if_skip_true(insts *[]bpf.Instruction, i uint8, offset uint8) {
|
|
var inst bpf.JumpIf
|
|
inst = (*insts)[i].(bpf.JumpIf)
|
|
inst.SkipTrue = offset
|
|
(*insts)[i] = inst
|
|
}
|
|
|
|
func patch_jump_if_skip_false(insts *[]bpf.Instruction, i uint8, offset uint8) {
|
|
var inst bpf.JumpIf
|
|
inst = (*insts)[i].(bpf.JumpIf)
|
|
inst.SkipFalse = offset
|
|
(*insts)[i] = inst
|
|
}
|
|
|
|
func attach_discard_filter(fd int) error {
|
|
var insts []bpf.Instruction
|
|
var raw_insts []bpf.RawInstruction
|
|
var flts []unix.SockFilter
|
|
var fprog *unix.SockFprog
|
|
var i int
|
|
var err error
|
|
|
|
insts = []bpf.Instruction{
|
|
bpf.RetConstant{Val: 0x0},
|
|
}
|
|
|
|
raw_insts, err = bpf.Assemble(insts)
|
|
if err != nil { goto oops }
|
|
|
|
flts = make([]unix.SockFilter, len(raw_insts))
|
|
for i, _ = range raw_insts {
|
|
flts[i] = unix.SockFilter{
|
|
Code: raw_insts[i].Op,
|
|
Jt: raw_insts[i].Jt,
|
|
Jf: raw_insts[i].Jf,
|
|
K: raw_insts[i].K,
|
|
}
|
|
}
|
|
|
|
fprog = &unix.SockFprog{
|
|
Len: uint16(len(flts)),
|
|
Filter: &flts[0],
|
|
}
|
|
err = unix.SetsockoptSockFprog(fd, unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
|
|
if err != nil { goto oops }
|
|
|
|
return nil
|
|
|
|
|
|
oops:
|
|
return err
|
|
}
|
|
|
|
func attach_dhcp4_filter(fd int, iface string, udp_socks []*Dhcp4UdpSock) error {
|
|
var insts []bpf.Instruction
|
|
var raw_insts []bpf.RawInstruction
|
|
var flts []unix.SockFilter
|
|
var fprog *unix.SockFprog
|
|
var us *Dhcp4UdpSock
|
|
var i int
|
|
var ninsts int
|
|
var ipos uint8
|
|
var accept_pos uint8
|
|
var reject_pos uint8
|
|
var err error
|
|
|
|
ninsts = 8;
|
|
for _, us = range udp_socks {
|
|
if us.addr.IP.To4() == nil { break }
|
|
|
|
if !us.addr.IP.IsUnspecified() {
|
|
ninsts = ninsts + 1
|
|
}
|
|
ninsts = ninsts + 3
|
|
}
|
|
|
|
ninsts = ninsts + 2
|
|
if ninsts > 255 {
|
|
if iface != "" {
|
|
return fmt.Errorf("too many ip addresses on %s", iface)
|
|
} else {
|
|
return fmt.Errorf("too many ip addresses")
|
|
}
|
|
}
|
|
|
|
insts = make([]bpf.Instruction, ninsts)
|
|
|
|
// JumpIf instructions are patched below after ip address and port check instructions are produced
|
|
insts[0] = bpf.LoadAbsolute{Off: 12, Size: 2} // load ethernet type(2 bytes) at offset 12.
|
|
insts[1] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: unix.ETH_P_IP, SkipTrue: 0, SkipFalse: 0}
|
|
insts[2] = bpf.LoadAbsolute{Off: 14 + 9, Size: 1} // load IP protocol (offset 14 + 9)
|
|
insts[3] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: unix.IPPROTO_UDP, SkipTrue: 0, SkipFalse: 0}
|
|
insts[4] = bpf.LoadAbsolute{Off: 14 + 6, Size: 2} // load IP flags & fragment offset (2 bytes)
|
|
insts[5] = bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: 0x1FFF, SkipTrue: 0, SkipFalse: 0}
|
|
insts[6] = bpf.LoadAbsolute{Off: 14 + 16, Size: 4} // destination ip address
|
|
insts[7] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: 0xFFFFFFFF, SkipTrue: 0, SkipFalse: 0}
|
|
|
|
ipos = 8
|
|
for _, us = range udp_socks {
|
|
if us.addr.IP.To4() == nil { break }
|
|
|
|
if !us.addr.IP.IsUnspecified() {
|
|
var b net.IP
|
|
// check more desination ip addresses
|
|
b = us.addr.IP.To4() // must not fail as the check was done already
|
|
insts[ipos] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: binary.BigEndian.Uint32(b), SkipTrue: 0, SkipFalse: 3} // ip match. go to port match
|
|
ipos = ipos + 1
|
|
}
|
|
|
|
// compute IP header length (IHL) into X register. LDX is like this:
|
|
// +---------------+-----------------+---+---+---+
|
|
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
|
// +---------------+-----------------+---+---+---+
|
|
// this one is supposed to be LDX(0x1) | B(0x10) | MSH(0xA0)
|
|
insts[ipos] = bpf.RawInstruction{Op: (0x1 | 0x10 | 0xA0), Jt: 0, Jf: 0, K: 14 }
|
|
ipos = ipos + 1
|
|
|
|
// load UDP destination port: offset = Ethernet(14) + 2 (UDP dest port) + IP header length * 4
|
|
insts[ipos] = bpf.LoadIndirect{Off: 14 + 2, Size: 2}
|
|
ipos = ipos + 1
|
|
insts[ipos] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(us.addr.Port), SkipTrue: 0, SkipFalse: 0}
|
|
ipos = ipos + 1
|
|
}
|
|
|
|
insts[ipos] = bpf.RetConstant{Val: 0} // reject
|
|
reject_pos = ipos
|
|
ipos = ipos + 1
|
|
insts[ipos] = bpf.RetConstant{Val: 0xFFFF} // accept
|
|
accept_pos = ipos
|
|
ipos = ipos + 1
|
|
|
|
patch_jump_if_skip_false(&insts, 1, reject_pos - 1 - 1) // discard if not ETH_P_IP
|
|
patch_jump_if_skip_false(&insts, 3, reject_pos - 3 - 1) // discard if not UDP
|
|
patch_jump_if_skip_true(&insts, 5, reject_pos - 5 - 1) // discard fragmented packets
|
|
patch_jump_if_skip_true(&insts, 7, accept_pos - 7 - 1) // accept broadcast
|
|
|
|
ipos = 8
|
|
for _, us = range udp_socks {
|
|
if us.addr.IP.To4() == nil { break }
|
|
|
|
if !us.addr.IP.IsUnspecified() {
|
|
ipos = ipos + 1
|
|
}
|
|
|
|
ipos = ipos + 1 // skip bpf.RawInstruction
|
|
ipos = ipos + 1 // skip bpf.LoadIndirect
|
|
patch_jump_if_skip_true(&insts, ipos, accept_pos - ipos - 1) // accept if port match
|
|
ipos = ipos + 1
|
|
}
|
|
|
|
raw_insts, err = bpf.Assemble(insts)
|
|
if err != nil { goto oops }
|
|
|
|
flts = make([]unix.SockFilter, len(raw_insts))
|
|
for i, _ = range raw_insts {
|
|
flts[i] = unix.SockFilter{
|
|
Code: raw_insts[i].Op,
|
|
Jt: raw_insts[i].Jt,
|
|
Jf: raw_insts[i].Jf,
|
|
K: raw_insts[i].K,
|
|
}
|
|
}
|
|
|
|
fprog = &unix.SockFprog{
|
|
Len: uint16(len(flts)),
|
|
Filter: &flts[0],
|
|
}
|
|
err = unix.SetsockoptSockFprog(fd, unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
|
|
if err != nil { goto oops }
|
|
|
|
return nil
|
|
|
|
oops:
|
|
return err
|
|
}
|
|
|
|
func open_udp4_socket(addr *net.UDPAddr) (int, error) {
|
|
var fd int
|
|
var ip net.IP
|
|
var err error
|
|
|
|
ip = addr.IP.To4()
|
|
if ip == nil {
|
|
err = fmt.Errorf("invalid address - %v", addr)
|
|
goto oops
|
|
}
|
|
|
|
// create a real udp packet to deceive the kernel.
|
|
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM | unix.SOCK_CLOEXEC, unix.IPPROTO_UDP)
|
|
if err != nil { goto oops }
|
|
|
|
// we won't be using this socket to receive packets.
|
|
// attach a filter that discard incoming packets
|
|
err = attach_discard_filter(fd)
|
|
if err != nil { goto oops }
|
|
|
|
err = unix.Bind(fd, &unix.SockaddrInet4{ Addr: [4]byte(ip[0:4]), Port: addr.Port})
|
|
if err != nil { goto oops }
|
|
|
|
return fd, nil
|
|
|
|
oops:
|
|
return -1, err
|
|
}
|