Last active
June 22, 2022 07:01
-
-
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"
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
#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