Skip to content

Instantly share code, notes, and snippets.

@gvanem
Last active June 22, 2022 07:01
Show Gist options
  • Save gvanem/50808ce0e38e8859a19dd66d3eb23085 to your computer and use it in GitHub Desktop.
Save gvanem/50808ce0e38e8859a19dd66d3eb23085 to your computer and use it in GitHub Desktop.
A simple pcap-based example for Mongoose with MIP (mini-TCP/IP). Could be added as "examples/pcap/main.c"
#include <pcap/pcap.h>
#include <pcap-int.h>
#include <stdint.h>
#include <stdarg.h>
#include <signal.h>
#ifdef _WIN32
#include <ntddndis.h>
#endif
#include "mongoose.h"
#if (MG_ENABLE_MIP != 1)
#error "This example needs 'MG_ENABLE_MIP=1'"
#endif
#if !defined(_WIN32) || defined(USE_PTHREADS)
#include <pthread.h>
#define THREAD_RET_TYPE uint32_t
#else
#define THREAD_RET_TYPE DWORD WINAPI
#endif
#define MAC_ADDRESS { 0xAA, 0xBB, 0xCC, 1, 2, 3 } // Our default MAC-address
#define SNTP_REFRESH (5*60) // Do a mg_sntp_request() every 5 min
#define SNTP_URL "udp://time.google.com:123"
#define SSDP_URL "udp://239.255.255.250:1900"
static volatile int g_quit_sig = 0;
static int g_trace_level = 0;
static uint8_t g_mac_address[6] = MAC_ADDRESS;
static void (*g_rx) (void*, size_t, void*); // Recv callback
static void *g_rxdata; // Recv callback data
static time_t g_timestamp = 0; // Updated by SNTP
static struct mg_connection *g_sntp_conn; // SNTP connection
static struct mg_connection *g_ssdp_conn; // SSDP connection
//
// In examples/device-dashboard/net.c
//
extern void device_dashboard_fn (struct mg_connection *c, int ev, void *ev_data, void *fn_data);
static void fatal (const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
vprintf (fmt, ap);
va_end (ap);
putchar ('\n');
exit (1);
}
#if defined(_WIN32) && !defined(USE_PTHREADS)
static void *start_thread (LPTHREAD_START_ROUTINE func, void *userdata)
{
DWORD thread_id;
HANDLE thread = CreateThread (NULL, 2048, func, userdata, 0, &thread_id);
if (!thread)
fatal ("CreateThread() failed: %lu", GetLastError());
SetThreadPriority (thread, THREAD_PRIORITY_TIME_CRITICAL);
return (void*) thread;
}
static void kill_thread (void *thread)
{
TerminateThread ((HANDLE)thread, 1);
CloseHandle ((HANDLE)thread);
}
#else
static void *start_thread (THREAD_RET_TYPE (*func)(void*), void *userdata)
{
pthread_t thread_id;
pthread_attr_t attr;
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
pthread_create (&thread_id, &attr, (void *(*) (void*))func, userdata);
pthread_attr_destroy (&attr);
return (NULL);
}
static void kill_thread (void *thread)
{
(void) thread;
}
#endif
static void sig_handler (int sig)
{
g_quit_sig = sig;
}
static size_t pcap_tx (const void *data, size_t len, void *userdata)
{
if (pcap_inject((pcap_t*)userdata, data, len) == PCAP_ERROR)
g_quit_sig = 1;
return (len);
}
#ifdef _WIN32
typedef enum link_state {
STATE_UNKNOWN,
STATE_UP,
STATE_DOWN
} link_state;
//
// Ask the NDIS-driver for the link-status
//
static link_state get_pcap_linkstate (pcap_t *pc, const char **state_str)
{
NDIS_MEDIA_STATE ndis_state;
size_t size = sizeof(ndis_state);
if (pcap_oid_get_request(pc, OID_GEN_MEDIA_CONNECT_STATUS, &ndis_state, &size) || size != sizeof(ndis_state))
{
*state_str = "STATE_UNKNOWN";
return (STATE_UNKNOWN);
}
switch (ndis_state)
{
case NdisMediaStateConnected:
*state_str = "STATE_UP";
return (STATE_UP);
case NdisMediaStateDisconnected:
*state_str = "STATE_DOWN";
return (STATE_DOWN);
default:
break;
}
*state_str = "STATE_UNKNOWN";
return (STATE_UNKNOWN);
}
static bool pcap_status (void *userdata)
{
const char *state_str;
link_state state = get_pcap_linkstate ((pcap_t*)userdata, &state_str);
MG_INFO (("link-state: %s.", state_str));
return (state == STATE_UP);
}
#else
/*
* Non-Windows.
* I've no idea if this is good enough.
*/
static bool pcap_status (void *userdata)
{
return pcap_get_selectable_fd ((pcap_t*)userdata);
}
#endif
static void pcap_set_rxcb (void (*rx)(void *data, size_t data_sz, void *cb_data), void *rxdata)
{
g_rx = rx;
g_rxdata = rxdata;
}
static void pcap_rx_handler (uint8_t *userdata, const struct pcap_pkthdr *pcap_hdr, const uint8_t *data)
{
if (g_rx)
(*g_rx) ((void*)data, pcap_hdr->caplen, g_rxdata);
}
static THREAD_RET_TYPE pcap_rx_thread (void *userdata)
{
pcap_t *pc = (pcap_t*)userdata;
pcap_loop (pc, 0, pcap_rx_handler, userdata);
return (0);
}
static const char *get_mac_string (void)
{
static char buf [30];
snprintf (buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X",
g_mac_address[0], g_mac_address[1], g_mac_address[2],
g_mac_address[3], g_mac_address[4], g_mac_address[5]);
return (buf);
}
static void set_mac_address (const char *arg)
{
if (sscanf(arg, "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
&g_mac_address[0], &g_mac_address[1], &g_mac_address[2],
&g_mac_address[3], &g_mac_address[4], &g_mac_address[5]) != 6)
fatal ("Illegal MAC-address: '%s'", arg);
}
//
// Use this BPF filter to reduce noise.
// Only broadcasts and our own traffic.
//
static const char *get_default_filter (void)
{
static char buf [100];
snprintf (buf, sizeof(buf), "ether host %s or ether host FF:FF:FF:FF:FF:FF", get_mac_string());
return (buf);
}
static void show_help (const char *argv0, const char *extra, ...)
{
if (extra)
{
va_list ap;
va_start (ap, extra);
vprintf (extra, ap);
va_end (ap);
}
printf ("Usage: %s [options]\n"
" -d N sets debug-level 'N'\n"
" -i D selects pcap-device 'D'\n"
" --filter <BPF filter>. \"none\" is allowed. Default is \"%s\".\n", argv0, get_default_filter());
printf (" --mac <MAC address>. Default is \"%s\".\n"
" --sntp start SNTP client\n"
" --ssdp start SSDP discovery\n"
" --tcpdump start 'tcpdump' in parallell\n\n"
"Compiled with %s\n", get_mac_string(), pcap_lib_version());
exit (0);
}
//
// SNTP connection event handler. When we get a response from an SNTP server,
// adjust g_timestamp. We'll get a valid time from that point on.
//
static void sntp_callback (struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
if (ev == MG_EV_SNTP_TIME)
{
uint64_t time_ms = *(uint64_t*) ev_data;
double epoch_time = time_ms / 1000;
g_timestamp = (time_t) (time_ms / 1000);
printf ("SNTP: %.03lf sec from epoch. g_timestamp: %.24s\n", epoch_time, ctime(&g_timestamp));
// mg_close_conn (c);
// g_sntp_conn = NULL;
}
else if (ev == MG_EV_CLOSE)
{
MG_INFO (("Closed connection to SNTP %s", SNTP_URL));
}
(void) fn_data;
}
//
// SNTP timer function.
// Called every 5 sec.
// But sync up time every 5 min (SNTP_REFRESH)
//
static void sntp_timer_fn (void *param)
{
struct mg_mgr *mgr = (struct mg_mgr *) param;
uint64_t now = mg_millis();
bool refreshing = (g_timestamp > 0 && ((now / 1000) % SNTP_REFRESH) == 0);
MG_INFO (("refreshing: %d, now: %llu", refreshing, now));
if (g_timestamp == 0 || refreshing)
{
if (!g_sntp_conn)
g_sntp_conn = mg_sntp_connect (mgr, SNTP_URL, sntp_callback, NULL);
mg_sntp_request (g_sntp_conn);
}
}
//
// SSDP connection event handler.
//
static void ssdp_callback (struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
MG_INFO (("%p got event: %d %p", c, ev, ev_data));
if (ev == MG_EV_OPEN)
{
c->is_hexdumping = 1;
}
else if (ev == MG_EV_RESOLVE)
{
// c->rem gets populated with multicast address. Store it in c->label
memcpy (c->label, &c->rem, sizeof(c->rem));
MG_INFO (("label: '%s'", c->label));
}
else if (ev == MG_EV_READ)
{
//
// Each response to the SSDP socket will change c->rem.
// We can now do mg_printf(c, "haha"); to respond back to the remote side.
// But in our case, we should restore the multicast address in order
// to have next search to go to the multicast address
//
memcpy (&c->rem, c->label, sizeof(c->rem));
MG_INFO (("label: '%s'", c->label));
}
else if (ev == MG_EV_CLOSE)
{
g_ssdp_conn = NULL;
MG_INFO (("Closed connection to SSDP %s", SSDP_URL));
}
}
static void ssdp_timer_fn (void *param)
{
struct mg_mgr *mgr = (struct mg_mgr *) param;
if (g_ssdp_conn == NULL)
g_ssdp_conn = mg_connect (mgr, SSDP_URL, ssdp_callback, NULL);
MG_INFO (("Sending M-SEARCH"));
mg_printf (g_ssdp_conn, "%s",
"M-SEARCH * HTTP/1.1\r\n"
"HOST: %s\r\n"
"MAN: \"ssdp:discover\"\r\n"
"ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n"
"MX: 2\r\n"
"\r\n", 6 + (const char*)SSDP_URL);
}
//
// Initialise Mongoose network stack
// Specify MAC address, and use 0 for IP, mask, GW - i.e. use DHCP
// For static configuration, specify IP/mask/GW in network byte order
//
int main (int argc, char **argv)
{
struct mip_ipcfg ipcfg;
struct mip_driver pcap_driver;
struct mg_mgr mgr;
pcap_t *pc;
char err_buf [PCAP_ERRBUF_SIZE];
const char *error;
const char *iface = NULL;
char *filter = strdup (get_default_filter());
int rc = 0, i;
int sntp = 0, ssdp = 0, tcpdump = 0;
void *thread;
memset (&ipcfg, '\0', sizeof(ipcfg));
memcpy (&ipcfg.mac, &g_mac_address, sizeof(ipcfg.mac));
// Parse options
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "-i") && i + 1 < argc)
{
iface = argv[++i];
}
else if (!strcmp(argv[i], "-d") && i + 1 < argc && isdigit(argv[i+1][0]))
{
g_trace_level = argv[++i][0] - '0';
}
else if (!strcmp(argv[i], "--filter") && i + 1 < argc)
{
free (filter);
filter = strdup (argv[++i]);
}
else if (!strcmp(argv[i], "--mac") && i + 1 < argc)
{
set_mac_address (argv[++i]);
}
else if (!strcmp(argv[i], "--sntp"))
{
sntp = 1;
}
else if (!strcmp(argv[i], "--ssdp"))
{
ssdp = 1;
}
else if (!strcmp(argv[i], "--tcpdump"))
{
tcpdump = 1;
}
else if (!strcmp(argv[i], "-?") || !strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
{
show_help (argv[0], NULL);
}
else
{
show_help (argv[0], "Unknown option %s\n", argv[i]);
}
}
if (!iface)
show_help (argv[0], "Specify pcap interface by option '-i xx'");
pc = pcap_open_live (iface, 65536, 1, 1000, err_buf);
if (!pc)
fatal ("pcap_open_live() failed: %s", err_buf);
memset (&pcap_driver, '\0', sizeof(pcap_driver));
pcap_driver.data = pc;
pcap_driver.tx = pcap_tx;
pcap_driver.status = pcap_status;
pcap_driver.rxcb = pcap_set_rxcb;
if (pcap_datalink(pc) != DLT_EN10MB)
fatal ("This example works only on Ethernet");
if (tcpdump)
{
char buf [200];
snprintf (buf, sizeof(buf), "start tcpdump -%svni %s %s",
g_trace_level >= 1 ? "e" : "", iface, !strcmp(filter, "none") ? "" : filter);
system (buf);
}
if (strcmp(filter, "none"))
{
struct bpf_program bpf;
if (pcap_compile(pc, &bpf, filter, 1, 0) != 0)
fatal ("pcap_compile() failed: %s", pcap_geterr(pc));
pcap_setfilter (pc, &bpf);
if (g_trace_level >= 1)
{
printf ("bpf_dump():\n");
bpf_dump (&bpf, 1);
}
pcap_freecode (&bpf);
}
free (filter);
signal (SIGINT, sig_handler);
signal (SIGTERM, sig_handler);
mg_mgr_init (&mgr);
mg_log_set (g_trace_level >= 2 ? "3" : "2");
MG_INFO (("Starting main pcap thread"));
thread = start_thread (pcap_rx_thread, pc);
if (ssdp)
{
mg_timer_add (&mgr, 2000, MG_TIMER_REPEAT, ssdp_timer_fn, &mgr);
}
else if (sntp)
{
mg_timer_add (&mgr, 5000, MG_TIMER_REPEAT, sntp_timer_fn, &mgr);
}
else
{
mg_http_listen (&mgr, "udp://0.0.0.0:8000", device_dashboard_fn, NULL);
}
mip_init (&mgr, &ipcfg, &pcap_driver);
MG_INFO (("Starting main poller"));
while (1)
{
mg_mgr_poll (&mgr, 200);
if (g_quit_sig)
{
printf ("Calling pcap_breakloop() on signal %d\n", g_quit_sig);
pcap_breakloop (pc);
break;
}
}
kill_thread (thread);
free (mgr.priv); // there is no 'mip_free()'
mg_mgr_free (&mgr);
error = pcap_geterr (pc);
if (error && error[0])
{
MG_INFO (("pcap-error: '%s'\n", error));
rc = 1;
}
pcap_close (pc);
return (rc);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment