Last active
August 29, 2024 05:15
-
-
Save landonf/5281356 to your computer and use it in GitHub Desktop.
Go C ffi example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2013 Landon Fuller <[email protected]> | |
* All rights reserved. | |
*/ | |
/* Interface to the native pcap(3) library */ | |
package pcap | |
/* | |
#cgo LDFLAGS: -lpcap | |
#include <stdlib.h> | |
#include <string.h> | |
#include <pcap/pcap.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <stdint.h> | |
// Linux, BSD, and Mac OS X compatible address fetching implementation | |
// TODO: Untested on Linux | |
#if defined(__linux__) | |
#define AF_LINK AF_PACKET | |
#else | |
#include <net/if_dl.h> | |
#endif | |
static int fetch_mac_addr (const char *ifname, struct sockaddr_storage *ss, uint8_t mac48[5]) { | |
#if defined(__linux__) | |
struct ifreq ifr; | |
int fd = socket(AF_INET, SOCK_DGRAM, 0); | |
if (fd < 0) | |
return -1; | |
ifr.ifr_addr.sa_family = AF_INET; | |
strcpy(ifr.ifr_name, ifname); | |
if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) | |
return -1; | |
close(fd); | |
memcpy(mac48, ifr.ifr_hwaddr.sa_data, sizeof(mac48)); | |
return 0; | |
#elif defined(AF_LINK) | |
struct sockaddr_dl *sdl = (struct sockaddr_dl *) ss; | |
memcpy(mac48, LLADDR(sdl), sizeof(mac48)); | |
return 0; | |
#else | |
#error Unsupported OS | |
#endif | |
} | |
*/ | |
import "C" | |
import ( | |
"errors" | |
"fmt" | |
"runtime" | |
"unsafe" | |
) | |
const ( | |
// IPv4 | |
AF_INET = C.AF_INET | |
// IPv6 | |
AF_INET6 = C.AF_INET6 | |
// BSD-style AF_LINK. Mapped to AF_PACKET on Linux. | |
AF_LINK = C.AF_LINK | |
) | |
/* A standard UNIX sockaddr structure. */ | |
type SocketAddress struct { | |
// The parent interface name | |
ifname string | |
// Backing storage | |
cval C.struct_sockaddr_storage | |
} | |
// Return the socket address family | |
func (sa *SocketAddress) Family() int { | |
return int(sa.cval.ss_family) | |
} | |
// Return the socket address in network byte order, or nil | |
// if the address' family is unknown | |
func (sa *SocketAddress) AddressBytes() []byte { | |
switch sa.Family() { | |
case AF_INET: | |
sin := (*C.struct_sockaddr_in)(unsafe.Pointer(&sa.cval)) | |
return C.GoBytes(unsafe.Pointer(&sin.sin_addr), 4) | |
case AF_INET6: | |
sin6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(&sa.cval)) | |
return C.GoBytes(unsafe.Pointer(&sin6.sin6_addr), 16) | |
case AF_LINK: | |
cstr := C.CString(sa.ifname) | |
defer C.free(unsafe.Pointer(cstr)) | |
var mac64 [5]C.uint8_t | |
C.fetch_mac_addr(cstr, &sa.cval, &mac64[0]) | |
return C.GoBytes(unsafe.Pointer(&mac64[0]), 5) | |
} | |
return nil | |
} | |
// Convert a (possibly nil) sockaddr struct pointer to a SocketAddress instance | |
func newSocketAddress(ifname string, sa *C.struct_sockaddr) *SocketAddress { | |
if sa == nil { | |
return nil | |
} | |
address := new(SocketAddress) | |
C.memcpy(unsafe.Pointer(&address.cval), unsafe.Pointer(sa), C.size_t(sa.sa_len)) | |
address.ifname = ifname | |
return address | |
} | |
/* Address associated with a pcap interface */ | |
type InterfaceAddress struct { | |
// The address assigned to this interface | |
source *SocketAddress | |
// The netmask. May be nil. | |
netmask *SocketAddress | |
// The broadcast address. May be nil. | |
broadcast *SocketAddress | |
// The destination address (eg, for point-to-point links). May be nil. | |
destination *SocketAddress | |
} | |
// Return the address assigned to this interface | |
func (addr *InterfaceAddress) Source() *SocketAddress { | |
return addr.source | |
} | |
/* | |
* A pcap interface. | |
*/ | |
type Interface struct { | |
// Interface name. | |
name string | |
// Human readable description. May be nil. | |
description string | |
// If true, this is a loopback interface | |
loopback bool | |
// InterfaceAddress associated with this interface | |
addresses []*InterfaceAddress | |
} | |
// Return the interface name. | |
func (iface *Interface) Name() string { | |
return iface.name | |
} | |
// Return the interface description. May be nil | |
func (iface *Interface) Description() string { | |
return iface.description | |
} | |
// Return true if this is a loopback interface | |
func (iface *Interface) Loopback() bool { | |
return iface.loopback | |
} | |
// Return the addresses associated with this interface | |
func (iface *Interface) Addresses() []*InterfaceAddress { | |
return iface.addresses | |
} | |
type CaptureSource interface { | |
/* | |
* Set the snapshot length via pcap_set_snaplen(). | |
* | |
* The snaplen must be greater than 0. This call will fail if the | |
* capture source has already been actived. | |
*/ | |
SetSnapshotLength(snaplen int) error | |
/* | |
* Set the buffer size via pcap_set_buffer_size(). | |
* The bufsize must be greater than or equal to 0. This call will fail if the | |
* capture source has already been actived. | |
*/ | |
SetBufferSize(bufsize int) error | |
/** | |
* Activate the capture source via pcap_activate() | |
*/ | |
Activate() error | |
/* | |
* Inject a packet via pcap_sendpacket(). | |
*/ | |
SendPacket(data []byte) error | |
/* | |
* Close a previously opened capture source via pcap_close() | |
*/ | |
Close() | |
} | |
/* Packet capture reference. */ | |
type captureSource struct { | |
cptr *C.pcap_t | |
} | |
/* | |
* Create a new pcap capture reference via pcap_create(). | |
* | |
* The ifname must be a valid network device name. | |
*/ | |
func NewCaptureSource(ifname string) (CaptureSource, error) { | |
/* Set up our error buffer */ | |
errbuf := (*C.char)(C.calloc(C.PCAP_ERRBUF_SIZE, 1)) | |
defer C.free(unsafe.Pointer(errbuf)) | |
/* Convert arguments to appropriate C types */ | |
src_cptr := C.CString(ifname) | |
defer C.free(unsafe.Pointer(src_cptr)) | |
/* Create the handle */ | |
result := new(captureSource) | |
result.cptr = C.pcap_create(src_cptr, errbuf) | |
if result.cptr == nil { | |
return nil, errors.New(C.GoString(errbuf)) | |
} | |
/* Set a finalier */ | |
runtime.SetFinalizer(result, func(source *captureSource) { | |
source.Close() | |
}) | |
return result, nil | |
} | |
/* | |
* Find a specific interface. Returns nil if it can not be found, | |
* and an error if an error occurs iterating the available interfaces. | |
*/ | |
func FindInterface(ifname string) (*Interface, error) { | |
if ifaces, err := FindAllInterfaces(); err != nil { | |
return nil, err | |
} else { | |
for _, iface := range ifaces { | |
if iface.Name() == ifname { | |
return iface, nil | |
} | |
} | |
} | |
return nil, nil | |
} | |
/* | |
* Find all interfaces available for capture. | |
*/ | |
func FindAllInterfaces() ([]*Interface, error) { | |
var devices *C.pcap_if_t | |
/* Set up our error buffer */ | |
errbuf := (*C.char)(C.calloc(C.PCAP_ERRBUF_SIZE, 1)) | |
defer C.free(unsafe.Pointer(errbuf)) | |
/* Request the interfaces */ | |
if res := C.pcap_findalldevs(&devices, errbuf); res != 0 { | |
return nil, errors.New(C.GoString(errbuf)) | |
} | |
defer C.pcap_freealldevs(devices) | |
/* Prepare the result */ | |
var ifaces = make([]*Interface, 0) | |
for ifp := devices; ifp != nil; ifp = (*C.pcap_if_t)(ifp.next) { | |
iface := new(Interface) | |
ifaces = append(ifaces, iface) | |
if ifp.name == nil { | |
continue | |
} | |
iface.name = C.GoString(ifp.name) | |
if ifp.description != nil { | |
iface.description = C.GoString(ifp.description) | |
} | |
if ifp.flags&C.PCAP_IF_LOOPBACK != 0 { | |
iface.loopback = true | |
} | |
for ap := ifp.addresses; ap != nil; ap = (ap.next) { | |
addr := new(InterfaceAddress) | |
addr.source = newSocketAddress(iface.name, ap.addr) | |
addr.netmask = newSocketAddress(iface.name, ap.netmask) | |
addr.broadcast = newSocketAddress(iface.name, ap.broadaddr) | |
addr.destination = newSocketAddress(iface.name, ap.dstaddr) | |
iface.addresses = append(iface.addresses, addr) | |
} | |
} | |
return ifaces, nil | |
} | |
// Call a pcap_ function that may return PCAP_ERROR_ACTIVATED. | |
func (source *captureSource) requireInactive(impl func() C.int) error { | |
if ret := impl(); ret == C.PCAP_ERROR_ACTIVATED { | |
return errors.New("Called on a capture source that has already been actived") | |
} else if ret != 0 { | |
return fmt.Errorf("An unexpected error occured: %d", ret) | |
} | |
return nil | |
} | |
// from CaptureSource | |
func (source *captureSource) SetSnapshotLength(snaplen int) error { | |
return source.requireInactive(func() C.int { | |
return C.pcap_set_snaplen(source.cptr, C.int(snaplen)) | |
}) | |
} | |
// from CaptureSource | |
func (source *captureSource) SetBufferSize(bufsize int) error { | |
return source.requireInactive(func() C.int { | |
return C.pcap_set_buffer_size(source.cptr, C.int(bufsize)) | |
}) | |
} | |
// from CaptureSource | |
func (source *captureSource) Activate() error { | |
if ret := C.pcap_activate(source.cptr); ret != 0 { | |
switch ret { | |
case C.PCAP_ERROR_ACTIVATED: | |
return source.requireInactive(func() C.int { return ret }) | |
case C.PCAP_ERROR_RFMON_NOTSUP: | |
return errors.New("Monitor mode is not supported") | |
case C.PCAP_ERROR_IFACE_NOT_UP: | |
return errors.New("Interface is not up") | |
source.Close() | |
return errors.New("Could not enable promiscuous mode on the interface") | |
case C.PCAP_WARNING: | |
// TODO - Should we log here? | |
case C.PCAP_WARNING_PROMISC_NOTSUP: | |
fallthrough | |
case C.PCAP_ERROR_NO_SUCH_DEVICE: | |
fallthrough | |
case C.PCAP_ERROR_PERM_DENIED: | |
fallthrough | |
case C.PCAP_ERROR: | |
return errors.New(C.GoString(C.pcap_geterr(source.cptr))) | |
} | |
} | |
return nil | |
} | |
// from CaptureSource | |
func (source *captureSource) SendPacket(data []byte) error { | |
buf := (*C.uchar)(C.malloc((C.size_t)(len(data)))) | |
defer C.free(unsafe.Pointer(buf)) | |
for i, b := range data { | |
*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + uintptr(i))) = b | |
} | |
if ret := C.pcap_sendpacket(source.cptr, (*C.u_char)(buf), C.int(len(data))); ret != 0 { | |
return errors.New(C.GoString(C.pcap_geterr(source.cptr))) | |
} | |
return nil | |
} | |
// from CaptureSource | |
func (source *captureSource) Close() { | |
if source.cptr != nil { | |
C.pcap_close(source.cptr) | |
source.cptr = nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment