Skip to content

Instantly share code, notes, and snippets.

@landonf
Last active August 29, 2024 05:15
Show Gist options
  • Save landonf/5281356 to your computer and use it in GitHub Desktop.
Save landonf/5281356 to your computer and use it in GitHub Desktop.
Go C ffi example
/*
* 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