>From 8df0fd4d0a972bd76bb71d7fdac274b273cec50d Mon Sep 17 00:00:00 2001 From: Rakesh Ranjan Date: Mon, 16 Nov 2009 18:32:57 +0530 Subject: [PATCH 2/2] dhcp handler for libiscsi to support dhcp provisioning for offload capable cards. This module contains a compact dhcp handler to support dhcp based provisioning for offload capable cards. Signed-off-by: Rakesh Ranjan --- libiscsi_ipconfig.c | 466 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 466 insertions(+), 0 deletions(-) create mode 100644 libiscsi_ipconfig.c diff --git a/libiscsi_ipconfig.c b/libiscsi_ipconfig.c new file mode 100644 index 0000000..4057aae --- /dev/null +++ b/libiscsi_ipconfig.c @@ -0,0 +1,466 @@ +/* libiscsi_ipconfig.c: libiscsi dhcp client. + * + * Copyright (c) 2009 Chelsio Communications, Inc. + * + * 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. + * + * Written by: Rakesh Ranjan (rakesh@chelsio.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DHCP_REQUEST 1 +#define DHCP_REPLY 2 +#define DHCP_HTYPE_ETHERNET 1 +#define DHCP_HLEN_ETHERNET 6 +#define DHCP_MSG_LEN 236 + +#define DHCPC_SERVER_PORT 67 +#define DHCPC_CLIENT_PORT 68 + +/* DHCP message types */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +/* DHCP options */ +#define DHCP_OPTION_SUBNET_MASK 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_DNS_SERVER 6 +#define DHCP_OPTION_MTU 26 +#define DHCP_OPTION_REQ_IPADDR 50 +#define DHCP_OPTION_LEASE_TIME 51 +#define DHCP_OPTION_MSG_TYPE 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_REQ_LIST 55 +#define DHCP_OPTION_VCID 60 +#define DHCP_OPTION_END 255 + +enum { + STATE_INIT = 0, + STATE_SENDING, + STATE_OFFER_REC, + STATE_CONFIG_REC, +}; + +struct dhcp_pkt { + struct iphdr iph; + struct udphdr udph; + u8 op; + u8 htype; + u8 hlen; + u8 hops; + __be32 xid; + __be16 secs; + __be16 flags; + __be32 cipaddr; + __be32 yipaddr; + __be32 sipaddr; + __be32 ripaddr; + u8 chwaddr[16]; + u8 sname[64]; + u8 bfile[128]; + u8 options[312]; +}; + + +static struct dhcp_client_state { + struct sk_buff *skb; + struct dhcp_pkt *pkt; + struct net_device *ndev; + struct list_head list; + struct dhcp_info dinfo; + + volatile __u8 state; + __be32 xid; + +} client_state; + +static DEFINE_SPINLOCK(rcv_lock); + +static const char *RFC2132_VENDOR_CLASS_ID = "libiscsi client"; + +static const u8 magic_cookie[4] = { 99, 130, 83, 99 }; + +static inline u8 *add_msg_type(u8 *optptr, u8 type) +{ + *optptr++ = DHCP_OPTION_MSG_TYPE; + *optptr++ = 1; + *optptr++ = type; + return optptr; +} + +static inline u8 *add_req_options(u8 *optptr) +{ + *optptr++ = DHCP_OPTION_REQ_LIST; + *optptr++ = 4; + *optptr++ = DHCP_OPTION_SUBNET_MASK; + *optptr++ = DHCP_OPTION_ROUTER; + *optptr++ = DHCP_OPTION_DNS_SERVER; + *optptr++ = DHCP_OPTION_MTU; + return optptr; +} + +static inline u8 *add_vendor_cid(u8 *optptr) +{ + u8 len = strlen(RFC2132_VENDOR_CLASS_ID); + *optptr++ = DHCP_OPTION_VCID; + *optptr++ = len; + memcpy(optptr, RFC2132_VENDOR_CLASS_ID, len); + optptr += len; + return optptr; +} + +static inline u8 *add_server_id(__be32 *sid, u8 *optptr) +{ + *optptr++ = DHCP_OPTION_SERVER_ID; + *optptr++ = 4; + memcpy(optptr, sid, 4); + return optptr + 4; +} + +static inline u8 *add_req_ipaddr(__be32 *ip, u8 *optptr) +{ + *optptr++ = DHCP_OPTION_REQ_IPADDR; + *optptr++ = 4; + memcpy(optptr, ip, 4); + return optptr + 4; +} + +static inline u8 *add_end(u8 *optptr) +{ + *optptr++ = DHCP_OPTION_END; + return optptr; +} + +static void +libiscsi_process_dhcp_opts(struct dhcp_client_state *client, u8 *optptr, + int len) +{ + u8 *end = optptr + len; + u8 type = 0; + + while (optptr < end) { + switch (*optptr) { + case DHCP_OPTION_SUBNET_MASK: + memcpy(&client->dinfo.netmask, optptr + 2, 4); + break; + case DHCP_OPTION_ROUTER: + memcpy(&client->dinfo.gipaddr, optptr + 2, 4); + break; + case DHCP_OPTION_DNS_SERVER: + memcpy(&client->dinfo.dnsaddr, optptr + 2, 4); + break; + case DHCP_OPTION_MSG_TYPE: + type = *(optptr + 2); + if (type == DHCPOFFER) + client->state = STATE_OFFER_REC; + else if (type == DHCPACK) + client->state = STATE_CONFIG_REC; + break; + case DHCP_OPTION_SERVER_ID: + memcpy(&client->dinfo.serverid, optptr + 2, 4); + break; + case DHCP_OPTION_LEASE_TIME: + memcpy(&client->dinfo.ltime, optptr + 2, 4); + break; + case DHCP_OPTION_END: + break; + } + + optptr += optptr[1] + 2; + } +} + +static void +libiscsi_process_dhcp_pack(struct dhcp_client_state *client, + struct dhcp_pkt *pkt) +{ + u8 *start, *end; + int opt_len; + + start = &pkt->options[4]; + end = (u8 *) pkt + ntohs(pkt->iph.tot_len); + opt_len = end - start; + + if (pkt->op == DHCP_REPLY && + !memcmp(&pkt->xid, &client->xid, sizeof(client->xid)) && + !memcmp(pkt->chwaddr, client->dinfo.mac_addr, pkt->hlen)) { + memcpy(&client->dinfo.ipaddr, &pkt->yipaddr, 4); + libiscsi_process_dhcp_opts(client, start, opt_len); + } +} + +static int +libiscsi_ipconfig_send(struct dhcp_client_state *client) +{ + int rc = 0; + struct sk_buff *lskb = client->skb; + + lskb->dev = client->ndev; + lskb->protocol = htons(ETH_P_IP); + + dev_hard_header(lskb, client->ndev, ntohs(lskb->protocol), + client->ndev->broadcast, + client->dinfo.mac_addr, lskb->len); + rc = dev_queue_xmit(lskb); + + return rc; +} + +int +libiscsi_ipconfig_recv(struct net_device *ndev, struct sk_buff *skb) +{ + struct iphdr *iph; + struct udphdr *udph; + struct ethhdr *eh; + struct dhcp_pkt *pkt; + struct sk_buff *pskb; + int len, opts_len; + struct dhcp_client_state *client; + int rc = 0; + + + client = &client_state; + + if (unlikely(client->state == STATE_INIT)) + goto out; + + if (skb->pkt_type != PACKET_OTHERHOST) + goto out; + + pskb = skb_copy(skb, GFP_ATOMIC); + if (!pskb) { + rc = -ENOMEM; + goto out; + } + + eh = eth_hdr(pskb); + if (!is_valid_ether_addr(eh->h_dest)) + goto drop; + + if (compare_ether_addr(eh->h_dest, client->dinfo.mac_addr)) + goto drop; + + if (!pskb_may_pull(pskb, sizeof(struct iphdr) + sizeof(struct udphdr))) + goto drop; + + + skb_reset_network_header(pskb); + pkt = (struct dhcp_pkt *) skb_network_header(pskb); + iph = &pkt->iph; + + if (iph->ihl != 5 || iph->version != 4 || iph->protocol != IPPROTO_UDP) + goto drop; + + if (iph->frag_off & htons(IP_OFFSET | IP_MF)) + goto drop; + + if (skb->len < ntohs(iph->tot_len)) + goto drop; + + if (ip_fast_csum((u8 *)iph, iph->ihl)) + goto drop; + + udph = &pkt->udph; + if (udph->source != htons(67) || udph->dest != htons(68)) + goto drop; + + if (ntohs(iph->tot_len) < ntohs(udph->len) + sizeof(struct iphdr)) + goto drop; + + len = ntohs(udph->len) - sizeof(struct udphdr); + opts_len = len - (sizeof(*pkt) - + sizeof(struct iphdr) - + sizeof(struct udphdr) - + sizeof(pkt->options)); + if (opts_len < 0) + goto drop; + + if (memcmp(pkt->options, magic_cookie, 4)) + goto drop; + + spin_lock(&rcv_lock); + + libiscsi_process_dhcp_pack(client, pkt); + + spin_unlock(&rcv_lock); + +drop: + kfree(pskb); +out: + return rc; +} +EXPORT_SYMBOL(libiscsi_ipconfig_recv); + +static int +libiscsi_create_dhcp_msg(struct dhcp_client_state *client) +{ + struct iphdr *iph; + struct udphdr *udph; + struct sk_buff *skb; + struct dhcp_pkt *pkt; + int rc = 0; + + skb = alloc_skb(sizeof(*pkt) + + LL_ALLOCATED_SPACE(client->ndev) + 15, + GFP_KERNEL); + if (!skb) { + rc = -ENOMEM; + return rc; + } + + client->skb = skb; + skb_reserve(skb, LL_RESERVED_SPACE(client->ndev)); + + pkt = (struct dhcp_pkt *) skb_put(skb, sizeof(*pkt)); + BUG_ON(!pkt); + client->pkt = pkt; + memset(pkt, 0, sizeof(*pkt)); + + skb_reset_network_header(skb); + + /* construct IP header */ + iph = &pkt->iph; + iph->version = 4; + iph->ihl = 5; + iph->tot_len = htons(sizeof(struct dhcp_pkt)); + iph->frag_off = htons(IP_DF); + iph->ttl = 64; + iph->protocol = IPPROTO_UDP; + iph->daddr = htonl(INADDR_BROADCAST); + iph->check = ip_fast_csum((u8 *) iph, iph->ihl); + + /* Construct UDP header */ + udph = &pkt->udph; + udph->source = htons(DHCPC_CLIENT_PORT); + udph->dest = htons(DHCPC_SERVER_PORT); + udph->len = htons(sizeof(struct dhcp_pkt) - sizeof(struct iphdr)); + + pkt->op = DHCP_REQUEST; + pkt->htype = DHCP_HTYPE_ETHERNET; + pkt->hlen = ETH_ALEN; + + memcpy(pkt->chwaddr, client->dinfo.mac_addr, ETH_ALEN); + pkt->secs = htons(jiffies / HZ); + pkt->xid = client->xid; + + memcpy(pkt->options, magic_cookie, sizeof(magic_cookie)); + + return rc; +} + +static int libiscsi_send_dhcp_request(struct dhcp_client_state *client) +{ + int rc = 0; + u8 *end; + + rc = libiscsi_create_dhcp_msg(client); + if (rc) + return rc; + + end = add_msg_type(&client->pkt->options[4], DHCPREQUEST); + end = add_server_id(&client->dinfo.serverid, end); + end = add_req_ipaddr(&client->dinfo.ipaddr, end); + end = add_vendor_cid(end); + end = add_end(end); + + rc = libiscsi_ipconfig_send(client); + + return rc; +} + +static int libiscsi_send_dhcp_discover(struct dhcp_client_state *client) +{ + int rc = 0; + u8 *end; + + rc = libiscsi_create_dhcp_msg(client); + if (rc) + return rc; + + end = add_msg_type(&client->pkt->options[4], DHCPDISCOVER); + end = add_req_options(end); + end = add_vendor_cid(end); + end = add_end(end); + + client->state = STATE_SENDING; + rc = libiscsi_ipconfig_send(client); + + return rc; +} + +static void +libiscsi_wait_for_pack(struct dhcp_client_state *client, u8 state) +{ + unsigned long tout, ntout; + + get_random_bytes(&tout, sizeof(tout)); + tout = (tout % (unsigned)HZ) + (HZ * 2); + + ntout = jiffies + tout; + while (time_before(jiffies, ntout) && (client->state != state)) + schedule_timeout_uninterruptible(1); +} + +int libiscsi_do_ipconf(struct net_device *ndev, struct dhcp_info *dinfo) +{ + int rc = 0; + int retry; + struct dhcp_client_state *client; + retry = 2; + + client = &client_state; + client->dinfo.mac_addr = dinfo->mac_addr; + client->ndev = ndev; + client->state = STATE_INIT; + + /* show time */ + for (;;) { + get_random_bytes(&client->xid, sizeof(__be32)); + libiscsi_send_dhcp_discover(client); + libiscsi_wait_for_pack(client, STATE_OFFER_REC); + + if (client->state == STATE_OFFER_REC) { + libiscsi_send_dhcp_request(client); + libiscsi_wait_for_pack(client, STATE_CONFIG_REC); + if (client->state == STATE_CONFIG_REC) { + dinfo->ipaddr = client->dinfo.ipaddr; + dinfo->netmask = client->dinfo.netmask; + dinfo->ltime = client->dinfo.ltime; + client->state = STATE_INIT; + break; + } + } + + if (!--retry) { + rc = -ENETUNREACH; + break; + } + } + + return rc; +} +EXPORT_SYMBOL(libiscsi_do_ipconf); + +MODULE_AUTHOR("Rakesh Ranjan"); +MODULE_DESCRIPTION("iSCSI ipconfig functions"); +MODULE_LICENSE("GPL"); -- 1.6.0.6