Пример #1
0
/**
 * Dispatches an AFC packet over a client.
 * 
 * @param client The client to send data through.
 * @param operation The operation to perform.
 * @param data The data to send together with the header.
 * @param data_length The length of the data to send with the header.
 * @param payload The data to send after the header has been sent.
 * @param payload_length The length of data to send after the header.
 * @param bytes_sent The total number of bytes actually sent.
 *
 * @return AFC_E_SUCCESS on success or an AFC_E_* error value.
 */
static afc_error_t afc_dispatch_packet(afc_client_t client, uint64_t operation, const char *data, uint32_t data_length, const char* payload, uint32_t payload_length, uint32_t *bytes_sent)
{
	uint32_t sent = 0;

	if (!client || !client->parent || !client->afc_packet)
		return AFC_E_INVALID_ARG;

	*bytes_sent = 0;

	if (!data || !data_length)
		data_length = 0;
	if (!payload || !payload_length)
		payload_length = 0;

	client->afc_packet->packet_num++;
	client->afc_packet->operation = operation;
	client->afc_packet->entire_length = sizeof(AFCPacket) + data_length + payload_length;
	client->afc_packet->this_length = sizeof(AFCPacket) + data_length;

	debug_info("packet length = %i", client->afc_packet->this_length);

	debug_buffer((char*)client->afc_packet, sizeof(AFCPacket));

	/* send AFC packet header */
	AFCPacket_to_LE(client->afc_packet);
	sent = 0;
	service_send(client->parent, (void*)client->afc_packet, sizeof(AFCPacket), &sent);
	AFCPacket_from_LE(client->afc_packet);
	*bytes_sent += sent;
	if (sent < sizeof(AFCPacket)) {
		return AFC_E_SUCCESS;
	}

	/* send AFC packet data (if there's data to send) */
	sent = 0;
	if (data_length > 0) {
		debug_info("packet data follows");
		debug_buffer(data, data_length);
		service_send(client->parent, data, data_length, &sent);
	}
	*bytes_sent += sent;
	if (sent < data_length) {
		return AFC_E_SUCCESS;
	}

	sent = 0;
	if (payload_length > 0) {
		debug_info("packet payload follows");
		debug_buffer(payload, payload_length);
		service_send(client->parent, payload, payload_length, &sent);
	}
	*bytes_sent += sent;
	if (sent < payload_length) {
		return AFC_E_SUCCESS;
	}

	return AFC_E_SUCCESS;
}
Пример #2
0
void handle_connect(FILE* sock, const char* args) {
    // Attempt to autoconfigure, if there is no current configuration
    service_send(&service_write_ibuf, WRITE_AUTOCONFIGURE, args);
    service_pop(&service_write_ibuf, NULL, 0);
    
    service_send(&service_exec_ibuf, EXEC_NETSTART, args);
    int32_t result = service_pop(&service_exec_ibuf, NULL, 0);
    if(result == EXEC_RESPONSE_OK) {
        flatjson_send_singleton(sock, "ok");
    } else {
        flatjson_send_singleton(sock, "error");
    }

    fputs("\n", sock);
}
/**
 * Send binary data to the device.
 *
 * @note This function returns MOBILEBACKUP2_E_SUCCESS even if less than the
 *     requested length has been sent. The fourth parameter is required and
 *     must be checked to ensure if the whole data has been sent.
 *
 * @param client The MobileBackup client to send to.
 * @param data Pointer to the data to send
 * @param length Number of bytes to send
 * @param bytes Number of bytes actually sent
 *
 * @return MOBILEBACKUP2_E_SUCCESS if any data was successfully sent,
 *     MOBILEBACKUP2_E_INVALID_ARG if one of the parameters is invalid,
 *     or MOBILEBACKUP2_E_MUX_ERROR if sending of the data failed.
 */
mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, const char *data, uint32_t length, uint32_t *bytes)
{
	if (!client || !client->parent || !data || (length == 0) || !bytes)
		return MOBILEBACKUP2_E_INVALID_ARG;

	*bytes = 0;

	service_client_t raw = client->parent->parent->parent;

	int bytes_loc = 0;
	uint32_t sent = 0;
	do {
		bytes_loc = 0;
		service_send(raw, data+sent, length-sent, (uint32_t*)&bytes_loc);
		if (bytes_loc <= 0)
			break;
		sent += bytes_loc;
	} while (sent < length);
	if (sent > 0) {
		*bytes = sent;
		return MOBILEBACKUP2_E_SUCCESS;
	} else {
		return MOBILEBACKUP2_E_MUX_ERROR;
	}
}
Пример #4
0
void service_log(uint32_t handle, const char *fmt, ...) {
	if (g.log == 0) {
		fprintf(stderr, "[%u] ", handle);
		va_list ap;
		va_start(ap, fmt);
		vfprintf(stderr, fmt, ap);
		va_end(ap);
	} else {
		struct message m;
		int size;
		va_list ap;
		va_start(ap, fmt);
		size = vsnprintf(0, 0, fmt, ap);
		va_end(ap);
		m.data = service_alloc(0, size+1);
		va_start(ap, fmt);
		vsnprintf((char *)m.data, size+1, fmt, ap);
		va_end(ap);
		m.size = size+1;
		m.session = 0;
		m.proto = 0;
		m.source = handle;
		if (-1 == service_send(g.log, &m))
			service_alloc(m.data, 0);
	}
}
Пример #5
0
void handle_iface_change(int monitor) {
    char buf[2048];
    read(monitor, buf, sizeof(buf));
    
    struct rt_msghdr* rtm = (struct rt_msghdr*)&buf;
    struct if_msghdr ifm;
    memcpy(&ifm, rtm, sizeof(ifm));

    char iface[IF_NAMESIZE];
    if(if_indextoname(ifm.ifm_index, iface) == NULL) {
        warn("Failed to look up iface by index");
        return;
    }
    
    bool up = LINK_STATE_IS_UP(ifm.ifm_data.ifi_link_state);
    const char* term = up? "up" : "down";

    snprintf(buf, sizeof(buf), "%s %s", term, iface);
    service_send(&service_exec_ibuf, EXEC_LOGEVENT, buf);
    int32_t result = service_pop(&service_exec_ibuf, NULL, 0);
    if(result != EXEC_RESPONSE_OK) {
        warn("Failed to log iface change");
        return;
    }
}
/**
 * Sends a plist using the given property list service client.
 * Internally used generic plist send function.
 *
 * @param client The property list service client to use for sending.
 * @param plist plist to send
 * @param binary 1 = send binary plist, 0 = send xml plist
 *
 * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success,
 *      PROPERTY_LIST_SERVICE_E_INVALID_ARG when one or more parameters are
 *      invalid, PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid
 *      plist, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified
 *      error occurs.
 */
static property_list_service_error_t internal_plist_send(property_list_service_client_t client, plist_t plist, int binary)
{
	property_list_service_error_t res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR;
	char *content = NULL;
	uint32_t length = 0;
	uint32_t nlen = 0;
	int bytes = 0;

	if (!client || (client && !client->parent) || !plist) {
		return PROPERTY_LIST_SERVICE_E_INVALID_ARG;
	}

	if (binary) {
		plist_to_bin(plist, &content, &length);
	} else {
		plist_to_xml(plist, &content, &length);
	}

	if (!content || length == 0) {
		return PROPERTY_LIST_SERVICE_E_PLIST_ERROR;
	}

	nlen = htobe32(length);
	debug_info("sending %d bytes", length);
	service_send(client->parent, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes);
	if (bytes == sizeof(nlen)) {
		service_send(client->parent, content, length, (uint32_t*)&bytes);
		if (bytes > 0) {
			debug_info("sent %d bytes", bytes);
			debug_plist(plist);
			if ((uint32_t)bytes == length) {
				res = PROPERTY_LIST_SERVICE_E_SUCCESS;
			} else {
				debug_info("ERROR: Could not send all data (%d of %d)!", bytes, length);
			}
		}
	}
	if (bytes <= 0) {
		debug_info("ERROR: sending to device failed.");
	}

	free(content);

	return res;
}
Пример #7
0
static void queue_message_dtor(struct message *m, void *ud) {
	service_alloc(m->data, 0);
	struct message em;
	em.source = (uint32_t)(uintptr_t)ud;
	em.session = 0;
	em.data = 0;
	em.size = 0;
	service_send(m->source, &em);
}
Пример #8
0
static void service_timer_dispatch(void *p) {
	struct timer_event *evt = (struct timer_event *)p;
	struct message m;
	m.source = evt->handle;
	m.session = evt->session;
	m.data = 0;
	m.size = 0;
	m.proto = SERVICE_PROTO_RESP;
	service_send(evt->handle, &m);
}
Пример #9
0
int list_pseudo_classes(char* buf, size_t buf_len) {
    buf[0] = '\0';
    service_send(&service_exec_ibuf, EXEC_IFCONFIG_LIST_PSEUDO_INTERFACES, NULL);
    int32_t result = service_pop(&service_exec_ibuf, buf, buf_len);
    if(result != EXEC_RESPONSE_OK) {
        return 1;
    }

    return 0;
}
Пример #10
0
void handle_configure(FILE* sock, const char* args) {
    service_send(&service_write_ibuf, WRITE_WRITE, args);
    int32_t result = service_pop(&service_write_ibuf, NULL, 0);
    if(result == WRITE_RESPONSE_OK) {
        flatjson_send_singleton(sock, "ok");
    } else {
        flatjson_send_singleton(sock, "error");
    }

    fputs("\n", sock);
}
Пример #11
0
void handle_disconnect(FILE* sock, const char* args) {
    char iface[IF_NAMESIZE];
    strlcpy(iface, args, sizeof(iface));

    service_send(&service_exec_ibuf, EXEC_IFCONFIG_DOWN, args);
    int32_t result = service_pop(&service_exec_ibuf, iface, strlen(iface));
    if(result == EXEC_RESPONSE_OK) {
        flatjson_send_singleton(sock, "ok");
    } else {
        flatjson_send_singleton(sock, "error");
    }

    fputs("\n", sock);
}
Пример #12
0
static int service_socket_poll(void) {
	struct socket_message sm;
	if (!socket_poll(&sm))
		return 0;
	struct message m;
	int size = sizeof sm;
	m.source = 0;
	m.session = 0;
	m.data = service_alloc(0, size);
	m.size = size;
	m.proto = SERVICE_PROTO_SOCKET;
	memcpy(m.data, &sm, size);
	uint32_t handle = (uint32_t)(uintptr_t)sm.ud;
	if (-1 == service_send(handle, &m))
		service_alloc(m.data, 0);
  return 1;
}
Пример #13
0
int service_timeout(uint32_t handle, int ti) {
	int session = service_session(handle);
	if (ti == 0) {
		struct message m;
		m.source = handle;
		m.session = session;
		m.data = 0;
		m.size = 0;
		m.proto = SERVICE_PROTO_RESP;
		session = service_send(handle, &m);
	} else {
		struct timer_event evt;
		evt.session = session;
		evt.handle = handle;
		timer_timeout(ti, &evt, sizeof(evt));
	}
	return session;
}
Пример #14
0
debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent)
{
	debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR;
	int bytes = 0;

	if (!client || !data || (size == 0)) {
		return SERVICE_E_INVALID_ARG;
	}

	debug_info("sending %d bytes", size);
	res = debugserver_error(service_send(client->parent, data, size, (uint32_t*)&bytes));
	if (bytes <= 0) {
		debug_info("ERROR: sending to device failed.");
	}
	if (sent) {
		*sent = (uint32_t)bytes;
	}

	return res;
}
Пример #15
0
/**
 * Dispatches an AFC packet over a client.
 * 
 * @param client The client to send data through.
 * @param data The data to send.
 * @param length The length to send.
 * @param bytes_sent The number of bytes actually sent.
 *
 * @return AFC_E_SUCCESS on success or an AFC_E_* error value.
 * 
 * @warning set client->afc_packet->this_length and
 *          client->afc_packet->entire_length to 0 before calling this.  The
 *          reason is that if you set them to different values, it indicates
 *          you want to send the data as two packets.
 */
static afc_error_t afc_dispatch_packet(afc_client_t client, const char *data, uint32_t length, uint32_t *bytes_sent)
{
	uint32_t offset = 0;
	uint32_t sent = 0;

	if (!client || !client->parent || !client->afc_packet)
		return AFC_E_INVALID_ARG;

	*bytes_sent = 0;

	if (!data || !length)
		length = 0;

	client->afc_packet->packet_num++;
	if (!client->afc_packet->entire_length) {
		client->afc_packet->entire_length = (length) ? sizeof(AFCPacket) + length : sizeof(AFCPacket);
		client->afc_packet->this_length = client->afc_packet->entire_length;
	}
	if (!client->afc_packet->this_length) {
		client->afc_packet->this_length = sizeof(AFCPacket);
	}
	/* We want to send two segments; buffer+sizeof(AFCPacket) to this_length
	   is the parameters and everything beyond that is the next packet.
	   (for writing) */
	if (client->afc_packet->this_length != client->afc_packet->entire_length) {
		offset = (uint32_t)(client->afc_packet->this_length - sizeof(AFCPacket));

		debug_info("Offset: %i", offset);
		if ((length) < (client->afc_packet->entire_length - client->afc_packet->this_length)) {
			debug_info("Length did not resemble what it was supposed to based on packet");
			debug_info("length minus offset: %i", length - offset);
			debug_info("rest of packet: %i\n", client->afc_packet->entire_length - client->afc_packet->this_length);
			return AFC_E_INTERNAL_ERROR;
		}

		/* send AFC packet header */
		AFCPacket_to_LE(client->afc_packet);
		sent = 0;
		service_send(client->parent, (const char *)client->afc_packet, sizeof(AFCPacket), &sent);
		AFCPacket_from_LE(client->afc_packet);
		if (sent == 0) {
			/* FIXME: should this be handled as success?! */
			return AFC_E_SUCCESS;
		}
		*bytes_sent += sent;

		/* send AFC packet data */
		sent = 0;
		service_send(client->parent, data, offset, &sent);
		if (sent == 0) {
			return AFC_E_SUCCESS;
		}
		*bytes_sent += sent;

		debug_info("sent the first now go with the second");
		debug_info("Length: %i", length - offset);
		debug_info("Buffer: ");
		debug_buffer(data + offset, length - offset);

		sent = 0;
		service_send(client->parent, data + offset, length - offset, &sent);

		*bytes_sent = sent;
		return AFC_E_SUCCESS;
	} else {
		debug_info("doin things the old way");
		debug_info("packet length = %i", client->afc_packet->this_length);

		debug_buffer((char*)client->afc_packet, sizeof(AFCPacket));

		/* send AFC packet header */
		AFCPacket_to_LE(client->afc_packet);
		sent = 0;
		service_send(client->parent, (const char *)client->afc_packet, sizeof(AFCPacket), &sent);
		AFCPacket_from_LE(client->afc_packet);
		if (sent == 0) {
			return AFC_E_SUCCESS;
		}
		*bytes_sent += sent;
		/* send AFC packet data (if there's data to send) */
		if (length > 0) {
			debug_info("packet data follows");

			debug_buffer(data, length);
			service_send(client->parent, data, length, &sent);
			*bytes_sent += sent;
		}
		return AFC_E_SUCCESS;
	}
	return AFC_E_INTERNAL_ERROR;
}
Пример #16
0
/** delayer service loop */
static void
service_loop(int udp_s, int listen_s, struct ringbuf* ring, 
	struct timeval* delay, struct timeval* reuse,
	struct sockaddr_storage* srv_addr, socklen_t srv_len, 
	sldns_buffer* pkt)
{
	fd_set rset, rorig;
	fd_set wset, worig;
	struct timeval now, wait;
	int max, have_wait = 0;
	struct proxy* proxies = NULL;
	struct tcp_proxy* tcp_proxies = NULL;
	struct timeval tcp_timeout;
	tcp_timeout.tv_sec = 120;
	tcp_timeout.tv_usec = 0;
#ifndef S_SPLINT_S
	FD_ZERO(&rorig);
	FD_ZERO(&worig);
	FD_SET(FD_SET_T udp_s, &rorig);
	FD_SET(FD_SET_T listen_s, &rorig);
#endif
	max = udp_s + 1;
	if(listen_s + 1 > max) max = listen_s + 1;
	while(!do_quit) {
		/* wait for events */
		rset = rorig;
		wset = worig;
		if(have_wait)
			verbose(1, "wait for %d.%6.6d",
			(unsigned)wait.tv_sec, (unsigned)wait.tv_usec);
		else	verbose(1, "wait");
		if(select(max, &rset, &wset, NULL, have_wait?&wait:NULL) < 0) {
			if(errno == EAGAIN || errno == EINTR)
				continue;
			fatal_exit("select: %s", strerror(errno));
		}
		/* get current time */
		if(gettimeofday(&now, NULL) < 0) {
			if(errno == EAGAIN || errno == EINTR)
				continue;
			fatal_exit("gettimeofday: %s", strerror(errno));
		}
		verbose(1, "process at %u.%6.6u\n", 
			(unsigned)now.tv_sec, (unsigned)now.tv_usec);
		/* sendout delayed queries to master server (frees up buffer)*/
		service_send(ring, &now, pkt, srv_addr, srv_len);
		/* proxy return replies */
		service_proxy(&rset, udp_s, proxies, pkt, &now);
		/* see what can be received to start waiting */
		service_recv(udp_s, ring, pkt, &rorig, &max, &proxies,
			srv_addr, srv_len, &now, delay, reuse);
		/* see if there are new tcp connections */
		service_tcp_listen(listen_s, &rorig, &max, &tcp_proxies,
			srv_addr, srv_len, &now, &tcp_timeout);
		/* service tcp connections */
		service_tcp_relay(&tcp_proxies, &now, delay, &tcp_timeout, 
			pkt, &rset, &rorig, &worig);
		/* see what next timeout is (if any) */
		have_wait = service_findwait(&now, &wait, ring, tcp_proxies);
	}
	proxy_list_clear(proxies);
	tcp_proxy_list_clear(tcp_proxies);
}
Пример #17
0
/**
 * Uploads an image to the device.
 *
 * @param client The connected mobile_image_mounter client.
 * @param image_type Type of image that is being uploaded.
 * @param image_size Total size of the image.
 * @param upload_cb Callback function that gets the data chunks for uploading
 *    the image.
 * @param userdata User defined data for the upload callback function.
 *
 * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on succes, or a
 *    MOBILE_IMAGE_MOUNTER_E_* error code otherwise.
 */
mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_mounter_client_t client, const char *image_type, size_t image_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata)
{
	if (!client || !image_type || (image_size == 0) || !upload_cb) {
		return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG;
	}
	mobile_image_mounter_lock(client);
	plist_t result = NULL;

	plist_t dict = plist_new_dict();
	plist_dict_set_item(dict, "Command", plist_new_string("ReceiveBytes"));
	plist_dict_set_item(dict, "ImageSize", plist_new_uint(image_size));
	plist_dict_set_item(dict, "ImageType", plist_new_string(image_type));

	mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict));
	plist_free(dict);

	if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
		debug_info("Error sending XML plist to device!");
		goto leave_unlock;
	}

	res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result));
	if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
		debug_info("Error receiving response from device!");
		goto leave_unlock;
	}
	res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED;

	char* strval = NULL;
	plist_t node = plist_dict_get_item(result, "Status");
	if (node && plist_get_node_type(node) == PLIST_STRING) {
		plist_get_string_val(node, &strval);
	}
	if (!strval) {
		debug_info("Error: Unexpected response received!");
		goto leave_unlock;
	}
	if (strcmp(strval, "ReceiveBytesAck") != 0) {
		debug_info("Error: didn't get ReceiveBytesAck but %s", strval);
		free(strval);
		goto leave_unlock;
	}
	free(strval);

	size_t tx = 0;
	size_t bufsize = 65536;
	unsigned char *buf = (unsigned char*)malloc(bufsize);
	if (!buf) {
		debug_info("Out of memory");
		res = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
		goto leave_unlock;
	}
	debug_info("uploading image (%d bytes)", (int)image_size);
	while (tx < image_size) {
		size_t remaining = image_size - tx;
		size_t amount = (remaining < bufsize) ? remaining : bufsize;
		ssize_t r = upload_cb(buf, amount, userdata);
		if (r < 0) {
			debug_info("upload_cb returned %d", (int)r);
			break;
		}
		uint32_t sent = 0;
		if (service_send(client->parent->parent, (const char*)buf, (uint32_t)r, &sent) != SERVICE_E_SUCCESS) {
			debug_info("service_send failed");
			break;
		}
		tx += r;
	}
	free(buf);
	if (tx < image_size) {
		debug_info("Error: failed to upload image");
		goto leave_unlock;
	}
	debug_info("image uploaded");

	res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result));
	if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
		debug_info("Error receiving response from device!");
		goto leave_unlock;
	}
	res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED;

	strval = NULL;
	node = plist_dict_get_item(result, "Status");
	if (node && plist_get_node_type(node) == PLIST_STRING) {
		plist_get_string_val(node, &strval);
	}
	if (!strval) {
		debug_info("Error: Unexpected response received!");	
		goto leave_unlock;
	}
	if (strcmp(strval, "Complete") != 0) {
		debug_info("Error: didn't get Complete but %s", strval);
		free(strval);
		goto leave_unlock;
	} else {
		res = MOBILE_IMAGE_MOUNTER_E_SUCCESS;
	}
	free(strval);
	

leave_unlock:
	mobile_image_mounter_unlock(client);
	if (result)
		plist_free(result);
	return res;

}
Пример #18
0
void handle_list(FILE* sock, bool details) {
    char pseudo_classes[PSEUDO_CLASSES_LEN];
    if(list_pseudo_classes(pseudo_classes, sizeof(pseudo_classes))) {
        die("Failed to enumerate pseudo classes");
    }

    char* output_text = malloc(1024 * 1024);
    if(output_text == NULL) { die("Allocating output buffer failed"); }

    service_send(&service_exec_ibuf, EXEC_IFCONFIG_LIST_INTERFACES, NULL);
    int32_t result = service_pop(&service_exec_ibuf, output_text, 1024 * 1024);
    if(result != EXEC_RESPONSE_OK) {
        flatjson_send_singleton(sock, "error");
        fputs("\n", sock);
        free(output_text);
        return;
    }

    bool first_message = true;
    flatjson_start_send(sock);
    flatjson_send(sock, "ok", &first_message);

    char* cursor;
    char iface[IF_NAMESIZE];
    bool skipping = false;
    while((cursor = strsep(&output_text, "\n")) != NULL) {
        char rendered[IF_NAMESIZE + 10];
        char key[IFCONFIG_KEY_LEN];
        char flags[FLAGS_LEN];
        int mtu;
        if(parse_ifconfig_header(cursor, iface, flags, &mtu)) {
            if(iface_is_pseudo(iface, pseudo_classes)) {
                skipping = true;
                continue;
            } else {
                if(!details) {
                    flatjson_send(sock, iface, &first_message);
                    continue;
                }
                skipping = false;
            }

            snprintf(rendered, sizeof(rendered), "%s.flags", iface);
            flatjson_send(sock, rendered, &first_message);
            flatjson_send(sock, flags, &first_message);
            snprintf(rendered, sizeof(rendered), "%s.mtu", iface);
            flatjson_send(sock, rendered, &first_message);
            snprintf(rendered, sizeof(rendered), "%d", mtu);
            flatjson_send(sock, rendered, &first_message);
            continue;
        }

        if(!skipping && details && parse_ifconfig_kv(cursor, key, flags)) {
            snprintf(rendered, sizeof(rendered), "%s.%s", iface, key);
            flatjson_send(sock, rendered, &first_message);
            flatjson_send(sock, flags, &first_message);
        }
    }

    flatjson_finish_send(sock);
    fprintf(sock, "\n");
    free(output_text);
}