mirror of
https://github.com/vincentmli/bpfire.git
synced 2026-04-25 10:22:59 +02:00
665 lines
13 KiB
C
665 lines
13 KiB
C
/*
|
|
* wioscan
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <poll.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <netpacket/packet.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <netinet/ether.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdint.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
|
|
#define _STR(S) #S
|
|
#define STR(S) _STR(S)
|
|
|
|
#define ARP htons(ETHERTYPE_ARP)
|
|
#define IP htons(ETHERTYPE_IP)
|
|
|
|
typedef uint8_t u8;
|
|
typedef uint16_t u16;
|
|
typedef uint32_t u32;
|
|
|
|
#include "list.h"
|
|
#define elemof(T) (sizeof T/sizeof*T)
|
|
#define endof(T) (T+elemof(T))
|
|
#ifndef offsetof
|
|
#define offsetof(T,M) ((int)(long)&((T*)0)->M)
|
|
#endif
|
|
|
|
#define HWMAX 8
|
|
|
|
union addr {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in in;
|
|
struct sockaddr_ll ll;
|
|
};
|
|
|
|
int sock; /* packet socket */
|
|
union addr bcast;
|
|
|
|
struct opts {
|
|
unsigned sort:1;
|
|
unsigned noown:1;
|
|
unsigned noethn:1;
|
|
unsigned proui:1;
|
|
unsigned isrange:1;
|
|
unsigned passive:1;
|
|
unsigned nsend;
|
|
unsigned wait;
|
|
} opts = {nsend:8, wait:250};
|
|
|
|
void print_oui(int sp, u8 a[6]);
|
|
|
|
struct he;
|
|
void print_he(struct he *he);
|
|
|
|
struct hwaddr {
|
|
u8 len, addr[HWMAX];
|
|
};
|
|
|
|
static inline hw_eq(struct hwaddr *h, int hl, u8 *ha)
|
|
{
|
|
return h->len == hl && memcmp(h->addr, ha, hl) == 0;
|
|
}
|
|
|
|
static inline void hw_set(struct hwaddr *h, int hl, u8 *ha)
|
|
{
|
|
memcpy(h->addr, ha, (h->len = hl));
|
|
}
|
|
|
|
struct ifinfo {
|
|
int index;
|
|
char *name;
|
|
u32 ip, net, mask, bcast;
|
|
u16 hw_type;
|
|
struct hwaddr hw;
|
|
} ifinfo;
|
|
|
|
static inline u32 ip_from_sa(struct sockaddr *sa)
|
|
{
|
|
return ntohl(((struct sockaddr_in*)sa)->sin_addr.s_addr);
|
|
}
|
|
|
|
/* TABLE */
|
|
|
|
struct list hashtbl[128];
|
|
struct he {
|
|
struct list hash;
|
|
u32 ip;
|
|
struct hwaddr hw;
|
|
struct hwaddr from;
|
|
};
|
|
|
|
static void init_hash() __attribute__((constructor));
|
|
static void init_hash()
|
|
{
|
|
int i;
|
|
for(i=0;i<elemof(hashtbl);i++) list_init(&hashtbl[i]);
|
|
}
|
|
|
|
int he_for(u32 ip, struct he **ret, int alloc)
|
|
{
|
|
struct list *h, *l;
|
|
struct he *he;
|
|
int v = 1;
|
|
h = &hashtbl[ip & elemof(hashtbl)-1];
|
|
for(l=h->next; l!=h; l=l->next) {
|
|
he = list_entry(l, struct he, hash);
|
|
if(he->ip == ip)
|
|
goto ret;
|
|
if(he->ip > ip)
|
|
break;
|
|
}
|
|
v = 0;
|
|
if(alloc) {
|
|
he = (struct he*)malloc(sizeof *he);
|
|
he->ip = ip;
|
|
list_add(l->prev, &he->hash);
|
|
ret:
|
|
if(ret) *ret = he;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
/* INTERFACE */
|
|
|
|
static int net;
|
|
|
|
static void my__ioctl(int i, struct ifreq *r, char *t)
|
|
{
|
|
if(ioctl(net, i, r) < 0)
|
|
err(1, "ioctl(%s,%s)", t, r->ifr_name);
|
|
}
|
|
#define my_ioctl(I,R) my__ioctl(I,R,#I)
|
|
|
|
void fill_ifinfo(char *name)
|
|
{
|
|
struct ifreq ir;
|
|
int flags;
|
|
|
|
ifinfo.index = if_nametoindex(name);
|
|
if(!ifinfo.index) errx(1, "No such interface: %s", name);
|
|
ifinfo.name = name;
|
|
|
|
net = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if(net<0) err(1, "socket(PF_INET)");
|
|
strcpy(ir.ifr_name, ifinfo.name);
|
|
my_ioctl(SIOCGIFFLAGS, &ir);
|
|
flags = ir.ifr_flags;
|
|
if(flags & IFF_NOARP) errx(1, "%s: ARP not supported.", name);
|
|
my_ioctl(SIOCGIFADDR, &ir);
|
|
ifinfo.ip = ip_from_sa(&ir.ifr_addr);
|
|
if(flags & IFF_POINTOPOINT) {
|
|
my_ioctl(SIOCGIFDSTADDR, &ir);
|
|
ifinfo.net = ip_from_sa(&ir.ifr_dstaddr);
|
|
ifinfo.mask = (u32)~0;
|
|
ifinfo.bcast = 0; /* none */
|
|
} else {
|
|
my_ioctl(SIOCGIFNETMASK, &ir);
|
|
ifinfo.mask = ip_from_sa(&ir.ifr_netmask);
|
|
my_ioctl(SIOCGIFBRDADDR, &ir);
|
|
ifinfo.bcast = ip_from_sa(&ir.ifr_broadaddr);
|
|
ifinfo.net = ifinfo.ip & ifinfo.mask;
|
|
}
|
|
close(net);
|
|
}
|
|
|
|
static inline char *str_ip(u32 ip)
|
|
{
|
|
struct in_addr n;
|
|
n.s_addr = htonl(ip);
|
|
return inet_ntoa(n);
|
|
}
|
|
|
|
char *str_hw(u8 *a, int l)
|
|
{
|
|
static char buf[3*HWMAX];
|
|
char *d = buf;
|
|
if(!l) return "*";
|
|
if(l>HWMAX) l=HWMAX;
|
|
for(;;) {
|
|
d += sprintf(d, "%02X", *a++);
|
|
if(--l <= 0) break;
|
|
*d++ = ':';
|
|
}
|
|
*d = 0;
|
|
return buf;
|
|
}
|
|
|
|
static char *str_addr(union addr *addr)
|
|
{
|
|
switch(addr->sa.sa_family) {
|
|
case AF_INET: return inet_ntoa(addr->in.sin_addr);
|
|
case AF_PACKET: return str_hw(addr->ll.sll_addr, addr->ll.sll_halen);
|
|
default: return "???";
|
|
}
|
|
}
|
|
|
|
static inline void setup_socket()
|
|
{
|
|
union addr addr;
|
|
socklen_t l;
|
|
|
|
sock = socket(PF_PACKET, SOCK_DGRAM, 0);
|
|
if(sock < 0) err(1, "socket(PF_PACKET)");
|
|
|
|
memset(&addr.ll, 0, sizeof addr.ll);
|
|
addr.sa.sa_family = AF_PACKET;
|
|
addr.ll.sll_protocol = ARP;
|
|
addr.ll.sll_ifindex = ifinfo.index;
|
|
|
|
if(bind(sock, &addr.sa, sizeof addr.ll)<0)
|
|
err(1, "bind");
|
|
l = sizeof addr.ll;
|
|
if(getsockname(sock, &addr.sa, &l)<0)
|
|
err(1, "getsockname");
|
|
|
|
if(addr.ll.sll_halen > HWMAX)
|
|
errx(1, "hardware address too long (%d)", addr.ll.sll_halen);
|
|
ifinfo.hw.len = addr.ll.sll_halen;
|
|
memcpy(ifinfo.hw.addr, addr.ll.sll_addr, sizeof ifinfo.hw.addr);
|
|
ifinfo.hw_type = addr.ll.sll_hatype;
|
|
}
|
|
|
|
/* SCAN */
|
|
|
|
struct arppkt {
|
|
u16 hrd, pro;
|
|
u8 hln, pln;
|
|
u16 op;
|
|
u8 a[2*HWMAX+2*4];
|
|
/* u8 sha[6];
|
|
u8 sip[4];
|
|
u8 tha[6];
|
|
u8 tip[4];*/
|
|
};
|
|
|
|
static inline u8 *get_sha(struct arppkt *pkt) {return pkt->a;}
|
|
static inline u8 *get_tha(struct arppkt *pkt) {return pkt->a+pkt->hln+4;}
|
|
static inline u32 get_sip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+pkt->hln));}
|
|
static inline u32 get_tip(struct arppkt *pkt) {return ntohl(*(u32*)(pkt->a+2*pkt->hln+4));}
|
|
|
|
#if 0
|
|
void print_arp(struct arppkt *arp)
|
|
{
|
|
u8 *p = arp->a;
|
|
printf("hrd:%04X pro:%04X ", ntohs(arp->hrd), ntohs(arp->pro));
|
|
printf("hln:%d pln:%d op:%d ", arp->hln, arp->pln, ntohs(arp->op));
|
|
printf("sha:%s ", str_hw(p, arp->hln)); p+=arp->hln;
|
|
printf("sip:%s ", str_ip(ntohl(*(u32*)p))); p+=arp->pln;
|
|
printf("tha:%s ", str_hw(p, arp->hln)); p+=arp->hln;
|
|
printf("tip:%s\n", str_ip(ntohl(*(u32*)p)));
|
|
}
|
|
#endif
|
|
|
|
static struct scan {
|
|
u32 ip, start, end;
|
|
} scan;
|
|
|
|
#define IN_RANGE(I) ((I) >= scan.start && (I) <= (u32)(scan.end-1))
|
|
|
|
int sendscan()
|
|
{
|
|
struct arppkt arp;
|
|
int ns;
|
|
u8 *p;
|
|
|
|
arp.hrd = htons(ifinfo.hw_type);
|
|
arp.pro = IP;
|
|
arp.hln = ifinfo.hw.len;
|
|
arp.pln = 4;
|
|
arp.op = htons(1);
|
|
p = arp.a;
|
|
memcpy(p, ifinfo.hw.addr, ifinfo.hw.len); p += ifinfo.hw.len;
|
|
*(u32*)p = htonl(ifinfo.ip); p += 4;
|
|
memset(p, 0, ifinfo.hw.len); p += ifinfo.hw.len;
|
|
|
|
ns = 0;
|
|
while(scan.ip != scan.end) {
|
|
int v;
|
|
if(scan.ip == ifinfo.bcast || he_for(scan.ip, 0, 0)) {
|
|
scan.ip++;
|
|
continue;
|
|
}
|
|
*(u32*)p = htonl(scan.ip);
|
|
v = sendto(sock, &arp, p+4-(u8*)&arp, 0, &bcast.sa, sizeof bcast.ll);
|
|
if(v<0) {
|
|
if(errno != ENOBUFS || opts.nsend <= 1)
|
|
err(1, "send(%s)", str_addr(&bcast));
|
|
opts.nsend--;
|
|
return -1;
|
|
}
|
|
scan.ip++;
|
|
if(++ns >= opts.nsend) break;
|
|
}
|
|
return ns;
|
|
}
|
|
|
|
void compare_resp(struct he *he, union addr *src, int hln, u8 *sha)
|
|
{
|
|
if(hw_eq(&he->hw, hln, sha)
|
|
&& hw_eq(&he->from, src->ll.sll_halen, src->ll.sll_addr))
|
|
return;
|
|
|
|
fprintf(stderr, "%s: ", str_ip(he->ip));
|
|
fprintf(stderr, "inconsistency: %s", str_hw(sha, hln));
|
|
if(src->ll.sll_halen != hln || memcmp(src->ll.sll_addr, sha, hln))
|
|
fprintf(stderr, " from %s",
|
|
str_hw(src->ll.sll_addr, src->ll.sll_halen));
|
|
fprintf(stderr, ", was %s\n", str_hw(he->hw.addr, he->hw.len));
|
|
if(!hw_eq(&he->hw, he->from.len, he->from.addr))
|
|
fprintf(stderr, " from %s",
|
|
str_hw(he->from.addr, he->from.len));
|
|
}
|
|
|
|
int arp_recv(struct arppkt *pkt, union addr *src)
|
|
{
|
|
socklen_t l = sizeof *src;
|
|
int v = recvfrom(sock, pkt, sizeof *pkt, 0, &src->sa, &l);
|
|
if(v < 0) err(1, "recvfrom");
|
|
if(v < offsetof(struct arppkt, a))
|
|
return 0;
|
|
if(pkt->pro != IP)
|
|
return 0;
|
|
if(pkt->hrd != htons(ifinfo.hw_type) || pkt->hln != ifinfo.hw.len)
|
|
return 0;
|
|
if(v < offsetof(struct arppkt, a) + 2*pkt->hln + 2*4)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
void receive()
|
|
{
|
|
union addr addr;
|
|
struct arppkt arp;
|
|
struct he *he;
|
|
u32 ip;
|
|
|
|
if(!arp_recv(&arp, &addr))
|
|
return;
|
|
if(arp.op != htons(2)) /* only responses */
|
|
return;
|
|
|
|
ip = get_sip(&arp);
|
|
|
|
if(!he_for(ip, &he, 1)) {
|
|
hw_set(&he->hw, arp.hln, get_sha(&arp));
|
|
hw_set(&he->from, addr.ll.sll_halen, addr.ll.sll_addr);
|
|
if(opts.sort) return;
|
|
if(opts.isrange && !IN_RANGE(ip)) return;
|
|
print_he(he);
|
|
} else
|
|
compare_resp(he, &addr, arp.hln, get_sha(&arp));
|
|
}
|
|
|
|
/**/
|
|
|
|
void passive()
|
|
{
|
|
for(;;) {
|
|
struct arppkt arp;
|
|
union addr src;
|
|
if(!arp_recv(&arp, &src))
|
|
continue;
|
|
printf("%s: ", str_addr(&src));
|
|
printf("%s %-15s ", str_hw(get_sha(&arp),arp.hln),
|
|
str_ip(get_sip(&arp)));
|
|
switch(htons(arp.op)) {
|
|
case 1:
|
|
printf("Q %s", str_ip(get_tip(&arp)));
|
|
break;
|
|
case 2:
|
|
printf("A %s %s", str_hw(get_tha(&arp),arp.hln),
|
|
str_ip(get_tip(&arp)));
|
|
break;
|
|
default:
|
|
printf("%X", htons(arp.op));
|
|
}
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
/**/
|
|
|
|
int waitsock(int n)
|
|
{
|
|
int v;
|
|
struct pollfd pollfd;
|
|
pollfd.fd = sock;
|
|
pollfd.events = POLLIN;
|
|
v = poll(&pollfd, 1, n);
|
|
if(v < 0) {
|
|
if(errno != EINTR)
|
|
err(1, "poll");
|
|
v = 0;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void print_he(struct he *he)
|
|
{
|
|
int l, w;
|
|
if(opts.noown && he->ip == ifinfo.ip)
|
|
return;
|
|
printf("%s,", str_hw(he->hw.addr, he->hw.len));
|
|
l = 15 - printf("%s", str_ip(he->ip));
|
|
w = 0;
|
|
if(!opts.proui && !hw_eq(&he->from, he->hw.len, he->hw.addr))
|
|
w = 1, l = 1;
|
|
|
|
if(opts.proui)
|
|
print_oui(l, he->hw.addr);
|
|
else if(!opts.noethn) {
|
|
#if !defined __dietlibc_ && !defined __UCLIBC__
|
|
char nm[1024];
|
|
if(!ether_ntohost(nm, (struct ether_addr*)he->hw.addr))
|
|
printf("%*s%s", l, "", nm);
|
|
#endif
|
|
}
|
|
if(w)
|
|
printf(" from %s", str_hw(he->from.addr, he->from.len));
|
|
putchar('\n');
|
|
}
|
|
|
|
static int parse_iprange(char *p)
|
|
{
|
|
char *e;
|
|
u32 ip=0;
|
|
int sh;
|
|
|
|
for(sh = 24;; sh -= 8) {
|
|
unsigned long v;
|
|
|
|
v = strtoul(p, &e, 10);
|
|
if(p == e || v > 255)
|
|
return 0;
|
|
|
|
ip |= v << sh;
|
|
|
|
p = e + 1;
|
|
if(*e == '/') {
|
|
v = strtoul(p, &e, 10);
|
|
if(p == e || *e || v > 32)
|
|
return 0;
|
|
if(v) {
|
|
v = 32 - v;
|
|
if(sh > v)
|
|
return 0;
|
|
mask:
|
|
v = ~0 << v;
|
|
}
|
|
scan.start = ip & v;
|
|
scan.end = scan.start - v;
|
|
return 1;
|
|
}
|
|
|
|
if(!sh) break;
|
|
|
|
v = sh;
|
|
if(!*e)
|
|
goto mask;
|
|
|
|
if(*e != '.')
|
|
return 0;
|
|
|
|
if(!*p || *p == '*' && !p[1])
|
|
goto mask;
|
|
}
|
|
|
|
scan.start = ip;
|
|
scan.end = ip + 1;
|
|
|
|
if(*e == '-') {
|
|
u32 end = 0, m = ~0;
|
|
|
|
do {
|
|
unsigned long v = strtoul(p, &e, 10);
|
|
if(p == e || v > 255)
|
|
return 0;
|
|
p = e + 1;
|
|
end = end<<8 | v;
|
|
m <<= 8;
|
|
} while(m && *e);
|
|
|
|
if(*e)
|
|
return 0;
|
|
|
|
end |= ip & m;
|
|
if(end < ip)
|
|
return 0;
|
|
|
|
scan.end = end + 1;
|
|
return 1;
|
|
}
|
|
return *e == 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
for(;;) switch(getopt(argc, argv, "fsaepwlh")) {
|
|
case 'f': opts.sort=0; break;
|
|
case 's': opts.sort=1; break;
|
|
case 'a': opts.noown=1; break;
|
|
case 'e': opts.noethn=1; break;
|
|
case 'p': opts.proui=1; break;
|
|
case 'w': opts.nsend=2; opts.wait=1000; break;
|
|
case 'l': opts.passive=1; break;
|
|
case 'h':
|
|
printf(
|
|
"wioscan [-faep] [interface] [ip-range]\n"
|
|
"\t-s sort responses\n"
|
|
"\t-a do not list interface's own address\n"
|
|
#if !defined __dietlibc_ && !defined __UCLIBC__
|
|
"\t-e do not include info from /etc/ethers\n"
|
|
#endif
|
|
"\t-p print vendor names\n"
|
|
"\t-w slow operation\n"
|
|
"\t-l listen only (not promiscuous)\n"
|
|
"ip-range: ip ip/bits ip-ip\n"
|
|
);
|
|
return 0;
|
|
case EOF:
|
|
goto endopt;
|
|
}
|
|
endopt:
|
|
|
|
{
|
|
char *dev = "eth0";
|
|
if(optind<argc && (*argv[optind] < '0' || *argv[optind] > '9'))
|
|
dev = argv[optind++];
|
|
fill_ifinfo(dev);
|
|
setup_socket();
|
|
}
|
|
|
|
if(optind>=argc) {
|
|
scan.start = ifinfo.net;
|
|
scan.end = (ifinfo.net | ~ifinfo.mask) + 1;
|
|
} else {
|
|
if(!parse_iprange(argv[optind]))
|
|
errx(1, "%s: bad IP range", argv[optind]);
|
|
opts.isrange = 1;
|
|
}
|
|
|
|
if(ifinfo.hw_type != ARPHRD_ETHER)
|
|
opts.proui = 0, opts.noethn = 1;
|
|
|
|
if(opts.passive)
|
|
passive();
|
|
|
|
/* hw broadcast address is Linux's secret, this works with Ethernet */
|
|
bcast.sa.sa_family = AF_PACKET;
|
|
bcast.ll.sll_protocol = ARP;
|
|
bcast.ll.sll_ifindex = ifinfo.index;
|
|
bcast.ll.sll_hatype = ifinfo.hw_type;
|
|
bcast.ll.sll_pkttype = PACKET_BROADCAST; /* unused :-( */
|
|
bcast.ll.sll_halen = ifinfo.hw.len;
|
|
memset(bcast.ll.sll_addr, 0xFF, ifinfo.hw.len);
|
|
|
|
if(IN_RANGE(ifinfo.ip)) {
|
|
/* XXX we should add all our arpable addresses on the interface */
|
|
struct he *he;
|
|
he_for(ifinfo.ip, &he, 1);
|
|
hw_set(&he->hw, ifinfo.hw.len, ifinfo.hw.addr);
|
|
hw_set(&he->from, ifinfo.hw.len, ifinfo.hw.addr);
|
|
if(!opts.sort)
|
|
print_he(he);
|
|
}
|
|
|
|
/* 1st scan */
|
|
scan.ip = scan.start;
|
|
while(sendscan()) {
|
|
while(waitsock(10))
|
|
receive();
|
|
}
|
|
/* 2nd scan */
|
|
scan.ip = scan.start;
|
|
while(sendscan()) {
|
|
while(waitsock(10))
|
|
receive();
|
|
}
|
|
while(waitsock(opts.wait))
|
|
receive();
|
|
|
|
if(opts.sort) for(scan.ip = ifinfo.net; scan.ip != scan.end; scan.ip++) {
|
|
struct he *he;
|
|
if(he_for(scan.ip, &he, 0))
|
|
print_he(he);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef uint8_t u8;
|
|
static int fd = -2;
|
|
static char *ouiptr, *ouiend;
|
|
|
|
static void open_oui()
|
|
{
|
|
struct stat st;
|
|
fd = open("oui", O_RDONLY);
|
|
if(fd < 0) {
|
|
fd = open(STR(OUI), O_RDONLY);
|
|
if(fd < 0) goto err;
|
|
}
|
|
if(fstat(fd, &st) < 0 || st.st_size == 0) goto err_cl;
|
|
ouiptr = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
ouiend = ouiptr + st.st_size;
|
|
if(ouiptr == MAP_FAILED) {
|
|
err_cl:
|
|
close(fd); fd=-1;
|
|
err:
|
|
warnx("Can't open OUI database");
|
|
return;
|
|
}
|
|
#ifdef MADV_SEQUENTIAL
|
|
madvise(ouiptr, st.st_size, MADV_SEQUENTIAL);
|
|
#endif
|
|
}
|
|
|
|
void print_oui(int sp, u8 a[6])
|
|
{
|
|
char addr[7], *p, *q;
|
|
if(fd < 0) {
|
|
if(fd == -2)
|
|
open_oui();
|
|
if(fd < 0)
|
|
return;
|
|
}
|
|
sprintf(addr, "%02X%02X%02X", a[0], a[1], a[2]);
|
|
|
|
for(p=ouiptr; p<ouiend; p=q+1) {
|
|
q = memchr(p, '\n', ouiend-p);
|
|
if(!q) q=ouiend;
|
|
if(q-p < 8 || memcmp(p, addr, 6))
|
|
continue;
|
|
|
|
p += 7;
|
|
print:
|
|
printf("%*s%.*s", sp, "", (int)(q-p), p);
|
|
return;
|
|
}
|
|
if(a[0]==0 && a[1]==0xFF) {
|
|
p = "(generated)";
|
|
q = p + 11;
|
|
goto print;
|
|
}
|
|
} |