|
Barracuda Application Server C/C++ Reference
Native APIs, integration guides, and platform interfaces
|
BamDNS lets a client on the local network find your server as device.local instead of entering its IP address. Multicast DNS (mDNS) answers the client's name lookup directly on the local link, without a DNS server. Once the name resolves, the browser connects to your existing HTTP or HTTPS listener.
Mako Server includes BamDNS in its build. Other BAS builds, including Xedge, do not include it by default. Many real-time operating system (RTOS) platforms already provide mDNS through their network stack. If that implementation can advertise your server's hostname, you may not need BamDNS.
The Mako startup code registers ba.createmdns; it does not choose a name or start a responder automatically. A Mako application can start one as follows:
The required name argument is a Lua string containing one host label, without .local. The constructor returns responder userdata, or nil and an error string. Invalid Lua argument types raise an error. mdns:status() returns true, or nil and an error string; mdns:close() returns no values and is safe to repeat. Garbage collection also closes the responder.
After starting it, use http://device.local/ or https://device.local/. A nonstandard web port still belongs in the URL, such as http://device.local:8080/. HTTPS also needs an appropriate certificate; mDNS does not configure certificates or web listeners. The client must support mDNS, and the selected network must permit multicast traffic.
This is a small hostname responder: one name, one network interface, and at most one IPv4 and one IPv6 address. It answers A (IPv4), AAAA (IPv6), and ANY queries. It does not discover services or publish HTTP service records.
There is no name probing, conflict detection, or automatic renaming. Choose a name that is unique on the selected link. Construction success confirms local setup, not name uniqueness or reachability from another device.
Three parts work together:
| Part | Responsibility |
|---|---|
| Your application | Choose the name, select automatic discovery or supply fixed network information, and own the responder's lifetime. |
xrc/misc/BamDNS.c | Process DNS packets, schedule replies with BaTimer, and receive socket events through SoDisp. |
src/arch/bsdSocket/BamDNS-Sock.c | Configure multicast sockets, obtain packet metadata, and send replies on the selected interface. It also supplies a desktop interface-enumeration helper. |
The core uses nonblocking BAS sockets and the existing SoDisp dispatcher. It creates no private polling loop or thread. Reuse your application's BaTimer and its dispatcher mutex.
Mako's Visual Studio project and examples/MakoServer/make/Makefile already include both C files. For another BAS or standalone Barracuda Web Server (BWS) application, add these files explicitly:
inc/BamDNS.h: public declarations and caller-allocated object storage.xrc/misc/BamDNS.c: portable responder and optional Lua binding.src/arch/bsdSocket/BamDNS-Sock.c: supplied Windows/Linux/macOS socket port, or one replacement for your target as described below.They are not included in the BAS/BWS amalgamations. Compile the files with the same BAS headers, socket port, and feature definitions as the host. USE_DGRAM is required. Define USE_IPV6 consistently throughout the build to include IPv6 fields and behavior. Without it, the responder is IPv4-only. Define BAS_LOADED when including the Lua binding; leave it undefined for standalone BWS without Lua or Lua headers. The supplied Windows discovery helper needs iphlpapi.lib; the port adds this library automatically for MSVC.
BamDNS_getNetInfo fills a network snapshot using the operating system's assigned addresses and interface indices. Call it after network initialization and before taking the BAS mutex, because interface enumeration may block. On Windows, Winsock must already be initialized by your host.
Use cfg.netInfo = NULL for automatic discovery at construction and an approximately five-second refresh. The timer releases the dispatcher mutex while the port enumerates interfaces. A separate timer context keeps close/GC safe during this unlocked operation; it does not require a private thread. Other timer callbacks can be delayed by enumeration, but HTTP/Lua dispatch can continue. Addresses, indexes, or prefixes changing cause sockets and multicast memberships to be rebuilt, pending old replies to be discarded, and the new records to be announced. Unchanged checks send no announcements. If no usable network remains, replies stop and the same object retries on later checks. Initial construction still fails if no network is available.
The following integration functions assume that the application already owns an initialized SoDisp and BaTimer sharing the same non-NULL mutex. The HTTP listener must serve the interface chosen by the helper. Call startLocalName once from serialized application startup, with the mutex initially unlocked. If it returns zero, call stopLocalName before destroying the dispatcher or timer, again from application code with the mutex initially unlocked.
Here disp (SoDisp*) and timer (BaTimer*) are required borrowed pointers to the host's existing objects. localName (BamDNS) remains at a fixed address until closed. cfg (BamDNS_Config) is a temporary input: the constructor copies the name and obtains its own network snapshot. status (int) is zero on success or a BAS error code on failure.
The constructor returns void, consistent with its C++ constructor wrapper. Always check BamDNS_status afterward. Continue running the host's dispatcher and timer to process queries and announcements. Do not copy or move a live responder, or construct another instance over its storage before closing it.
All native responder calls require the dispatcher mutex. BAS already holds that mutex when entering socket/timer callbacks or Lua bindings; those callers must not add another lock/unlock pair. The example acquires the mutex because it represents application startup and shutdown outside those callbacks.
BamDNS_getNetInfo selects the first usable active, multicast-capable LAN interface in OS enumeration order, including eligible virtual Ethernet interfaces. It prefers a link-local IPv6 address. It does not select a route to a particular client. Automatic responders call it at construction and periodically afterward. If the choice is unsuitable, supply your own fixed snapshot instead; close and recreate fixed-mode responders after changes. The web listener must continue to serve whichever addresses are selected.
An embedded application usually already knows its assigned addresses and interface. Supply that information directly to use fixed-snapshot mode; that object will not enumerate or automatically switch interfaces.
BamDNS_Config contains these two fields:
| Field | Type | Contract |
|---|---|---|
name | const char* | NUL-terminated ASCII label, 1 to 63 bytes. Letters, digits, and internal hyphens only; no leading/trailing hyphen, dots, or .local suffix. Case-insensitive. Copied during construction. |
netInfo | const BamDNS_NetInfo* | Optional pointer to a zero-initialized, filled fixed snapshot, copied during construction. NULL selects automatic discovery and periodic refresh. |
The network snapshot has these fields. Addresses are arrays of bytes in network order, not text strings or host-order integers.
| Field | Type | Contract |
|---|---|---|
ifIndex4 | U32 | Nonzero target interface index when IPv4 is enabled. |
addr4 | U8[4] | Assigned, usable IPv4 unicast address. All zero disables IPv4. |
prefix4 | U8 | IPv4 subnet prefix length in bits, 1 to 32. |
ifIndex6 | U32 | Nonzero IPv6 interface index. Present only with USE_IPV6. |
addr6 | U8[16] | Assigned, usable IPv6 unicast address. All zero disables IPv6. Present only with USE_IPV6. |
prefix6 | U8 | IPv6 subnet prefix length in bits, 1 to 128. Present only with USE_IPV6. |
At least one family must be enabled. Disabled families ignore their index and prefix fields. Both enabled families must describe the same link, even if the stack uses different numeric indices for IPv4 and IPv6. Supply addresses that your web listener serves, and exclude loopback, tentative, expired, multicast, broadcast, and IPv4-mapped IPv6 addresses.
This example deliberately enables only IPv4, even in a build with USE_IPV6. Its inputs come from the target's existing network configuration:
responder (BamDNS*) is required caller-owned, unused or previously closed storage. disp and timer have the same lifetime requirements as above. interfaceIndex (U32), address (required pointer to four U8 bytes), and prefix (U8, bits) follow the snapshot table. The return type is int. The caller closes the initialized responder under the mutex, including after a failed construction. For dual-stack operation, also fill ifIndex6, addr6, and prefix6 inside #ifdef USE_IPV6 before construction.
BamDNS_status(const BamDNS* o) returns zero while local setup is ready. Its required pointer o must refer to an initialized responder. Later socket errors can change the status, so check it when diagnosing lost responses.
| Status | Meaning |
|---|---|
E_INVALID_PARAM | Invalid name, snapshot, or dispatcher/timer configuration. |
E_INVALID_SOCKET_CON | Socket creation, nonblocking mode, or multicast setup failed. |
E_BIND | UDP port 5353 could not be bound. |
E_MALLOC | Discovery, timer context, or timer-node allocation failed. |
E_CANNOT_RESOLVE | Automatic discovery found no usable LAN interface. |
E_SOCKET_READ_FAILED / E_SOCKET_WRITE_FAILED | A later local socket operation failed. |
E_SOCKET_CLOSED | The responder has been closed. |
BamDNS_destructor(BamDNS* o) returns void, sends a best-effort goodbye, cancels its timer, and closes/detaches its sockets. It is safe to repeat after initialization, including failed construction; do not call it on uninitialized storage. Close responders before destroying their BaTimer or SoDisp.
The desktop helper separately returns 0, E_CANNOT_RESOLVE for no usable interface, E_MALLOC, E_INVALID_SOCKET_CON for an OS enumeration failure, or E_INVALID_PARAM for a NULL output pointer. It clears a non-NULL output snapshot on failure. Automatic mode calls it outside the dispatcher mutex. An existing automatic responder retries discovery and socket setup after a failure; explicit close is permanent. A timer-allocation failure is terminal because there is no remaining timer to drive retries.
First check whether your RTOS's own mDNS responder already solves the problem. If you use BamDNS, retain the portable BamDNS.c and replace only the socket port. Your target must already have a BAS HttpSocket and SoDisp port capable of nonblocking UDP receive events.
A replacement BamDNS-Sock.c implements three functions declared in BamDNS.h: setup, receive, and send. They use the same BAS socket that the core creates and closes. Your port does not parse DNS, call Lua, register dispatcher callbacks, schedule timers, or acquire the BAS mutex.
BamDNS_getNetInfo is the fourth port function and is now required for linking the core. It fills a zero-initialized snapshot of the currently selected usable LAN, returning zero on success or a BAS error code on failure. It may block and runs without the dispatcher mutex; do not access Lua or responder storage from it. A fixed-snapshot-only port can supply a stub that returns E_CANNOT_RESOLVE; fixed-mode objects never call it. The supplied BSD socket port already implements this function. Mako registers the Lua binding with NULL so discovery is performed for each new object and refreshed for existing ones, even if Mako originally started without a usable LAN.
An mDNS socket receives multicast traffic on UDP port 5353. A sender's address alone does not tell the responder which local interface received the packet, or whether the destination was a multicast group or the server's own address. The port needs both facts to choose the correct reply and avoid answering on another link. IPv6 link-local replies also need an outgoing interface scope.
The current generic HttpSocket_recvfrom API does not supply all that metadata. Use your stack's packet-information facility or an equivalent interface-bound receive API in the port. The supplied desktop file uses WSARecvMsg/WSASendMsg on Windows and recvmsg/sendmsg on POSIX systems. This extension does not require changing the public BAS socket API or HttpSockaddr.
int BamDNS_socketSetup(BamDNS_Channel* c, const BamDNS_NetInfo* net) receives required, borrowed pointers. c->con.httpSocket (HttpSocket) is already created and nonblocking. c->ipv6 (BaBool) selects IPv4 when false or IPv6 when true. net is the responder's current copied snapshot, borrowed only for the call; automatic refresh may replace it and recreate the sockets.
Configure this socket to:
224.0.0.251 for IPv4 or ff02::fb for IPv6 on that interface.Return 0 on success, E_BIND when binding fails, or -1 for another setup failure. The core closes the socket on failure. Do not close it yourself or replace it with a separate, undispatched socket. Socket close must release memberships and any associated target resources; there is no separate port destructor hook. Do not assume that port sharing guarantees coexistence with another responder, especially for unicast traffic.
int BamDNS_socketRecv(BamDNS_Channel* c, const BamDNS_NetInfo* net,
void* data, int size, BamDNS_Peer* peer) reads at most one datagram without waiting. data is a required writable buffer of size bytes (int, positive). It and the required output pointer peer are borrowed for this call only.
Return a positive byte count for an accepted packet, 0 for a consumed but discarded packet, -2 when there is no data yet, or -1 for a fatal local socket error. Never copy more than size bytes or treat a truncated datagram as complete. Discard packets with missing/truncated metadata or from the wrong interface. The core separately rejects DNS packets larger than 1500 bytes.
For accepted packets, fill these BamDNS_Peer fields:
| Field | Type | Meaning |
|---|---|---|
addr | HttpSockaddr | Sender address. addr.addr contains network-order bytes; addr.isIp6 matches the channel family. |
port | U16 | Nonzero sender UDP port in host byte order. |
multicast | BaBool | True only when the packet was addressed to this family's mDNS multicast group. This describes the received destination, not the sender. |
For direct unicast, accept only a destination equal to the configured local address and a source on the configured subnet or in the family's link-local range (169.254.0.0/16 or fe80::/10). Apply the same checks as the supplied port. Drop disallowed packets with 0; they must not stop the responder.
int BamDNS_socketSend(BamDNS_Channel* c, const BamDNS_NetInfo* net,
const void* data, int size, const BamDNS_Peer* peer) sends one datagram. data is a required readable buffer of size bytes (int, positive). All pointers are borrowed only for this call; never retain the packet buffer.
If peer is NULL, send to the family's mDNS group on port 5353. Otherwise, send to peer->addr and peer->port, even when peer->multicast is true: that flag describes the original query's destination. In both cases, select the configured source address and outgoing interface; include IPv6 scope.
Return exactly size when sent, -2 for would-block, or -1 for a fatal local error. Do not queue a buffer pointer or report a partial UDP send as success. The core handles bounded multicast retries and drops a unicast reply on would-block so the client can retry.
This template shows the boundary between BAS and your stack. The Target_* functions are application-defined placeholders, not BAS APIs. Implement them using your stack's actual socket and packet-information facilities before using this as a replacement port. Their comments describe the required work.
The setup/send placeholder parameters have the same types, units, ownership, and return contracts as the corresponding hooks. socket (HttpSocket*) is required, borrowed storage owned by the core; ipv6 (BaBool) selects its family. Target_recvPacket borrows its buffer and fills the required TargetPacket* packet only for a positive byte count. Its source (HttpSockaddr) and port (U16) identify the sender; destination (U8[16], first four bytes for IPv4) and interfaceIndex (U32) come from receive metadata. truncated (BaBool) must also cover missing metadata. All these outputs must be initialized by that target function.
The receive hook above performs the interface, destination, and on-link checks without parsing DNS. For a small port, put the actual stack calls directly in the three BamDNS_socket* functions instead of adding a separate Target_* call layer. No target-specific operations in this template have implementations until you provide them; it is not a drop-in port.
Keep native socket structures and OS conditionals in the port. Guard IPv6-only types and field access with #ifdef USE_IPV6. Do not change the DNS core to accommodate a stack-specific address or interface representation. If your stack cannot supply the required receive metadata or source/interface control, extend its BAS socket port or use the stack's own mDNS implementation rather than guessing those values from a request.
Verify A and AAAA replies on the intended link, including queries received over either family. Check multicast and unicast replies, a non-5353 query source port, and outgoing TTL/hop limit 255. Try a second active interface to confirm that unrelated-link traffic is ignored. Finally, exercise close, recreation, would-block/error paths, and network reconfiguration with the actual target stack. A successful constructor alone does not verify these.
Native C initialization above needs no Lua. A custom BAS Lua host can instead call BamDNS_luaopen(struct lua_State* L, const BamDNS_NetInfo* netInfo) when compiled with BAS_LOADED. It returns void and installs ba.createmdns in the existing public ba table without leaving values on the Lua stack.
L (struct lua_State*) is a required initialized BAS state with a server and timer sharing the BAS mutex. Pass netInfo = NULL for automatic discovery and periodic refresh, as Mako does. A non-NULL netInfo selects fixed mode and is borrowed until the Lua VM closes, including retained constructor closures. Registration does not copy a supplied snapshot. Hold the BAS mutex while registering; enumerate fixed snapshots before taking it. Close fixed responders before changing their supplied snapshot, and close all responders before shutting down host dependencies. The protocol never calls Lua callbacks, so it needs no Lua thread-manager jobs.