Set mac address via ioctl(3)

I want to be able change my NIC mac address with my own utility (ifconfig does the trick but it's too big for my project), so after hours of reading ifconfig source code utility, and of course a lot of googling, I end with this:
Code:
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mac.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>

/* IP */
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <ifaddrs.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct afswtch {
	const char	*af_name;	/* as given on cmd line, e.g. "inet" */
	short		af_af;		/* AF_* */
	/*
	 * Status is handled one of two ways; if there is an
	 * address associated with the interface then the
	 * associated address family af_status method is invoked
	 * with the appropriate addressin info.  Otherwise, if
	 * all possible info is to be displayed and af_other_status
	 * is defined then it is invoked after all address status
	 * is presented.
	 */
	void		(*af_status)(int, const struct ifaddrs *);
	void		(*af_other_status)(int);
					/* parse address method */
	void		(*af_getaddr)(const char *, int);
					/* parse prefix method (IPv6) */
	void		(*af_getprefix)(const char *, int);
	void		(*af_postproc)(int s, const struct afswtch *);
	u_long		af_difaddr;	/* set dst if address ioctl */
	u_long		af_aifaddr;	/* set if address ioctl */
	void		*af_ridreq;	/* */
	void		*af_addreq;	/* */
	struct afswtch	*af_next;

	/* XXX doesn't fit model */
	void		(*af_status_tunnel)(int);
	void		(*af_settunnel)(int s, struct addrinfo *srcres,
				struct addrinfo *dstres);
};

int
main(int argc, char **argv) 
{
	int	s, error;
	struct	ifreq ifr;
	//char	name[IFNAMSIZ];
	mac_t label;
	
	if (mac_from_text(&label, argv[1]) == -1) {
		perror(argv[1]);
		return;
	}
	
	ifr.ifr_addr.sa_family = AF_INET;
	if ((s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0)) < 0)
		err(1, "socket(family %u,SOCK_DGRAM", ifr.ifr_addr.sa_family);
		
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, argv[2], sizeof(ifr.ifr_name));
	ifr.ifr_ifru.ifru_data = (void *)label;

	error = ioctl(s, SIOCSIFMAC, &ifr);
	mac_free(label);
	if (error == -1)
		perror("setifmac");
	
	return 0;
}
The good new, its compiles fine, the bad new, it doesn't change the mac address, and I get this error message every time I try to change:
Code:
# ./mactool dc0 00:11:22:33:44:55
setifmac: Device not configured
 
SIFE said:
The good new, its compiles fine, the bad new, it doesn't change the mac address, and I get this error message every time I try to change:
Code:
# ./mactool dc0 00:11:22:33:44:55
setifmac: Device not configured
That is because SIOCSIFMAC is for Mandatory Access Control, not for Media Access Control, and neither ethernet stack nor dc(4) handles such operation.
 
I thought SIOCSIFMAC responsible for changing mac address, due I found it on sys/sockio. So, is there any way to change mac address in C. ifconfig can change mac address, but I can't find the piece responsible for that.
 
ifconfig uses SIOCSIFADDR (and similar) ioctl commands to manipulate device addresses ('mac' is just a 'ether' layer address).
 
expl said:
ifconfig uses SIOCSIFADDR (and similar) ioctl commands to manipulate device addresses ('mac' is just a 'ether' layer address).
I also made a try for it but I am stuck in the some problem, is there any snippet can do the trick?
 
Here is an example (different function for different chipsets) of how dc(4) writes mac adr onto hw.
http://fxr.watson.org/fxr/source/dev/dc/if_dc.c?v=FREEBSD9#L1133
It uses mac adr stored in ifp->if_addr->ifa_addr.

What ifconfig(8) does is overwrite the stored adr and bring hw back up, so that the driver will use overwritten adr.

Here is dc(4) ioctl function
http://fxr.watson.org/fxr/source/dev/dc/if_dc.c?v=FREEBSD9#L3816
SIOCSIFFLAGS, SIOCADDMULTI, and SIOCDELMULTI will call dc_setfilt(), and dc_setfilt() will update the mac adr. You may use those, but you need to update the mac adr first.

I would copy ifconfig link mac:adr function. Or, you can modify the driver.
 
I was actually trying to do the same thing myself. Well, I don't need to change it, however, I would like to read the mac address. socket(2) works with descriptors and protocols. For the basic SIOCGIFADDR and the like, you utilize PF_INET as your first argument. The mac address is at the link level. In the socket(2) man page, PF_LINK is listed, I'm assuming that it would be possible then to pull an address from the link layer? I just don't know what the proper type for the second argument, nor protocol for the third socket argument would or should be or the associated ioctl(2) call.

I spent a lot of time combing through ifconfig.c with a little luck, but with a different api. ifconfig(8) seems to rely on getifaddrs(3) and then traverses through the list of interfaces returned, distinguishing between AF_INET and AF_LINK. I suppose storing the information and piecing it together elsewhere? I'm not that great with c/c++ and it takes a while for me to decipher what is going on in other people's code.
 
Here is an example (different function for different chipsets) of how dc(4) writes mac adr onto hw.
I need some think portable, as I know, some chipsets support the mac address to be changed. I don't want to involve with driver layer.
I would copy ifconfig link mac:adr function. Or, you can modify the driver.
What is the function name?
 
@SIFE
Further browsing the code, SIOCSIFLLADDR might do the trick.

ifconfig.c:625 (line numbers are of CURRENT.)
Code:
if (newaddr && (setaddr || setmask)) {
	strncpy(afp->af_addreq, name, sizeof ifr.ifr_name);
	if (ioctl(s, afp->af_aifaddr, afp->af_addreq) < 0)
		Perror("ioctl (SIOCAIFADDR)");
}
af_link.c:114
Code:
static struct afswtch af_lladdr = {
	.af_name	= "lladdr",
	.af_af		= AF_LINK,
	.af_status	= link_status,
	.af_getaddr	= link_getaddr,
	.af_aifaddr	= [b]SIOCSIFLLADDR[/b],
	.af_addreq	= &link_ridreq,
};

SIOCSIFLLADDR ends up here, and calls if_setlladdr(). Then, driver will be re-init if necessary. dc_setfilt() will be called in dc_init_locked().

@zeroseven
Just to read mac adr, checkout link_status() @ af_link.c:55.
main()->status()->link_status().
ifconfig.c:982
Code:
	for (ift = ifa; ift != NULL; ift = ift->ifa_next) {
		if (ift->ifa_addr == NULL)
			continue;
		if (strcmp(ifa->ifa_name, ift->ifa_name) != 0)
			continue;
		if (allfamilies) {
			const struct afswtch *p;
			p = af_getbyfamily(ift->ifa_addr->sa_family);
			if (p != NULL && p->af_status != NULL)
				p->[b]af_status[/b](s, ift);
		} else if (afp->af_af == ift->ifa_addr->sa_family)
			afp->[b]af_status[/b](s, ift);
	}
See a member .af_status of af_lladdr{} in the above.
 
Well, I'm stuck:
Code:
int
main(int argc, char **argv)
{
	int	s, error;
	struct	ifreq ifr;
	char	name[IFNAMSIZ];
	long mac;
	
	ifr.ifr_addr.sa_family = AF_INET;
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		err(1, "socket(family %u,SOCK_DGRAM", ifr.ifr_addr.sa_family);

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name));
	//mac = ConvertMACString(argv[2]);
	//ifr.ifr_ifru.ifru_data = mac;
	//printf("%lld\n", mac);
	error = ioctl(s, SIOCSIFLLADDR, &ifr);
	if (error == -1)
		perror("setifmac");

	return 0;
}
/*
int
ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data)
{
	struct ifreq *ifr;
	int error;
	
	ifr = (struct ifreq *)data;
	if (cmd == SIOCSIFLLADDR) {
		error = if_setlladdr(ifp,
		    ifr->ifr_addr.sa_data, ifr->ifr_addr.sa_len);
	}
	return (error);
}*/
With the main function, I always get this message:
Code:
setifmac: Invalid argument
With ifhwioctl function, I get this:
Code:
/var/tmp//cc9sqkWW.o(.text+0xf4): In function `ifhwioctl':
mactool.c:112: undefined reference to `if_setlladdr'
I want my code to be driver-less, the dc either-net card is just an example.
 
SIFE said:
Well, I'm stuck:
Code:
int
main(int argc, char **argv)
{
	int	s, error;
	struct	ifreq ifr;
	char	name[IFNAMSIZ];
	long mac;
	
	ifr.ifr_addr.sa_family = AF_INET;
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		err(1, "socket(family %u,SOCK_DGRAM", ifr.ifr_addr.sa_family);

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, argv[1], sizeof(ifr.ifr_name));
	//mac = ConvertMACString(argv[2]);
	//ifr.ifr_ifru.ifru_data = mac;
	//printf("%lld\n", mac);
	error = ioctl(s, SIOCSIFLLADDR, &ifr);
	if (error == -1)
		perror("setifmac");

	return 0;
}
Before calling ioctl(), ifr.ifr_addr.sa_data and ifr.ifr_addr.sa_len (sa_data, not ifru_data) need to be initialized. And sa_family should be AF_LINK. See link_getaddr() @af_link.c:76.

SIFE said:
Code:
/*
int
ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data)
{
	struct ifreq *ifr;
	int error;
	
	ifr = (struct ifreq *)data;
	if (cmd == SIOCSIFLLADDR) {
		error = if_setlladdr(ifp,
		    ifr->ifr_addr.sa_data, ifr->ifr_addr.sa_len);
	}
	return (error);
}*/
When you call a system call, ioctl(), with SIOCSIFLLADDR, kernel will call if_setlladdr(). User space progam cannot/shouldn't call kernel functions directly.

SIFE said:
I want my code to be driver-less, the dc either-net card is just an example.
Because you use dc(4) as example, I use it as an example, too. Kernel will find the correct device based on ifr_name you pass.
http://fxr.watson.org/fxr/source/net/if.c?im=10#L2530
 
I am steal seeking in the some error:
Code:
setifmac: Invalid argument
The piece of code I tried:
Code:
int
main(int argc, char **argv)
{
	int	s, error;
	char	name[IFNAMSIZ], *temp;
	const char *addr = argv[2];
	struct ifreq link_ridreq;
	struct sockaddr_dl sdl;
	struct sockaddr *sa = &link_ridreq.ifr_addr;
	
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		err(1, "socket(family %u,SOCK_DGRAM", link_ridreq.ifr_addr.sa_family);
	
	if ((temp = malloc(strlen(addr) + 2)) == NULL)
		errx(1, "malloc failed");
		
	temp[0] = ':';
	link_addr(temp, &sdl);
	free(temp);
	if (sdl.sdl_alen > sizeof(sa->sa_data))
		errx(1, "malformed link-level address");
		
	sa->sa_family = AF_LINK;
	sa->sa_family = AF_LINK;
	sa->sa_len = sdl.sdl_alen;
	bcopy(LLADDR(&sdl), sa->sa_data, sdl.sdl_alen);
	error = ioctl(s, SIOCSIFLLADDR, &link_ridreq);
	if (error == -1)
		perror("setifmac");
	
	return 0;
}
When you call a system call, ioctl(), with SIOCSIFLLADDR, kernel will call if_setlladdr(). User space progam cannot/shouldn't call kernel functions directly.
My program is too simple, I am trying to the maximum to not call kernel directly. KLD is not option for me.
 
Try calling socket() with AF_LINK as well.

SIFE said:
My program is too simple, I am trying to the maximum to not call kernel directly. KLD is not option for me.
That what system calls are for. When your program uses them, system calls (in this case ioctl(2)) will do the rest. So that, your program don't have to (user space program cannot) call kernel functions or you don't have to make special KLD. (Even though you make your KLD, you need to add a system call to call the functions in the KLD. Otherwise, your user space program cannot call the KLD functions.) I posted kernel functions to help you understand how the values you passed with ioctl(2) are processed in the kernel, not for you to call them in your program.
 
Now I get this:
Code:
mactool: socket(family 232,SOCK_DGRAM: Protocol not supported
Even I changed AF_INET to AF_LINK:
Code:
if ((s = socket(AF_LINK, SOCK_DGRAM, 0)) < 0)
 
Oops! Should be AF_LOCAL.

No error checking, but this code works. If you cannot still make your code work, I cannot help any further.
Code:
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <net/if.h>
#include <net/if_dl.h>

#include <string.h>

int
main (int argc, char **argv)
{
	struct ifreq ifr;
	struct sockaddr_dl sdl;
	uint8_t mac[19];
	uint8_t name[IFNAMSIZ];
	int s;

	strncpy(ifr.ifr_name, argv[1], IFNAMSIZ);
	memset(mac, 0, sizeof(mac));
	mac[0] = ':';
	strncpy(mac + 1, argv[2], strlen(argv[2]));
	sdl.sdl_len = sizeof(sdl);
	link_addr(mac, &sdl);

	bcopy(sdl.sdl_data, ifr.ifr_addr.sa_data, 6);
	ifr.ifr_addr.sa_len = 6;

	s = socket(AF_LOCAL, SOCK_DGRAM, 0);

	ioctl(s, SIOCSIFLLADDR, &ifr);

	close(s);
}
To run# ./mactool dc0 00:11:22:33:44:55
 
Your code works fine, I think I make some mistakes in my code, that is why didn't work to me to now. I appreciate your help man.
 
Back
Top