Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX NETLINK 2026

Netlink Socket
Kernel⇔Userspace İletişimi

AF_NETLINK ile kernel ve userspace arasında asenkron mesaj protokolü — rtnetlink, libnl, Generic Netlink ve nl80211 pratik uygulamaları.

00 Netlink nedir

Netlink, Linux'ta kernel ve userspace arasında ağ konfigürasyonu, route yönetimi, netfilter ve sistem gözlemleme gibi iletişimleri sağlayan özel bir IPC mekanizmasıdır.

/proc vs ioctl vs netlink

MekanizmaYönAvantajDezavantaj
/proc / /sysÇift yönlü (dosya R/W)Basit, script dostuString parsing, yüksek overhead, kernel event yok
ioctlÇift yönlü (senkron)Hızlı, eski API uyumluGenişletilemez, ABI kırılgan, async yok
NetlinkÇift yönlü + asenkronBinary verimli, multicast, async bildirim, genişletilebilirKarmaşık mesaj yapısı, doğrulama gerekli

Netlink, ip, iw, ss, tc gibi standart Linux ağ araçlarının altında yatan protokoldür. strace ip link show çalıştırıldığında AF_NETLINK socket'inin açıldığı ve çeşitli sendmsg/recvmsg çağrılarının yapıldığı görülür.

AF_NETLINK socket protokol türleri

ProtokolDeğerKullanım
NETLINK_ROUTE0Ağ arayüzleri, route tablosu, ARP, neigh, trafik şekillendirme
NETLINK_NETFILTER12iptables/nftables kural yönetimi, conntrack
NETLINK_KOBJECT_UEVENT15Donanım olayları — hotplug, udev
NETLINK_AUDIT9Linux Audit subsystem
NETLINK_GENERIC16Generic Netlink — özel kernel modülleri için
NETLINK_CRYPTO21Kernel crypto API konfigürasyonu
NETLINK_XFRM6IPsec SA/SP yönetimi
bash — netlink socket'ini gözlemle
# ip komutunun altında netlink görmek için
strace -e trace=network ip link show 2>&1 | grep -E "socket|sendmsg|recvmsg"
# socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE) = 3
# bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=0}, 12) = 0
# sendmsg(3, {msg_name={...}, msg_iov=[{iov_base="\x28\x00..."}]}, 0) = 40
# recvmsg(3, {msg_iov=[{iov_base="...", iov_len=4096}]}, 0) = 1592

# Sistem geneli netlink socket'lerini listele
ss -f netlink
cat /proc/net/netlink

Bu bölümde

  • Netlink: /proc ve ioctl'nin modern alternatifi — binary, async, multicast
  • ip/iw/ss/tc araçlarının tamamı altta Netlink kullanır
  • NETLINK_ROUTE: ağ konfigürasyonu; NETLINK_GENERIC: özel modüller
  • strace ile netlink trafiğini gözlemleyebilirsin

01 Netlink mesaj yapısı

Her Netlink mesajı sabit uzunluklu bir başlık (nlmsghdr) ile başlar, ardından isteğe bağlı payload ve nlattr nitelik dizisi gelir. Tüm alanlar 4 byte hizalamasına tabidir.

nlmsghdr — mesaj başlığı

C — nlmsghdr yapısı
#include <linux/netlink.h>

struct nlmsghdr {
    __u32 nlmsg_len;    /* Başlık dahil toplam mesaj uzunluğu (byte) */
    __u16 nlmsg_type;   /* Mesaj tipi: RTM_GETLINK, RTM_NEWROUTE, NLMSG_DONE vb. */
    __u16 nlmsg_flags;  /* NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK */
    __u32 nlmsg_seq;    /* Sequence numarası — istek/yanıt eşleştirme */
    __u32 nlmsg_pid;    /* Gönderenin PID'i (kernel için 0) */
};

nlmsg_flags değerleri

FlagDeğerAnlam
NLM_F_REQUEST0x01Bu mesaj bir istektir (request)
NLM_F_MULTI0x02Çok parçalı yanıt — NLMSG_DONE ile biter
NLM_F_ACK0x04Yanıt olarak ACK iste
NLM_F_ECHO0x08Mesajı echo et
NLM_F_DUMPNLM_F_ROOT|NLM_F_MATCHTüm nesneleri listele (dump)
NLM_F_CREATE0x400Nesne oluştur (yoksa)
NLM_F_REPLACE0x100Varsa değiştir

nlattr — Netlink özelliği

C — nlattr yapısı ve makrolar
#include <linux/netlink.h>

/* Her özellik bu başlıkla başlar */
struct nlattr {
    __u16 nla_len;    /* Başlık + değer uzunluğu (byte) */
    __u16 nla_type;   /* Özellik tipi (IFLA_MTU, RTA_DST vb.) */
    /* Ardından nla_len - sizeof(struct nlattr) byte değer gelir */
};

/* Makrolar: hizalama ve gezinme */
#define NLMSG_ALIGN(len)    (((len) + NLMSG_ALIGNTO - 1) & ~(NLMSG_ALIGNTO - 1))
#define NLMSG_HDRLEN        ((int)NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_DATA(nlh)     ((void*)(((char*)nlh) + NLMSG_HDRLEN))
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                             (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
#define NLMSG_OK(nlh,len)   ((len) >= (int)sizeof(struct nlmsghdr) && \
                             (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                             (nlh)->nlmsg_len <= (len))

/* nlattr hizalama */
#define NLA_ALIGN(len)      (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#define NLA_DATA(na)        ((void *)((char*)(na) + NLA_HDRLEN))

Mesaj layout diyagramı

[ nlmsghdr (16 byte) ][ payload (struct ifinfomsg/rtmsg vb.) ][ nlattr ][ nlattr ][ nlattr ]...
 ←───── nlmsg_len ─────────────────────────────────────────────────────────────────→

Bu bölümde

  • nlmsghdr: len/type/flags/seq/pid — 16 byte sabit başlık
  • nlmsg_flags: NLM_F_REQUEST (istek) | NLM_F_DUMP (tümünü al)
  • nlattr: type-length-value; 4 byte hizalamalı; iç içe (nested) olabilir
  • NLMSG_OK + NLMSG_NEXT: çok parçalı yanıtları döngüyle işlemek için

02 NETLINK_ROUTE — rtnetlink

rtnetlink, NETLINK_ROUTE ailesi üzerinde çalışan ağ konfigürasyon protokolüdür. ip komutunun kullandığı aynı API'dir: arayüz, adres, route, neigh yönetimi.

Önemli RTM_ mesaj tipleri

TipDeğerAçıklama
RTM_NEWLINK / RTM_DELLINK16/17Ağ arayüzü ekle/sil (ip link add/del)
RTM_GETLINK18Arayüz bilgilerini al (ip link show)
RTM_NEWADDR / RTM_DELADDR20/21IP adresi ekle/sil (ip addr add/del)
RTM_GETADDR22IP adres listesi (ip addr show)
RTM_NEWROUTE / RTM_DELROUTE24/25Route ekle/sil (ip route add/del)
RTM_GETROUTE26Route tablosunu al (ip route show)
RTM_NEWNEIGH / RTM_GETNEIGH28/30ARP/neighbor tablosu (ip neigh)

RTM_GETLINK — ifinfomsg yapısı

C — rtnetlink yapıları
#include <linux/rtnetlink.h>
#include <linux/if_link.h>

/* RTM_GETLINK / RTM_NEWLINK için payload */
struct ifinfomsg {
    unsigned char  ifi_family;   /* AF_UNSPEC */
    unsigned char  __ifi_pad;
    unsigned short ifi_type;     /* ARPHRD_ETHER vb. */
    int            ifi_index;    /* Arayüz indeksi */
    unsigned int   ifi_flags;    /* IFF_UP, IFF_RUNNING vb. */
    unsigned int   ifi_change;   /* Değişen bayraklar için maske */
};

/* IFLA_ özellik tipleri (nlattr nla_type) */
/* IFLA_IFNAME: char[] — arayüz adı (eth0, lo, vb.) */
/* IFLA_MTU:    uint32  — MTU değeri */
/* IFLA_LINK:   int32   — üst arayüz indeksi */
/* IFLA_STATS:  struct rtnl_link_stats */

/* RTM_GETADDR için payload */
struct ifaddrmsg {
    __u8  ifa_family;       /* AF_INET veya AF_INET6 */
    __u8  ifa_prefixlen;    /* Önek uzunluğu (prefix length) */
    __u8  ifa_flags;        /* IFA_F_PERMANENT, IFA_F_TEMPORARY */
    __u8  ifa_scope;        /* RT_SCOPE_HOST, RT_SCOPE_LINK, vb. */
    __u32 ifa_index;        /* Arayüz indeksi */
};

Bu bölümde

  • RTM_GETLINK: arayüz dump — ip link show'un altı
  • RTM_GETADDR: adres dump — ip addr show'un altı
  • RTM_GETROUTE: route dump — ip route show'un altı
  • ifinfomsg/ifaddrmsg/rtmsg: payload yapıları; nlattr ile özellikler

03 C ile netlink client

Ham POSIX socket API'siyle netlink client: socket() → bind() → sendmsg() → recvmsg() → mesaj parse. Arayüz listesi alma örneği.

Tam C örneği — arayüz listesi

C — netlink_iflist.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

#define BUFSIZE 8192

/* Netlink isteği gönder */
static int send_nl_req(int sock_fd, uint16_t msg_type, uint16_t flags)
{
    struct {
        struct nlmsghdr nlh;
        struct ifinfomsg ifm;
    } req;

    memset(&req, 0, sizeof(req));

    req.nlh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nlh.nlmsg_type  = msg_type;
    req.nlh.nlmsg_flags = flags | NLM_F_REQUEST;
    req.nlh.nlmsg_seq   = 1;
    req.nlh.nlmsg_pid   = getpid();

    req.ifm.ifi_family  = AF_UNSPEC;

    struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
    struct iovec iov = { &req, req.nlh.nlmsg_len };
    struct msghdr msg = {
        .msg_name    = &sa,
        .msg_namelen = sizeof(sa),
        .msg_iov     = &iov,
        .msg_iovlen  = 1,
    };

    return sendmsg(sock_fd, &msg, 0);
}

/* nlattr'lardan arayüz adını al */
static void parse_link(struct nlmsghdr *nlh)
{
    struct ifinfomsg *ifm = NLMSG_DATA(nlh);
    struct rtattr *rta;
    int rta_len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifm));

    printf("Index: %d  Flags: %s\n",
           ifm->ifi_index,
           (ifm->ifi_flags & IFF_UP) ? "UP" : "DOWN");

    for (rta = IFLA_RTA(ifm); RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
        switch (rta->rta_type) {
        case IFLA_IFNAME:
            printf("  Name: %s\n", (char *)RTA_DATA(rta));
            break;
        case IFLA_MTU:
            printf("  MTU:  %u\n", *(uint32_t *)RTA_DATA(rta));
            break;
        default:
            break;
        }
    }
}

int main(void)
{
    /* 1. Socket oluştur */
    int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock_fd < 0) { perror("socket"); return 1; }

    /* 2. Bind (pid=0: kernel otomatik atar) */
    struct sockaddr_nl sa = { .nl_family = AF_NETLINK, .nl_pid = 0 };
    if (bind(sock_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        perror("bind"); return 1;
    }

    /* 3. RTM_GETLINK isteği gönder (dump modu) */
    if (send_nl_req(sock_fd, RTM_GETLINK, NLM_F_DUMP) < 0) {
        perror("sendmsg"); return 1;
    }

    /* 4. Yanıtları oku ve parse et */
    char buf[BUFSIZE];
    struct iovec iov = { buf, sizeof(buf) };
    struct sockaddr_nl src;
    struct msghdr msg = {
        .msg_name    = &src,
        .msg_namelen = sizeof(src),
        .msg_iov     = &iov,
        .msg_iovlen  = 1,
    };

    while (1) {
        ssize_t len = recvmsg(sock_fd, &msg, 0);
        if (len < 0) { perror("recvmsg"); break; }

        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        for (; NLMSG_OK(nlh, (unsigned)len); nlh = NLMSG_NEXT(nlh, len)) {
            if (nlh->nlmsg_type == NLMSG_DONE)
                goto done;
            if (nlh->nlmsg_type == NLMSG_ERROR) {
                fprintf(stderr, "Netlink hatası\n");
                goto done;
            }
            if (nlh->nlmsg_type == RTM_NEWLINK)
                parse_link(nlh);
        }
    }

done:
    close(sock_fd);
    return 0;
}
bash — derleme ve çalıştırma
gcc -Wall -o netlink_iflist netlink_iflist.c
./netlink_iflist

# Beklenen çıktı:
# Index: 1  Flags: UP
#   Name: lo
#   MTU:  65536
# Index: 2  Flags: UP
#   Name: eth0
#   MTU:  1500

Bu bölümde

  • socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE): netlink soketi
  • bind() + sendmsg(): istek gönder; recvmsg() + NLMSG_OK/NEXT: yanıt parse
  • NLM_F_DUMP: tüm nesneleri listele; NLMSG_DONE: dump sonu sinyali
  • RTA_OK + RTA_NEXT: rtattr (nlattr benzeri) döngüsü

04 libnl kütüphanesi

libnl, ham netlink API'sini saran yüksek seviyeli C kütüphanesidir. Mesaj oluşturma, hata yönetimi ve callback mekanizmaları ile geliştirme sürecini kolaylaştırır.

libnl kurulumu

bash — libnl kurulumu
sudo apt install libnl-3-dev libnl-route-3-dev libnl-genl-3-dev

# Derleme bayrağı (pkg-config ile)
pkg-config --cflags --libs libnl-3.0 libnl-route-3.0
# -I/usr/include/libnl3 -lnl-3 -lnl-route-3

libnl temel API

C — libnl ile arayüz listesi
#include <netlink/netlink.h>
#include <netlink/route/link.h>

int main(void)
{
    /* Socket oluştur ve kernel'e bağlan */
    struct nl_sock *sock = nl_socket_alloc();
    if (!sock) { fprintf(stderr, "nl_socket_alloc hatası\n"); return 1; }

    int err = nl_connect(sock, NETLINK_ROUTE);
    if (err < 0) {
        fprintf(stderr, "nl_connect: %s\n", nl_geterror(err));
        return 1;
    }

    /* Link cache doldur (RTM_GETLINK dump) */
    struct nl_cache *cache;
    err = rtnl_link_alloc_cache(sock, AF_UNSPEC, &cache);
    if (err < 0) {
        fprintf(stderr, "rtnl_link_alloc_cache: %s\n", nl_geterror(err));
        return 1;
    }

    /* Cache'deki her arayüzü gezin */
    struct rtnl_link *link = (struct rtnl_link *)nl_cache_get_first(cache);
    while (link) {
        printf("Index: %-3d Name: %-12s MTU: %u  Flags: 0x%08X\n",
               rtnl_link_get_ifindex(link),
               rtnl_link_get_name(link),
               rtnl_link_get_mtu(link),
               rtnl_link_get_flags(link));

        link = (struct rtnl_link *)nl_cache_get_next((struct nl_object *)link);
    }

    nl_cache_free(cache);
    nl_socket_free(sock);
    return 0;
}
bash — derleme
gcc -Wall -o libnl_iflist libnl_iflist.c \
  $(pkg-config --cflags --libs libnl-3.0 libnl-route-3.0)

libnl ile IP adresi okuma

C — IP adresleri okuma
#include <netlink/route/addr.h>
#include <arpa/inet.h>

void list_addresses(struct nl_sock *sock)
{
    struct nl_cache *addr_cache;
    rtnl_addr_alloc_cache(sock, &addr_cache);

    struct rtnl_addr *addr = (struct rtnl_addr *)nl_cache_get_first(addr_cache);
    char buf[INET6_ADDRSTRLEN];

    while (addr) {
        struct nl_addr *local = rtnl_addr_get_local(addr);
        if (local) {
            nl_addr2str(local, buf, sizeof(buf));
            printf("  IF idx: %d  Addr: %s/%d\n",
                   rtnl_addr_get_ifindex(addr),
                   buf,
                   rtnl_addr_get_prefixlen(addr));
        }
        addr = (struct rtnl_addr *)nl_cache_get_next((struct nl_object *)addr);
    }

    nl_cache_free(addr_cache);
}

Bu bölümde

  • nl_socket_alloc + nl_connect: bağlantı yönetimi
  • rtnl_link_alloc_cache: tek çağrıda tüm link dump
  • nl_cache_get_first + nl_cache_get_next: cache iterasyonu
  • libnl-route-3: rtnl_link_*, rtnl_addr_*, rtnl_route_* API'leri

05 Generic Netlink (genl)

Generic Netlink, kernel modüllerinin kendi netlink aile (family) tanımlamalarına olanak tanıyan genişletilebilir bir çerçevedir. Özel kernel-userspace iletişim protokolleri için idealdir.

Generic Netlink mimarisi

Userspace (genl socket) → NETLINK_GENERIC → genetlink.ko → Aile araması (by name) → Kernel modülü handler
FamilyBir kernel bileşeninin kaydettirdiği isimli grup — örn. "MYMODULE"; dinamik ID alır
Command (op)Aile içindeki işlem — MYMODULE_CMD_GET, MYMODULE_CMD_SET gibi
AttributeHer komutun taşıdığı nlattr özellikler — politika (policy) ile tip doğrulaması
genl_familyKernel tarafında tanımlanan yapı — ad, versiyon, ops, mcgrps

Kernel modülü tarafı

C — kernel genl aile tanımı (özet)
#include <linux/module.h>
#include <net/genetlink.h>

/* Komut ve özellik tanımları */
enum mymod_cmd {
    MYMOD_CMD_UNSPEC = 0,
    MYMOD_CMD_HELLO,
    MYMOD_CMD_GET_DATA,
    __MYMOD_CMD_MAX,
};

enum mymod_attr {
    MYMOD_ATTR_UNSPEC = 0,
    MYMOD_ATTR_MSG,        /* string */
    MYMOD_ATTR_VALUE,      /* u32 */
    __MYMOD_ATTR_MAX,
};

/* Özellik politikası (tip ve boyut doğrulaması) */
static const struct nla_policy mymod_policy[__MYMOD_ATTR_MAX] = {
    [MYMOD_ATTR_MSG]   = { .type = NLA_STRING, .len = 256 },
    [MYMOD_ATTR_VALUE] = { .type = NLA_U32 },
};

/* Handler: HELLO komutu */
static int mymod_hello(struct sk_buff *skb, struct genl_info *info)
{
    char *msg = "Merhaba Userspace!";
    if (info->attrs[MYMOD_ATTR_MSG])
        msg = nla_data(info->attrs[MYMOD_ATTR_MSG]);

    pr_info("mymod: HELLO alındı: %s\n", msg);

    /* Yanıt gönder */
    struct sk_buff *reply = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    void *reply_hdr = genlmsg_put_reply(reply, info, &mymod_family, 0, MYMOD_CMD_HELLO);
    nla_put_string(reply, MYMOD_ATTR_MSG, "Merhaba!");
    genlmsg_end(reply, reply_hdr);
    return genlmsg_reply(reply, info);
}

/* Operasyon tablosu */
static const struct genl_ops mymod_ops[] = {
    {
        .cmd    = MYMOD_CMD_HELLO,
        .flags  = 0,
        .policy = mymod_policy,
        .doit   = mymod_hello,
    },
};

/* Aile tanımı */
static struct genl_family mymod_family = {
    .name     = "MYMODULE",
    .version  = 1,
    .maxattr  = __MYMOD_ATTR_MAX - 1,
    .ops      = mymod_ops,
    .n_ops    = ARRAY_SIZE(mymod_ops),
};

static int __init mymod_init(void) { return genl_register_family(&mymod_family); }
static void __exit mymod_exit(void) { genl_unregister_family(&mymod_family); }

module_init(mymod_init);
module_exit(mymod_exit);
MODULE_LICENSE("GPL");

Userspace genl client (libnl-genl)

C — genl userspace client
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>

int main(void)
{
    struct nl_sock *sock = nl_socket_alloc();
    genl_connect(sock);

    /* Aile ID'sini çözümle */
    int family_id = genl_ctrl_resolve(sock, "MYMODULE");
    if (family_id < 0) {
        fprintf(stderr, "Aile bulunamadı: %s\n", nl_geterror(family_id));
        return 1;
    }
    printf("Family ID: %d\n", family_id);

    /* HELLO mesajı oluştur ve gönder */
    struct nl_msg *msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ,
                family_id, 0, 0, MYMOD_CMD_HELLO, 1);
    nla_put_string(msg, MYMOD_ATTR_MSG, "Merhaba Kernel!");

    nl_send_auto(sock, msg);
    nlmsg_free(msg);

    /* Yanıtı bekle */
    nl_recvmsgs_default(sock);

    nl_socket_free(sock);
    return 0;
}

Bu bölümde

  • genl_register_family: kernel modülü kendi netlink ailesi kaydeder
  • genl_ctrl_resolve("MYMODULE"): dinamik family ID çözümleme
  • policy: nla_policy ile attr tipi ve uzunluk doğrulaması — güvenli parsing
  • genlmsg_put + nla_put_*: mesaj inşa API'si; nl_send_auto: gönder

06 nl80211 — Linux WiFi kontrol API

nl80211, Linux'ta WiFi konfigürasyonunun tüm arayüzüdür. Tarama, bağlanma, station dump, AP modu — hepsi nl80211 üzerinden. iw aracı nl80211'i kullanır.

nl80211 temelleri

bash — iw ile nl80211 gözlemleme
# iw'nin altındaki nl80211 komutları görmek için
iw dev wlan0 scan dump

# nl80211 komut ID'lerini listele (kernel kaynağından)
grep NL80211_CMD /usr/include/linux/nl80211.h | head -30

# Arayüz bilgisi al
iw dev wlan0 info

# Station listesi (AP modundaysa bağlı cihazlar)
iw dev wlan0 station dump

# Tarama tetikle
iw dev wlan0 scan trigger

# Tarama sonuçlarını oku
iw dev wlan0 scan dump

nl80211 ile C — arayüz bilgisi

C — nl80211 NL80211_CMD_GET_INTERFACE
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>
#include <net/if.h>

struct wifi_info {
    char ifname[IFNAMSIZ];
    uint32_t ifindex;
    uint32_t wiphy;
    uint32_t iftype;   /* NL80211_IFTYPE_STATION, AP, MONITOR vb. */
};

/* Callback: yanıt mesajını parse et */
static int nl80211_cb(struct nl_msg *msg, void *arg)
{
    struct wifi_info *info = (struct wifi_info *)arg;
    struct nlattr *attrs[NL80211_ATTR_MAX + 1];
    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

    nla_parse(attrs, NL80211_ATTR_MAX,
              genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), NULL);

    if (attrs[NL80211_ATTR_IFNAME])
        strncpy(info->ifname, nla_get_string(attrs[NL80211_ATTR_IFNAME]),
                sizeof(info->ifname) - 1);
    if (attrs[NL80211_ATTR_IFINDEX])
        info->ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]);
    if (attrs[NL80211_ATTR_WIPHY])
        info->wiphy = nla_get_u32(attrs[NL80211_ATTR_WIPHY]);
    if (attrs[NL80211_ATTR_IFTYPE])
        info->iftype = nla_get_u32(attrs[NL80211_ATTR_IFTYPE]);

    return NL_OK;
}

int main(void)
{
    struct nl_sock *sock = nl_socket_alloc();
    genl_connect(sock);

    int nl80211_id = genl_ctrl_resolve(sock, "nl80211");
    if (nl80211_id < 0) {
        fprintf(stderr, "nl80211 bulunamadı — cfg80211 yüklü mü?\n");
        return 1;
    }

    struct wifi_info info = {0};
    nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, nl80211_cb, &info);

    struct nl_msg *msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ,
                nl80211_id, 0, NLM_F_DUMP,
                NL80211_CMD_GET_INTERFACE, 0);

    nl_send_auto(sock, msg);
    nlmsg_free(msg);

    nl_recvmsgs_default(sock);

    printf("Arayüz: %s (idx=%u, wiphy=%u, type=%u)\n",
           info.ifname, info.ifindex, info.wiphy, info.iftype);

    nl_socket_free(sock);
    return 0;
}

Tarama tetikleme

C — NL80211_CMD_TRIGGER_SCAN
/* Tarama tetikle (özet) */
void trigger_scan(struct nl_sock *sock, int nl80211_id, uint32_t ifindex)
{
    struct nl_msg *msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ,
                nl80211_id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0);
    nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex);

    /* İsteğe bağlı: belirli SSID'leri tara */
    struct nlattr *ssids = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS);
    nla_put(msg, 1, 0, "");  /* Boş SSID = broadcast scan */
    nla_nest_end(msg, ssids);

    nl_send_auto(sock, msg);
    nlmsg_free(msg);

    /* Tarama tamamlanana kadar bekle (NL80211_CMD_NEW_SCAN_RESULTS event) */
    /* Gerçek uygulamada multicast group'a abone olunur: */
    /* nl_socket_add_membership(sock, scan_mcgrp_id); */
}

Bu bölümde

  • nl80211: tüm Linux WiFi konfigürasyonunun API'si — iw aracının altı
  • genl_ctrl_resolve("nl80211"): family ID; NL80211_CMD_* ile komutlar
  • nla_parse: attrs dizisine nlattr ayrıştırma; nla_get_u32/string ile değer al
  • NL80211_CMD_TRIGGER_SCAN: tarama başlat; multicast ile sonuç bildirimi

07 Kernel tarafı — netlink_kernel_create

Kernel modülü, netlink_kernel_create ile kendi netlink socket'ini açabilir. Userspace'ten gelen mesajları input callback'te işler ve yanıt gönderir.

Minimal kernel netlink modülü

C — kernel modülü (nl_module.c)
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <net/net_namespace.h>

#define NETLINK_MYMOD 31    /* Özel protocol numarası (17-31 kullanılabilir) */

static struct sock *nl_sk = NULL;

/* Kernel tarafı input callback: userspace'ten mesaj geldiğinde çağrılır */
static void nl_recv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh;
    int pid;
    struct sk_buff *skb_out;
    char *msg = "Kernel'dan merhaba!";
    int msg_size = strlen(msg) + 1;

    nlh = (struct nlmsghdr *)skb->data;
    pid = nlh->nlmsg_pid;  /* Gönderenin PID'i */

    pr_info("nl_module: PID %d'den mesaj: %s\n",
            pid, (char *)nlmsg_data(nlh));

    /* Yanıt gönder */
    skb_out = nlmsg_new(msg_size, GFP_KERNEL);
    if (!skb_out) {
        pr_err("nl_module: nlmsg_new başarısız\n");
        return;
    }

    nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
    NETLINK_CB(skb_out).dst_group = 0;   /* unicast */
    strncpy(nlmsg_data(nlh), msg, msg_size);

    int ret = nlmsg_unicast(nl_sk, skb_out, pid);
    if (ret < 0)
        pr_err("nl_module: nlmsg_unicast hatası: %d\n", ret);
}

static int __init nl_module_init(void)
{
    struct netlink_kernel_cfg cfg = {
        .input = nl_recv_msg,
    };

    nl_sk = netlink_kernel_create(&init_net, NETLINK_MYMOD, &cfg);
    if (!nl_sk) {
        pr_err("nl_module: netlink_kernel_create başarısız\n");
        return -ENOMEM;
    }

    pr_info("nl_module: NETLINK_MYMOD=%d kaydedildi\n", NETLINK_MYMOD);
    return 0;
}

static void __exit nl_module_exit(void)
{
    netlink_kernel_release(nl_sk);
    pr_info("nl_module: kaldırıldı\n");
}

module_init(nl_module_init);
module_exit(nl_module_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal Netlink Kernel Module");

Userspace client (kernel modülü için)

C — userspace → kernel modülü mesaj
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_MYMOD 31
#define BUFSIZE 256

int main(void)
{
    int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MYMOD);

    struct sockaddr_nl src_addr = {
        .nl_family = AF_NETLINK,
        .nl_pid    = getpid(),
    };
    bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));

    /* Mesaj oluştur */
    char buf[NLMSG_SPACE(BUFSIZE)];
    memset(buf, 0, sizeof(buf));

    struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
    nlh->nlmsg_len  = NLMSG_SPACE(BUFSIZE);
    nlh->nlmsg_pid  = getpid();
    nlh->nlmsg_flags = 0;
    strncpy(NLMSG_DATA(nlh), "Merhaba Kernel!", BUFSIZE - 1);

    /* Kernel'e gönder (pid=0: kernel hedef) */
    struct sockaddr_nl dest_addr = {
        .nl_family = AF_NETLINK,
        .nl_pid    = 0,         /* Kernel */
    };
    struct iovec iov = { buf, nlh->nlmsg_len };
    struct msghdr msg = {
        .msg_name    = &dest_addr,
        .msg_namelen = sizeof(dest_addr),
        .msg_iov     = &iov,
        .msg_iovlen  = 1,
    };
    sendmsg(sock_fd, &msg, 0);

    /* Yanıtı bekle */
    memset(buf, 0, sizeof(buf));
    iov.iov_base = buf;
    iov.iov_len  = sizeof(buf);
    recvmsg(sock_fd, &msg, 0);
    printf("Kernel yanıtı: %s\n", (char *)NLMSG_DATA(nlh));

    close(sock_fd);
    return 0;
}

Bu bölümde

  • netlink_kernel_create: kernel modülü tarafında socket aç; cfg.input = callback
  • nlmsg_unicast: tek bir process'e yanıt; nlmsg_multicast: gruba yayın
  • NETLINK_MYMOD 31: özel protocol numarası (17–31 kullanıcı tanımlı)
  • Userspace: socket(AF_NETLINK, SOCK_RAW, NETLINK_MYMOD) + pid=0 hedef

08 Pratik uygulamalar

Üç senaryo: C ile route tablosunu listele, Generic Netlink aile + userspace client, nl80211 ile scan sonuçlarını oku.

C ile route tablosunu listele

C — RTM_GETROUTE ile route dump
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>

static void print_route(struct nlmsghdr *nlh)
{
    struct rtmsg *rtm = NLMSG_DATA(nlh);
    struct rtattr *rta;
    int rta_len = RTM_PAYLOAD(nlh);
    char dst_buf[INET_ADDRSTRLEN] = "0.0.0.0";
    char gw_buf[INET_ADDRSTRLEN]  = "-";
    uint32_t oif = 0;

    /* Yalnızca main route tablosu */
    if (rtm->rtm_table != RT_TABLE_MAIN)
        return;

    for (rta = RTM_RTA(rtm); RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
        switch (rta->rta_type) {
        case RTA_DST:
            inet_ntop(rtm->rtm_family, RTA_DATA(rta), dst_buf, sizeof(dst_buf));
            break;
        case RTA_GATEWAY:
            inet_ntop(rtm->rtm_family, RTA_DATA(rta), gw_buf, sizeof(gw_buf));
            break;
        case RTA_OIF:
            oif = *(uint32_t *)RTA_DATA(rta);
            break;
        default:
            break;
        }
    }

    char ifname[IF_NAMESIZE];
    if_indextoname(oif, ifname);
    printf("%-20s/%-3u  GW: %-16s  IF: %s\n",
           dst_buf, rtm->rtm_dst_len, gw_buf, ifname);
}

int main(void)
{
    int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
    bind(fd, (struct sockaddr *)&sa, sizeof(sa));

    /* RTM_GETROUTE isteği */
    struct { struct nlmsghdr nlh; struct rtmsg rtm; } req = {
        .nlh = {
            .nlmsg_len   = NLMSG_LENGTH(sizeof(struct rtmsg)),
            .nlmsg_type  = RTM_GETROUTE,
            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
            .nlmsg_seq   = 1,
        },
        .rtm = { .rtm_family = AF_INET },
    };
    send(fd, &req, req.nlh.nlmsg_len, 0);

    char buf[32768];
    printf("%-20s  %-19s  %s\n", "Hedef/Prefix", "Gateway", "Arayüz");
    printf("%-60s\n", "------------------------------------------------------------");

    while (1) {
        ssize_t len = recv(fd, buf, sizeof(buf), 0);
        struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
        for (; NLMSG_OK(nlh, (unsigned)len); nlh = NLMSG_NEXT(nlh, len)) {
            if (nlh->nlmsg_type == NLMSG_DONE)  goto done;
            if (nlh->nlmsg_type == RTM_NEWROUTE) print_route(nlh);
        }
    }
done:
    close(fd);
    return 0;
}
bash — derleme ve çıktı
gcc -Wall -o route_list route_list.c
./route_list

# Beklenen çıktı:
# Hedef/Prefix          Gateway              Arayüz
# ----------------------------------------------------------------
# 0.0.0.0             /0    GW: 192.168.1.1    IF: eth0
# 192.168.1.0         /24   GW: -              IF: eth0
# 127.0.0.0           /8    GW: -              IF: lo

nl80211 ile scan sonuçlarını oku

C — nl80211 scan dump (özet)
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <linux/nl80211.h>

static int scan_cb(struct nl_msg *msg, void *arg)
{
    struct nlattr *attrs[NL80211_ATTR_MAX + 1];
    struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));

    nla_parse(attrs, NL80211_ATTR_MAX,
              genlmsg_attrdata(gnlh, 0),
              genlmsg_attrlen(gnlh, 0), NULL);

    if (attrs[NL80211_ATTR_BSS]) {
        struct nlattr *bss[NL80211_BSS_MAX + 1];
        nla_parse_nested(bss, NL80211_BSS_MAX,
                         attrs[NL80211_ATTR_BSS], NULL);

        if (bss[NL80211_BSS_BSSID]) {
            uint8_t *bssid = nla_data(bss[NL80211_BSS_BSSID]);
            printf("BSSID: %02X:%02X:%02X:%02X:%02X:%02X",
                   bssid[0], bssid[1], bssid[2],
                   bssid[3], bssid[4], bssid[5]);
        }
        if (bss[NL80211_BSS_SIGNAL_MBM]) {
            int signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);
            printf("  Sinyal: %d dBm", signal / 100);
        }
        printf("\n");
    }
    return NL_SKIP;
}

void read_scan_results(uint32_t ifindex)
{
    struct nl_sock *sock = nl_socket_alloc();
    genl_connect(sock);
    int nl80211_id = genl_ctrl_resolve(sock, "nl80211");

    nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, scan_cb, NULL);

    struct nl_msg *msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ,
                nl80211_id, 0, NLM_F_DUMP,
                NL80211_CMD_GET_SCAN, 0);
    nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex);

    nl_send_auto(sock, msg);
    nlmsg_free(msg);
    nl_recvmsgs_default(sock);
    nl_socket_free(sock);
}
bash — derleme ve kullanım
gcc -Wall -o wifi_scan wifi_scan.c \
  $(pkg-config --cflags --libs libnl-3.0 libnl-genl-3.0)

# Taramayı önce tetikle
sudo iw dev wlan0 scan trigger

# Sonuçları oku (root veya CAP_NET_ADMIN gerekli)
sudo ./wifi_scan

# Beklenen çıktı:
# BSSID: AA:BB:CC:DD:EE:FF  Sinyal: -45 dBm
# BSSID: 11:22:33:44:55:66  Sinyal: -72 dBm

Bu bölümde

  • RTM_GETROUTE dump: route tablosunu oku; RTA_DST/GATEWAY/OIF ile parse
  • nl80211 scan dump: NL80211_CMD_GET_SCAN + NLM_F_DUMP; BSS attr ile AP bilgisi
  • nla_parse_nested: iç içe nlattr (BSS içindeki BSSID, signal vb.)
  • CAP_NET_ADMIN: çoğu netlink işlemi bu capability'yi gerektirir