Set mac address via ioctl(3)

Kernel development, writing drivers, coding, and questions regarding FreeBSD internals.

Set mac address via ioctl(3)

Postby SIFE » 09 Nov 2011, 02:29

I want to be able change my NIC mac address with my own utility ([FILE]ifconfig[/FILE] 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: Select all
#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: Select all
# ./mactool dc0 00:11:22:33:44:55
setifmac: Device not configured
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 09 Nov 2011, 10:18

SIFE wrote: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: Select all
# ./mactool dc0 00:11:22:33:44:55
setifmac: Device not configured
That is because [FILE]SIOCSIFMAC[/FILE] is for Mandatory Access Control, not for Media Access Control, and neither ethernet stack nor [man=4]dc[/man] handles such operation.
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby SIFE » 09 Nov 2011, 11:25

I thought [FILE]SIOCSIFMAC[/FILE] responsible for changing mac address, due I found it on [FILE]sys/sockio[/FILE]. 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.
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby expl » 09 Nov 2011, 14:46

ifconfig uses SIOCSIFADDR (and similar) ioctl commands to manipulate device addresses ('mac' is just a 'ether' layer address).
User avatar
expl
Member
 
Posts: 664
Joined: 30 Oct 2009, 23:54
Location: In your shell, stealing your cookies.

Postby SIFE » 09 Nov 2011, 22:57

expl wrote: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?
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 09 Nov 2011, 23:39

Here is an example (different function for different chipsets) of how [man=4]dc[/man] 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 [man=8]ifconfig[/man] does is overwrite the stored adr and bring hw back up, so that the driver will use overwritten adr.

Here is [man=4]dc[/man] ioctl function
http://fxr.watson.org/fxr/source/dev/dc/if_dc.c?v=FREEBSD9#L3816
[FILE]SIOCSIFFLAGS[/FILE], [FILE]SIOCADDMULTI[/FILE], and [FILE]SIOCDELMULTI[/FILE] 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 [FILE]ifconfig link mac:adr[/FILE] function. Or, you can modify the driver.
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby zeroseven » 10 Nov 2011, 13:02

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. [man=2]socket[/man] works with descriptors and protocols. For the basic [FILE]SIOCGIFADDR[/FILE] and the like, you utilize [FILE]PF_INET[/FILE] as your first argument. The mac address is at the link level. In the [man=2]socket[/man] man page, [FILE]PF_LINK[/FILE] 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 [man=2]ioctl[/man] call.

I spent a lot of time combing through [file]ifconfig.c[/file] with a little luck, but with a different api. [man=8]ifconfig[/man] seems to rely on [man=3]getifaddrs[/man] and then traverses through the list of interfaces returned, distinguishing between [FILE]AF_INET[/FILE] and [FILE]AF_LINK[/FILE]. 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.
zeroseven
Junior Member
 
Posts: 23
Joined: 14 May 2010, 06:13

Postby SIFE » 10 Nov 2011, 13:26

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
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 11 Nov 2011, 02:21

@SIFE
Further browsing the code, [FILE]SIOCSIFLLADDR[/FILE] might do the trick.

[FILE]ifconfig.c:625[/FILE] (line numbers are of CURRENT.)
Code: Select all
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)");
}
[FILE]af_link.c:114[/FILE]
Code: Select all
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,
};


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

@zeroseven
Just to read mac adr, checkout [FILE]link_status()[/FILE] @ [FILE]af_link.c:55[/FILE].
[FILE]main()[/FILE]->[FILE]status()[/FILE]->[FILE]link_status()[/FILE].
[FILE]ifconfig.c:982[/FILE]
Code: Select all
   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 [FILE].af_status[/FILE] of [FILE]af_lladdr{}[/FILE] in the above.
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby SIFE » 14 Nov 2011, 21:45

Well, I'm stuck:
Code: Select all
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: Select all
setifmac: Invalid argument

With [FILE]ifhwioctl[/FILE] function, I get this:
Code: Select all
/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 [FILE]dc[/FILE] either-net card is just an example.
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 16 Nov 2011, 01:45

SIFE wrote:Well, I'm stuck:
Code: Select all
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 [FILE]ioctl()[/FILE], [FILE]ifr.ifr_addr.sa_data[/FILE] and [FILE]ifr.ifr_addr.sa_len[/FILE] ([FILE]sa_data[/FILE], not [FILE]ifru_data[/FILE]) need to be initialized. And [FILE]sa_family[/FILE] should be [FILE]AF_LINK[/FILE]. See [FILE]link_getaddr()[/FILE] @[FILE]af_link.c:76[/FILE].

SIFE wrote:
Code: Select all
/*
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, [FILE]ioctl()[/FILE], with [FILE]SIOCSIFLLADDR[/FILE], kernel will call [FILE]if_setlladdr()[/FILE]. User space progam cannot/shouldn't call kernel functions directly.

SIFE wrote:I want my code to be driver-less, the dc either-net card is just an example.
Because you use [man=4]dc[/man] as example, I use it as an example, too. Kernel will find the correct device based on [FILE]ifr_name[/FILE] you pass.
http://fxr.watson.org/fxr/source/net/if.c?im=10#L2530
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby SIFE » 25 Nov 2011, 22:50

I am steal seeking in the some error:
Code: Select all
setifmac: Invalid argument

The piece of code I tried:
Code: Select all
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.
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 26 Nov 2011, 11:55

Try calling [FILE]socket()[/FILE] with [FILE]AF_LINK[/FILE] as well.

SIFE wrote: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 [man=2]ioctl[/man]) 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 [man=2]ioctl[/man] are processed in the kernel, not for you to call them in your program.
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby SIFE » 28 Nov 2011, 13:17

Now I get this:
Code: Select all
mactool: socket(family 232,SOCK_DGRAM: Protocol not supported

Even I changed AF_INET to AF_LINK:
Code: Select all
if ((s = socket(AF_LINK, SOCK_DGRAM, 0)) < 0)
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever

Postby PseudoCylon » 29 Nov 2011, 13:35

Oops! Should be [FILE]AF_LOCAL[/FILE].

No error checking, but this code works. If you cannot still make your code work, I cannot help any further.
Code: Select all
#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[CMD="#"]./mactool dc0 00:11:22:33:44:55[/CMD]
PseudoCylon
Member
 
Posts: 151
Joined: 07 Oct 2009, 03:26
Location: Alberta, Canada

Postby SIFE » 29 Nov 2011, 20:09

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.
SIFE
Member
 
Posts: 472
Joined: 02 Feb 2009, 20:00
Location: When ever where ever


Return to FreeBSD Development

Who is online

Users browsing this forum: No registered users and 0 guests