How to create a vlan programmatically?

The situation was the following: I had to reinstall a server from scratch, remotely, using an IP-KVM. I was using a simple debian netinstall. When the network step came, I skiped as much as possible to type (you know, copy and paste issues) the networking configuration, which was involving vlan and brige. The install process finishes, the machine reboot and ... no network!

Disclaimer: Yes ifconfig could do the job. It's just that the bonding utilities were not installed, nor vlan's. Hence, I felt kind of stuck there, so I figured out a way to create VLAN by hand. I bet it's an ioctl thing. Hey debian is shiped with Perl, which can do ioctl! I might be saved after all! The actual issue was that my configuration was involving bond0, which could not be created. But iproute error message were so obvious, that it was not my first guess. Anyway, I found if fun enough to share; eventhough I managed to make it work using the proper interface name afterwards...

So the configuration is the following: a physical server having two network interfaces. Each of them is connected to a different switch. Both switch basically have the same configuration. The server (running debian) was configured to use failover bonding. On top of the bonded interfaces, are several vlans. In particular, one was used to access publicly reachable network. So, I had to bind vlan 73 to eth0.

This debian box runs on a 3.16 kernel. Let's go and see the source! VLAN are handled in net/8021q/. I suppose we're looking for ioctl commands: there's an interesting function: static int vlan_ioctl_handler(struct net *net, void __user *arg) (l 487) which accepts a ADD_VLAN_CMD which uses takes a dev and the VID field from the union u of the struct vlan_ioctl_args (see iproute2/if_vlan.h l. 48).

To sumup, we need to craft a blob which contains a int (cmd) 24 char (device1) int (vlan id in union) short (vlan qos). And the, we need to route the ioctl command to the vlan module. Hence, the command to provide ioctl is SIOCSIFVLAN (don't remember how I figured that out). So all we have to do is:

  1. create a socket;
  2. create the blob to fit in the struct vlan_ioctl_args format;
  3. invoke ioctl with that payload;
  4. profit!

#!/usr/bin/env perl                                                                                                                                                                                                                                                                   

use strict;
use warnings;

# https://fossies.org/dox/iproute2-4.7.0/if__vlan_8h_source.html                                                                                                                                                                                                                      
# struct vlan_ioctl_args {                                                                                                                                                                                                                                                            
#     int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */                                                                                                                                                                                                                 
#       char device1[24];                                                                                                                                                                                                                                                             
#     union {                                                                                                                                                                                                                                                                         
#       char device2[24];                                                                                                                                                                                                                                                             
#       int VID;                                                                                                                                                                                                                                                                      
#       unsigned int skb_priority;                                                                                                                                                                                                                                                    
#       unsigned int name_type;                                                                                                                                                                                                                                                       
#       unsigned int bind_type;                                                                                                                                                                                                                                                       
#       unsigned int flag; /* Matches vlan_dev_priv flags */                                                                                                                                                                                                                          
#     } u;                                                                                                                                                                                                                                                                            
#     short vlan_qos;                                                                                                                                                                                                                                                                 
# };                                                                                                                                                                                                                                                                                  

ReuseAddr => 1,

# for debug purpose only
sub DumpString {
    my @a = unpack('C*',$_[0]);
    my $o = 0;
    while (@a) {
        my @b = splice @a,0,16;
        my @d = map sprintf("%03d",$_), @b;
        my @x = map sprintf("%02x",$_), @b;
        my $c = substr($_[0],$o,16);
        $c =~ s/[[:^print:]]/ /g;
        printf "%6d %s\n",$o,join(' ',@d);
        print " "x8,join('  ',@x),"\n";
        print " "x9,join('   ',split(//,$c)),"\n";
        $o += 16;
    }
}

my $COMMAND = 0x8983; # SIOCSIFVLAN                                                                                                                                                                                                                                                   
my $format = 'l Z24 L s'; # int (cmd) 24 char (device1) int (vlan id in union) short (vlan qos)                                                                                                                                                                                       
my $value = pack($format, 0, "bond0", 234, 0);
# debug only                                                                                                                                                                                                                                                           
# DumpString($value);
use IO::Socket;
my $server = IO::Socket::INET->new( Proto     => 'tcp',
                                    Listen    => 1,
                                    ReuseAddr => 1,
    );

die "can't setup server... $!... $@..." unless $server;
ioctl($server, $COMMAND, $value) || die "Can't ioctl: $!\n";

And that's it! Also, this can be done with a one-liner: <pre>perl -MIO::Socket -e "ioctl(IO::Socket::INET->new('Proto' => 'tcp'), 0x8983, pack('l Z24 L s', 0, "eth0", 73, 0));"</pre>

References:

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.