1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Set mac address via ioctl(3)

Discussion in 'FreeBSD Development' started by SIFE, Nov 9, 2011.

  1. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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
     
  2. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    That is because SIOCSIFMAC is for Mandatory Access Control, not for Media Access Control, and neither ethernet stack nor dc(4) handles such operation.
     
  3. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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.
     
  4. expl

    expl New Member

    Messages:
    664
    Thanks Received:
    121
    ifconfig uses SIOCSIFADDR (and similar) ioctl commands to manipulate device addresses ('mac' is just a 'ether' layer address).
     
  5. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    I also made a try for it but I am stuck in the some problem, is there any snippet can do the trick?
     
  6. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    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.
     
  7. zeroseven

    zeroseven New Member

    Messages:
    23
    Thanks Received:
    2
    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.
     
  8. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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.
    What is the function name?
     
  9. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    @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	= SIOCSIFLLADDR,
    	.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->af_status(s, ift);
    		} else if (afp->af_af == ift->ifa_addr->sa_family)
    			afp->af_status(s, ift);
    	}
    See a member .af_status of af_lladdr{} in the above.
     
  10. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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.
     
  11. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    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.

    When you call a system call, ioctl(), with SIOCSIFLLADDR, kernel will call if_setlladdr(). User space progam cannot/shouldn't call kernel functions directly.

    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
     
  12. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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;
    }

    My program is too simple, I am trying to the maximum to not call kernel directly. KLD is not option for me.
     
  13. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    Try calling socket() with AF_LINK as well.

    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.
     
  14. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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)
     
  15. PseudoCylon

    PseudoCylon New Member

    Messages:
    151
    Thanks Received:
    18
    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
     
    SIFE thanks for this.
  16. SIFE

    SIFE New Member

    Messages:
    472
    Thanks Received:
    16
    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.