在Linux下编写程序经常遇到需要对网卡进行管理,但是在glic库中或者其他函数库中却发现没有境界能用的API。有的人在程序中通过system()执行ifconfig命令进行配置,但是获取参数却很麻烦,也有的人通过int getifaddrs(struct ifaddrs **ifap);去获取ip地址等信息,但是只能获取,却不能配置。

还有是通过ioctl()接口进行控制,ioctl接口算是比较常用的​

但是处理原始的netlink消息非常麻烦,且容易出错。但是netlink的功能相比于上面的方法要强大,比如现在ioctl()接口是不能处理ipv6的。netlink还可以轻松的获取路由信息。

我这里不直接使用netlink进行配置,其实早就已经有大牛帮我们写好了netlink相关的库即libnl-3.0​

Linux通过c语言函数管理网络(1)-----获取/修改IP和MAC地址_Linux网卡配置接口

编译下面函数需要:

-lnl-route-3
-lnl-3
-Ilibnl3
/*
* Copyright (C) 2021, 2021 huohongpeng
* Author: huohongpeng <1045338804@qq.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Change logs:
* Date Author Notes
* 2021-10-19 huohongpeng 首次添加
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/route.h>
#include <errno.h>
#include <netlink/route/link.h>
#include <netlink/route/route.h>
#include <netlink/route/addr.h>
#include <netlink/netlink.h>
#include <linux/if_arp.h>
#include <ifaddrs.h>
#include <net/ethernet.h>
#include <sys/socket.h>
#include <netdb.h>


/*
* 启动/停用 网卡状态 (类似 ifconfig ethx up或ifconfig ethx down)
* @eth_name: 网卡名, 如:eth0, wlan0..
* @state: "up": 启动网卡, "down": 停用网卡
* @ret: 0: 成功, -1: 失败
*/
int rtnetlink_eth_state(char *eth_name, char *state)
{
int ret;
struct rtnl_link *link, *link_conf;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!\n", eth_name);
ret = -1;
goto ERROR1;
}

link_conf = rtnl_link_alloc();

if (strcmp("up", state) == 0) {
rtnl_link_set_flags(link_conf, IFF_UP);
} else if (strcmp("down", state) == 0) {
rtnl_link_unset_flags(link_conf, IFF_UP);
} else {
ret = -1;
printf("Err: %s para error!!!\n", state);
goto ERROR2;
}

ret = rtnl_link_change(sk, link, link_conf, 0);

if (ret < 0) {
printf("Err: rtnetlink_eth_state: %d\n", ret);
}

ERROR2:
rtnl_link_put(link);
rtnl_link_put(link_conf);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}

/*
* 修改网卡的MAC地址
* @eth_name: 网卡名, 如:eth0, wlan0..
* @mac: 6字节mac地址, 如unsigned char mac[6] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB};
* 类似于 ifconfig ethx hw ether 12:34:56:78:90:AB
* @ret: 0: 成功, -1: 失败
*/
int rtnetlink_eth_set_mac(char *eth_name, unsigned char *mac)
{
int ret;
struct rtnl_link *link, *link_conf;
struct nl_addr *hw_addr;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!\n", eth_name);
ret = -1;
goto ERROR1;
}

unsigned int if_flag = rtnl_link_get_flags(link);

/*
* 只有在网卡不启用的时候才能设置MAC地址,所以如果网卡已经启用,
* 首先停用网卡,然后设置MAC地址,设置完成后,再次启动网卡
*/
if (if_flag & IFF_UP) {
rtnetlink_eth_state(eth_name, "down");
}

link_conf = rtnl_link_alloc();

/*
* AF_LLC: 地址簇是MAC地址
*/
hw_addr = nl_addr_build(AF_LLC, mac, 6);
rtnl_link_set_addr(link_conf, hw_addr);

ret = rtnl_link_change(sk, link, link_conf, 0);

if (ret < 0) {
printf("Err: rtnetlink_eth_change_mac: %d\n", ret);
}

if (if_flag & IFF_UP) {
rtnetlink_eth_state(eth_name, "up");
}

nl_addr_put(hw_addr);
rtnl_link_put(link);
rtnl_link_put(link_conf);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}

/*
* 获取网卡的MAC地址
* @eth_name: 网卡名, 如:eth0, wlan0..
* @mac: 返回6字节mac地址, 如unsigned char mac[6] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB};
* @ret: 0: 成功, -1: 失败
*/
int rtnetlink_eth_get_mac(char *eth_name, unsigned char *mac)
{
int ret;
struct rtnl_link *link;
struct nl_addr *hw_addr;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!", eth_name);
ret = -1;
goto ERROR1;
}

hw_addr = rtnl_link_get_addr(link);

if (nl_addr_get_len(hw_addr) != 6 || nl_addr_get_family(hw_addr) != AF_LLC) {
ret = -1;
printf("Err: hw_addr len or family error!!!\n");
goto ERROR2;
}

unsigned char *pmac = (unsigned char *)nl_addr_get_binary_addr(hw_addr);
memcpy(mac, pmac, 6);

ERROR2:
rtnl_link_put(link);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}


static int ipv4_netmask2prefixlen(unsigned char *netmask)
{
int count = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 8; j++) {
if ((netmask[i] & (0x01<<j)) != 0) {
count++;
}
}
}
return count;
}


static void ipv4_prefixlen2netmask(int prefixlen, unsigned char *netmask)
{
int i;

memset(netmask, 0x00, 4);

int byte = prefixlen / 8;
int bit = prefixlen % 8;

for (i = 0; i < byte; i++) {
netmask[i] = 0xff;
}

netmask[byte] = 0;

for (i = 0; i < bit; i++) {
netmask[byte] |= (0x01<<(7 - i));
}
}


struct get_ipv4_addr_ctx_t {
struct nl_sock *sk;
int flag; /* 是否找到ip */
int index; /* 网卡索引 */
unsigned char *ip;
unsigned char *netmask;
};


static void get_ipv4_addr_cache_cb(struct nl_object *obj, void *arg)
{
struct get_ipv4_addr_ctx_t *ctx = (struct get_ipv4_addr_ctx_t *)arg;
struct rtnl_addr *rtnl_addr = (struct rtnl_addr *)obj;

if (rtnl_addr_get_ifindex(rtnl_addr) == ctx->index) {
struct nl_addr *nl_addr = rtnl_addr_get_local(rtnl_addr);

if (nl_addr_get_family(nl_addr) == AF_INET && nl_addr_get_len(nl_addr) == 4) {
ctx->flag = 0x01;
int netmask_prefixlen = rtnl_addr_get_prefixlen(rtnl_addr);
char *ip = (char *)nl_addr_get_binary_addr(nl_addr);
memcpy(ctx->ip, ip, 4);
if (netmask_prefixlen <= 32) {
ipv4_prefixlen2netmask(netmask_prefixlen, ctx->netmask);
}
}
}
}


/*
* 获取网卡ip地址
* @eth_name: 网卡名, 如:eth0, wlan0..
* @ip: 返回4字节ip地址, 如char ip[4] = {10, 10, 10, 3};即: 10.10.10.3
* @netmask: 返回4字节netmask地址, 如char netmask[4] = {0xff, 0xff, 0xff, 0};即: 255.255.255.0
* @ret: 0: 成功, -1: 不能获取ip地址
*/
int rtnetlink_eth_get_ipv4_addr(char *eth_name, unsigned char *ip, unsigned char *netmask)
{
int ret = 0;
struct nl_cache *addr_cache;
struct rtnl_link *link;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!\n", eth_name);
ret = -1;
goto ERROR1;
}

struct get_ipv4_addr_ctx_t ctx;
memset(&ctx, 0x00, sizeof(struct get_ipv4_addr_ctx_t));

ctx.index = rtnl_link_get_ifindex(link);
ctx.ip = ip;
ctx.netmask = netmask;

rtnl_addr_alloc_cache(sk, &addr_cache);
nl_cache_foreach(addr_cache, get_ipv4_addr_cache_cb, &ctx);

if (ctx.flag == 0) {
ret = -1;
printf("Err: %s not find ip!!!\n", eth_name);
}

nl_cache_put(addr_cache);
rtnl_link_put(link);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}

static void del_ipv4_addr_cache_cb(struct nl_object *obj, void *arg)
{
struct get_ipv4_addr_ctx_t *ctx = (struct get_ipv4_addr_ctx_t *)arg;
struct rtnl_addr *rtnl_addr = (struct rtnl_addr *)obj;

if (rtnl_addr_get_ifindex(rtnl_addr) == ctx->index) {
struct nl_addr *nl_addr = rtnl_addr_get_local(rtnl_addr);

if (nl_addr_get_family(nl_addr) == AF_INET && nl_addr_get_len(nl_addr) == 4) {
int ret = rtnl_addr_delete(ctx->sk, rtnl_addr, 0);
if (ret < 0) {
printf("Err: rtnl_addr_delete:%d!!!\n", ret);
}
}
}
}


/*
* 删除网卡ip地址
* @eth_name: 网卡名, 如:eth0, wlan0..
* @ip: 返回4字节ip地址, 如char ip[4] = {10, 10, 10, 3};即: 10.10.10.3
* @netmask: 返回4字节netmask地址, 如char netmask[4] = {0xff, 0xff, 0xff, 0};即: 255.255.255.0
* @ret: 0: 成功, -1: 不能获取ip地址
*/
int rtnetlink_eth_del_ipv4_addr(char *eth_name)
{
int ret = 0;
struct nl_cache *addr_cache;
struct rtnl_link *link;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!\n", eth_name);
ret = -1;
goto ERROR1;
}

struct get_ipv4_addr_ctx_t ctx;
memset(&ctx, 0x00, sizeof(struct get_ipv4_addr_ctx_t));

ctx.index = rtnl_link_get_ifindex(link);
ctx.sk = sk;

rtnl_addr_alloc_cache(sk, &addr_cache);
nl_cache_foreach(addr_cache, del_ipv4_addr_cache_cb, &ctx);

nl_cache_put(addr_cache);
rtnl_link_put(link);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}


/*
* 设置网卡ip地址
* @eth_name: 网卡名, 如:eth0, wlan0..
* @ip: 返回4字节ip地址, 如char ip[4] = {10, 10, 10, 3};即: 10.10.10.3
* @netmask: 返回4字节netmask地址, 如char netmask[4] = {0xff, 0xff, 0xff, 0};即: 255.255.255.0
* @ret: 0: 成功, -1: 不能获取ip地址
* 注意: 一般情况下网卡可以设置多个IP地址,这个函数只设置1个IP地址,设置过程是首先删除原来的IP地址,然后在添加新的IP地址
*/
int rtnetlink_eth_set_ipv4_addr(char *eth_name, unsigned char *ip, unsigned char *netmask)
{
int ret = 0;
struct rtnl_link *link;
struct nl_sock *sk = nl_socket_alloc();
nl_connect(sk, NETLINK_ROUTE);

ret = rtnl_link_get_kernel(sk, 0, eth_name, &link);

if (ret < 0) {
printf("Err: %s not found!!!\n", eth_name);
ret = -1;
goto ERROR1;
}

struct rtnl_addr *new_rtnl_addr = rtnl_addr_alloc();

int if_index = rtnl_link_get_ifindex(link);
/*
* 在rtnl_addr中,netmask用ip地址有效长度表示,
* 如: 255.225.0.0有效长度为16
*/
int netmask_prefixlen = ipv4_netmask2prefixlen(netmask);

rtnl_addr_set_ifindex(new_rtnl_addr, if_index);
rtnl_addr_set_family(new_rtnl_addr, AF_INET);

struct nl_addr *ip_addr = nl_addr_build(AF_INET, ip, 4);
rtnl_addr_set_local(new_rtnl_addr, ip_addr);
nl_addr_put(ip_addr);

rtnl_addr_set_prefixlen(new_rtnl_addr, netmask_prefixlen);

unsigned char broadcast[4];

for (int i = 0; i< 4; i++) {
broadcast[i] = (netmask[i] & ip[i]);
broadcast[i] |= ~netmask[i];
}

struct nl_addr *broadcast_addr = nl_addr_build(AF_INET, broadcast, 4);
rtnl_addr_set_broadcast(new_rtnl_addr, broadcast_addr);
nl_addr_put(broadcast_addr);

/*
* 添加地址之前首先删除原来的地址
*/
rtnetlink_eth_del_ipv4_addr(eth_name);

ret = rtnl_addr_add(sk, new_rtnl_addr, 0);

if (ret < 0) {
printf("Err: rtnl_addr_add: %d\n", ret);
}

rtnl_addr_put(new_rtnl_addr);
rtnl_link_put(link);

ERROR1:
nl_close(sk);
nl_socket_free(sk);
return ret;
}



void test_rtnetlink_manage(void)
{

unsigned char ip[4] = {192, 168, 60, 85};
unsigned char netmask[] = {255, 255, 255, 0};
//rtnetlink_eth_del_ipv4_addr("wlan0");
rtnetlink_eth_set_ipv4_addr("eth0", ip, netmask);


return;
//getifaddrs()
}