Files
qemu-ios/contrib/ipod-emu/netd/ipod-netd.c
T

658 lines
19 KiB
C

#define SLIRP_DEPRECATED_OK 1
/*
* ipod-netd : réseau pour l'émulateur iPod Touch 2G, sans privilèges.
*
* Termine le lien PPP que pppd (côté iPhone OS) ouvre sur l'UART1 émulée, et passe les paquets IP à libslirp :
* NAT vers Internet, DNS (10.0.2.3), redirections de ports hôte -> iPod.
*
* ipod-netd [-s uart1.sock] [-L [addr:]hostport:guestport]... [-v]
*
* Adresses : iPod 10.0.2.15, passerelle 10.0.2.2, DNS 10.0.2.3. Défaut : -L 127.0.0.1:7788:7788 (emuagent).
*/
#include <arpa/inet.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include <slirp/libslirp.h>
#define GUEST_IP "10.0.2.15"
#define GW_IP "10.0.2.2"
#define DNS_IP "10.0.2.3"
static const uint8_t guest_mac[6] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
static const uint8_t slirp_mac[6] = { 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02 };
static int verbose;
static int uart = -1;
static const char *sock_path;
static Slirp *slirp;
static void logm(const char *fmt, ...)
{
va_list ap;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
fprintf(stderr, "[%ld.%03ld] ", (long)ts.tv_sec % 100000, ts.tv_nsec / 1000000);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
}
/* ---------- PPP : trames HDLC (RFC 1662) ---------- */
static uint16_t fcstab[256];
static void fcs_init(void)
{
for (int b = 0; b < 256; b++) {
uint16_t v = b;
for (int i = 0; i < 8; i++) {
v = (v & 1) ? (v >> 1) ^ 0x8408 : v >> 1;
}
fcstab[b] = v;
}
}
static uint16_t fcs16(uint16_t fcs, const uint8_t *p, size_t len)
{
while (len--) {
fcs = (fcs >> 8) ^ fcstab[(fcs ^ *p++) & 0xff];
}
return fcs;
}
static void ppp_send(uint16_t proto, const uint8_t *data, size_t len)
{
static uint8_t raw[4 + 2048], out[2 * (sizeof(raw) + 2) + 2];
if (uart < 0 || len > 2048) {
return;
}
raw[0] = 0xff; raw[1] = 0x03; raw[2] = proto >> 8; raw[3] = proto & 0xff;
memcpy(raw + 4, data, len);
size_t n = 4 + len;
uint16_t fcs = fcs16(0xffff, raw, n) ^ 0xffff;
raw[n++] = fcs & 0xff;
raw[n++] = fcs >> 8;
size_t o = 0;
out[o++] = 0x7e;
for (size_t i = 0; i < n; i++) {
uint8_t c = raw[i];
if (c < 0x20 || c == 0x7e || c == 0x7d) {
out[o++] = 0x7d;
c ^= 0x20;
}
out[o++] = c;
}
out[o++] = 0x7e;
for (size_t w = 0; w < o;) {
ssize_t r = write(uart, out + w, o - w);
if (r < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
return;
}
w += r;
}
}
/* ---------- LCP / IPCP ---------- */
enum { CONF_REQ = 1, CONF_ACK, CONF_NAK, CONF_REJ, TERM_REQ, TERM_ACK, CODE_REJ, PROTO_REJ, ECHO_REQ, ECHO_REP, DISC_REQ };
#define PROTO_IP 0x0021
#define PROTO_LCP 0xc021
#define PROTO_IPCP 0x8021
struct ncp {
const char *name;
uint16_t proto;
int acked_peer; /* on a acquitté la requête du pair */
int acked_ours; /* le pair a acquitté la nôtre */
uint8_t id;
time_t last_req;
};
static struct ncp lcp = { "LCP", PROTO_LCP }, ipcp = { "IPCP", PROTO_IPCP };
static uint32_t lcp_magic = 0x49504f44; /* "IPOD" */
static int is_open(struct ncp *n)
{
return n->acked_peer && n->acked_ours;
}
static void send_ctl(uint16_t proto, uint8_t code, uint8_t id, const uint8_t *opts, size_t len)
{
uint8_t pkt[1500];
pkt[0] = code; pkt[1] = id; pkt[2] = (len + 4) >> 8; pkt[3] = (len + 4) & 0xff;
memcpy(pkt + 4, opts, len);
ppp_send(proto, pkt, len + 4);
}
static void send_conf_req(struct ncp *n)
{
uint8_t opts[16];
size_t len = 0;
if (n == &ipcp) {
struct in_addr a;
inet_aton(GW_IP, &a);
opts[len++] = 3; opts[len++] = 6;
memcpy(opts + len, &a, 4); len += 4;
} else {
opts[len++] = 5; opts[len++] = 6;
uint32_t m = htonl(lcp_magic);
memcpy(opts + len, &m, 4); len += 4;
}
send_ctl(n->proto, CONF_REQ, ++n->id, opts, len);
n->last_req = time(NULL);
}
static void guest_announce(void);
static void ncp_opened(struct ncp *n)
{
logm("%s ouvert", n->name);
if (n == &lcp) {
send_conf_req(&ipcp);
} else {
logm("lien IP prêt : iPod %s, passerelle %s, DNS %s", GUEST_IP, GW_IP, DNS_IP);
guest_announce();
}
}
/* Traite une Configure-Request du pair : Ack / Nak / Rej, option par option (RFC 1661 §5.1) */
static void handle_conf_req(struct ncp *n, uint8_t id, const uint8_t *opts, size_t len)
{
uint8_t ack[1500], nak[1500], rej[1500];
size_t na = 0, nn = 0, nr = 0;
struct in_addr want_ip, want_dns;
inet_aton(GUEST_IP, &want_ip);
inet_aton(DNS_IP, &want_dns);
for (size_t i = 0; i + 2 <= len;) {
uint8_t type = opts[i], olen = opts[i + 1];
if (olen < 2 || i + olen > len) {
break;
}
const uint8_t *o = opts + i;
int reject = 0, naked = 0;
if (n == &lcp) {
switch (type) {
case 1: /* MRU */
case 2: /* ACCM : on échappe de toute façon tous les caractères de contrôle */
case 5: /* magic */
case 7: /* PFC */
case 8: /* ACFC */
break;
default: /* authentification, callback... */
reject = 1;
}
} else {
if ((type == 3 || type == 129 || type == 131) && olen == 6) {
const struct in_addr *want = type == 3 ? &want_ip : &want_dns;
if (memcmp(o + 2, want, 4)) {
memcpy(nak + nn, o, 2);
memcpy(nak + nn + 2, want, 4);
nn += 6;
naked = 1;
}
} else {
reject = 1; /* compression VJ, NBNS... */
}
}
if (reject) {
memcpy(rej + nr, o, olen); nr += olen;
} else if (!naked) {
memcpy(ack + na, o, olen); na += olen;
}
i += olen;
}
if (nr) {
send_ctl(n->proto, CONF_REJ, id, rej, nr);
} else if (nn) {
send_ctl(n->proto, CONF_NAK, id, nak, nn);
} else {
int was_open = is_open(n);
send_ctl(n->proto, CONF_ACK, id, ack, na);
n->acked_peer = 1;
if (!n->acked_ours) {
send_conf_req(n);
}
if (!was_open && is_open(n)) {
ncp_opened(n);
}
}
}
static void handle_ctl(struct ncp *n, const uint8_t *p, size_t len)
{
if (len < 4) {
return;
}
uint8_t code = p[0], id = p[1];
size_t plen = (p[2] << 8) | p[3];
if (plen < 4 || plen > len) {
return;
}
const uint8_t *data = p + 4;
size_t dlen = plen - 4;
if (verbose) {
logm("%s <- code %d id %d len %zu", n->name, code, id, dlen);
}
switch (code) {
case CONF_REQ:
if (n == &lcp && is_open(&lcp)) { /* renégociation : tout repart de zéro */
lcp.acked_ours = 0;
ipcp.acked_peer = ipcp.acked_ours = 0;
}
if (n == &ipcp && !is_open(&lcp)) {
return;
}
handle_conf_req(n, id, data, dlen);
break;
case CONF_ACK:
if (id == n->id && !n->acked_ours) {
n->acked_ours = 1;
if (is_open(n)) {
ncp_opened(n);
}
}
break;
case CONF_NAK:
case CONF_REJ:
/* nos options sont minimales : on les renvoie sans l'option refusée */
send_ctl(n->proto, CONF_REQ, ++n->id, NULL, 0);
break;
case TERM_REQ:
send_ctl(n->proto, TERM_ACK, id, NULL, 0);
logm("%s fermé par l'iPod", n->name);
n->acked_peer = n->acked_ours = 0;
if (n == &lcp) {
ipcp.acked_peer = ipcp.acked_ours = 0;
}
break;
case ECHO_REQ:
if (n == &lcp && dlen >= 4) {
uint8_t rep[1500];
uint32_t m = htonl(lcp_magic);
memcpy(rep, &m, 4);
memcpy(rep + 4, data + 4, dlen - 4);
send_ctl(PROTO_LCP, ECHO_REP, id, rep, dlen);
}
break;
default:
break;
}
}
/* ---------- IP <-> slirp (trames Ethernet fictives) ---------- */
static void log_ip(const char *dir, const uint8_t *ip, size_t len)
{
if (!verbose || len < 20) return;
char a[16], b[16];
snprintf(a, sizeof a, "%d.%d.%d.%d", ip[12], ip[13], ip[14], ip[15]);
snprintf(b, sizeof b, "%d.%d.%d.%d", ip[16], ip[17], ip[18], ip[19]);
int ihl = (ip[0] & 0xf) * 4, proto = ip[9];
if (proto == 1 && len > (size_t)ihl + 1) logm("%s ICMP %s -> %s type %d code %d", dir, a, b, ip[ihl], ip[ihl + 1]);
else if ((proto == 6 || proto == 17) && len >= (size_t)ihl + 4)
logm("%s %s %s:%d -> %s:%d len %zu", dir, proto == 6 ? "TCP" : "UDP", a, (ip[ihl] << 8) | ip[ihl + 1], b, (ip[ihl + 2] << 8) | ip[ihl + 3], len);
else logm("%s proto %d %s -> %s", dir, proto, a, b);
}
static void slirp_ip_input(const uint8_t *ip, size_t len)
{
log_ip("iPod ->", ip, len);
uint8_t frame[14 + 2048];
if (len > 2048) {
return;
}
memcpy(frame, slirp_mac, 6);
memcpy(frame + 6, guest_mac, 6);
frame[12] = 0x08; frame[13] = 0x00;
memcpy(frame + 14, ip, len);
slirp_input(slirp, frame, len + 14);
}
static void arp_packet(uint16_t op, const uint8_t *dst_mac, const uint8_t *target_ip)
{
uint8_t f[42];
struct in_addr gip;
inet_aton(GUEST_IP, &gip);
memcpy(f, dst_mac, 6);
memcpy(f + 6, guest_mac, 6);
f[12] = 0x08; f[13] = 0x06;
f[14] = 0; f[15] = 1; f[16] = 8; f[17] = 0; f[18] = 6; f[19] = 4;
f[20] = 0; f[21] = op;
memcpy(f + 22, guest_mac, 6);
memcpy(f + 28, &gip, 4);
memcpy(f + 32, op == 2 ? dst_mac : (const uint8_t *)"\0\0\0\0\0\0", 6);
memcpy(f + 38, target_ip, 4);
slirp_input(slirp, f, sizeof f);
}
/* ARP gratuit : slirp apprend l'adresse MAC de l'iPod */
static void guest_announce(void)
{
static const uint8_t bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
struct in_addr gw;
inet_aton(GW_IP, &gw);
arp_packet(1, bcast, (const uint8_t *)&gw);
}
static slirp_ssize_t slirp_send(const void *buf, size_t len, void *opaque)
{
const uint8_t *f = buf;
if (len < 14) {
return len;
}
uint16_t type = (f[12] << 8) | f[13];
if (type == 0x0806 && len >= 42 && f[21] == 1) {
struct in_addr gip;
inet_aton(GUEST_IP, &gip);
if (!memcmp(f + 38, &gip, 4)) {
arp_packet(2, f + 22, f + 28);
}
} else if (type == 0x0800 && is_open(&ipcp)) {
log_ip("-> iPod", f + 14, len - 14);
ppp_send(PROTO_IP, f + 14, len - 14);
}
return len;
}
/* ---------- réception PPP ---------- */
static void ppp_frame(uint8_t *p, size_t len)
{
if (len < 4 || fcs16(0xffff, p, len) != 0xf0b8) {
if (verbose && len) {
logm("trame invalide (%zu octets)", len);
}
return;
}
len -= 2;
if (len >= 2 && p[0] == 0xff && p[1] == 0x03) {
p += 2; len -= 2;
}
if (!len) {
return;
}
uint16_t proto;
if (p[0] & 1) {
proto = p[0]; p += 1; len -= 1;
} else {
if (len < 2) {
return;
}
proto = (p[0] << 8) | p[1]; p += 2; len -= 2;
}
switch (proto) {
case PROTO_LCP:
handle_ctl(&lcp, p, len);
break;
case PROTO_IPCP:
if (is_open(&lcp)) {
handle_ctl(&ipcp, p, len);
}
break;
case PROTO_IP:
if (is_open(&ipcp)) {
slirp_ip_input(p, len);
}
break;
default:
if (is_open(&lcp)) { /* IPV6CP, CCP... : Protocol-Reject */
uint8_t rej[1500];
size_t n = len > 1400 ? 1400 : len;
rej[0] = proto >> 8; rej[1] = proto & 0xff;
memcpy(rej + 2, p, n);
send_ctl(PROTO_LCP, PROTO_REJ, ++lcp.id, rej, n + 2);
}
}
}
static void uart_input(const uint8_t *buf, size_t n)
{
static uint8_t frame[4096];
static size_t flen;
static int esc;
for (size_t i = 0; i < n; i++) {
uint8_t c = buf[i];
if (c == 0x7e) {
ppp_frame(frame, flen);
flen = 0; esc = 0;
} else if (c == 0x7d) {
esc = 1;
} else {
if (esc) {
c ^= 0x20; esc = 0;
}
if (flen < sizeof frame) {
frame[flen++] = c;
}
}
}
}
/* ---------- boucle principale, timers slirp ---------- */
struct timer { SlirpTimerCb cb; void *cb_opaque; SlirpTimerId id; int by_id; int64_t expire; int used; };
static struct timer timers[16];
static int64_t now_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000LL + ts.tv_nsec;
}
static int64_t cb_clock(void *o) { return now_ns(); }
static void cb_guest_error(const char *msg, void *o) { logm("slirp : %s", msg); }
static void cb_notify(void *o) {}
static void cb_register_poll_fd(int fd, void *o) {} /* on repasse tous les sockets à poll() à chaque tour */
static void cb_unregister_poll_fd(int fd, void *o) {}
static struct timer *timer_alloc(void)
{
for (int i = 0; i < 16; i++) {
if (!timers[i].used) {
memset(&timers[i], 0, sizeof timers[i]);
timers[i].used = 1;
timers[i].expire = -1;
return &timers[i];
}
}
abort();
}
static void *cb_timer_new(SlirpTimerCb cb, void *cb_opaque, void *o)
{
struct timer *t = timer_alloc();
t->cb = cb; t->cb_opaque = cb_opaque;
return t;
}
static void *cb_timer_new_opaque(SlirpTimerId id, void *cb_opaque, void *o)
{
struct timer *t = timer_alloc();
t->id = id; t->by_id = 1; t->cb_opaque = cb_opaque;
return t;
}
static void cb_timer_free(void *t, void *o) { ((struct timer *)t)->used = 0; }
static void cb_timer_mod(void *t, int64_t expire_ms, void *o) { ((struct timer *)t)->expire = expire_ms; }
static struct pollfd pfds[1024];
static int npfds;
static int add_poll(slirp_os_socket fd, int events, void *o)
{
short ev = 0;
if (events & SLIRP_POLL_IN) ev |= POLLIN;
if (events & SLIRP_POLL_OUT) ev |= POLLOUT;
if (events & SLIRP_POLL_PRI) ev |= POLLPRI;
pfds[npfds].fd = fd;
pfds[npfds].events = ev;
pfds[npfds].revents = 0;
return npfds++;
}
static int get_revents(int idx, void *o)
{
short r = pfds[idx].revents;
int ev = 0;
if (r & POLLIN) ev |= SLIRP_POLL_IN;
if (r & POLLOUT) ev |= SLIRP_POLL_OUT;
if (r & POLLPRI) ev |= SLIRP_POLL_PRI;
if (r & POLLERR) ev |= SLIRP_POLL_ERR;
if (r & POLLHUP) ev |= SLIRP_POLL_HUP;
return ev;
}
static int uart_connect(void)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un a = { .sun_family = AF_UNIX };
strncpy(a.sun_path, sock_path, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) {
close(fd);
return -1;
}
return fd;
}
static void add_fwd(const char *spec)
{
char buf[128], *parts[3];
int n = 0;
strncpy(buf, spec, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
for (char *t = strtok(buf, ":"); t && n < 3; t = strtok(NULL, ":")) {
parts[n++] = t;
}
struct in_addr host, guest;
inet_aton("127.0.0.1", &host);
inet_aton(GUEST_IP, &guest);
int hp, gp;
if (n == 3) {
inet_aton(parts[0], &host);
hp = atoi(parts[1]); gp = atoi(parts[2]);
} else if (n == 2) {
hp = atoi(parts[0]); gp = atoi(parts[1]);
} else {
fprintf(stderr, "redirection invalide : %s\n", spec);
exit(1);
}
if (slirp_add_hostfwd(slirp, 0, host, hp, guest, gp) < 0) {
logm("redirection %s impossible (port occupé ?)", spec);
/* Ne pas laisser le client contacter le relais d'une autre session. */
exit(1);
} else {
logm("redirection tcp %s:%d -> iPod:%d", inet_ntoa(host), hp, gp);
}
}
int main(int argc, char **argv)
{
const char *fwds[32];
int nfwd = 0;
static char default_sock[512];
snprintf(default_sock, sizeof default_sock, "%s/ipod-emu/run/uart1.sock", getenv("HOME"));
sock_path = default_sock;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-s") && i + 1 < argc) sock_path = argv[++i];
else if (!strcmp(argv[i], "-L") && i + 1 < argc && nfwd < 32) fwds[nfwd++] = argv[++i];
else if (!strcmp(argv[i], "-v")) verbose = 1;
else { fprintf(stderr, "usage: %s [-s uart1.sock] [-L [addr:]hostport:guestport]... [-v]\n", argv[0]); return 1; }
}
if (!nfwd) fwds[nfwd++] = "127.0.0.1:7788:7788";
signal(SIGPIPE, SIG_IGN);
fcs_init();
SlirpConfig cfg = { 0 };
cfg.version = 4;
cfg.in_enabled = true;
inet_aton("10.0.2.0", &cfg.vnetwork);
inet_aton("255.255.255.0", &cfg.vnetmask);
inet_aton(GW_IP, &cfg.vhost);
inet_aton(DNS_IP, &cfg.vnameserver);
inet_aton(GUEST_IP, &cfg.vdhcp_start);
cfg.vhostname = "ipod-emu-host";
cfg.if_mtu = 1500;
cfg.if_mru = 1500;
static const SlirpCb cb = {
.send_packet = slirp_send, .guest_error = cb_guest_error, .clock_get_ns = cb_clock,
.timer_new = cb_timer_new, .timer_free = cb_timer_free, .timer_mod = cb_timer_mod,
.notify = cb_notify, .timer_new_opaque = cb_timer_new_opaque,
.register_poll_fd = cb_register_poll_fd, .unregister_poll_fd = cb_unregister_poll_fd,
};
slirp = slirp_new(&cfg, &cb, NULL);
for (int i = 0; i < nfwd; i++) add_fwd(fwds[i]);
for (;;) {
if (uart < 0) {
uart = uart_connect();
if (uart < 0) {
sleep(1);
continue;
}
logm("connecté à %s, en attente de pppd", sock_path);
lcp.acked_peer = lcp.acked_ours = ipcp.acked_peer = ipcp.acked_ours = 0;
}
uint32_t timeout = 1000;
npfds = 0;
int uidx = add_poll(uart, SLIRP_POLL_IN, NULL);
slirp_pollfds_fill_socket(slirp, &timeout, add_poll, NULL);
int64_t now_ms = now_ns() / 1000000;
for (int i = 0; i < 16; i++) {
if (timers[i].used && timers[i].expire >= 0) {
int64_t d = timers[i].expire - now_ms;
if (d < 0) d = 0;
if (d < timeout) timeout = d;
}
}
int r = poll(pfds, npfds, timeout);
slirp_pollfds_poll(slirp, r < 0, get_revents, NULL);
now_ms = now_ns() / 1000000;
for (int i = 0; i < 16; i++) {
struct timer *t = &timers[i];
if (t->used && t->expire >= 0 && t->expire <= now_ms) {
t->expire = -1;
if (t->by_id) slirp_handle_timer(slirp, t->id, t->cb_opaque);
else t->cb(t->cb_opaque);
}
}
if (r > 0 && (pfds[uidx].revents & (POLLIN | POLLHUP | POLLERR))) {
uint8_t buf[4096];
ssize_t n = read(uart, buf, sizeof buf);
if (n <= 0) {
logm("UART déconnectée (émulateur arrêté ?)");
close(uart);
uart = -1;
continue;
}
uart_input(buf, n);
}
/* relance des Configure-Request non acquittées */
time_t t = time(NULL);
if (is_open(&lcp) && !ipcp.acked_ours && t - ipcp.last_req >= 2) send_conf_req(&ipcp);
if (lcp.acked_peer && !lcp.acked_ours && t - lcp.last_req >= 2) send_conf_req(&lcp);
}
}