AQE Sensor Interface Shield vicatcu

aqe-sensor-interface-shield / AQEWkshop / libraries / EtherCard / dhcp.cpp

7.0 KB · raw

// DHCP look-up functions based on the udp client
// http://www.ietf.org/rfc/rfc2131.txt
//
// Author: Andrew Lindsay
// Rewritten and optimized by Jean-Claude Wippler, http://jeelabs.org/
//
// Copyright: GPL V2
// See http://www.gnu.org/licenses/gpl.html

#include "EtherCard.h"
#include "net.h"

#define gPB ether.buffer

#define DHCP_BOOTREQUEST 1
#define DHCP_BOOTRESPONSE 2

// DHCP States for access in applications
enum {
    DHCP_STATE_INIT,
    DHCP_STATE_DISCOVER,
    DHCP_STATE_OFFER,
    DHCP_STATE_REQUEST,
    DHCP_STATE_ACK,
    DHCP_STATE_OK,
    DHCP_STATE_RENEW,
};

// size 236
typedef struct {
    byte op, htype, hlen, hops;
    uint32_t xid;
    word secs, flags;
    byte ciaddr[4], yiaddr[4], siaddr[4], giaddr[4];
    byte chaddr[16], sname[64], file[128];
    // options
} DHCPdata;

// src port high byte must be a a0 or higher:
#define DHCPCLIENT_SRC_PORT_H 0xe0 
#define DHCP_SRC_PORT 67
#define DHCP_DEST_PORT 68

static byte dhcpState;
static char hostname[] = "Arduino-00";
static uint32_t currentXid;
static uint32_t leaseStart;
static uint32_t leaseTime;
static byte* bufPtr;

static const byte allOnes[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

static void addToBuf (byte b) {
    *bufPtr++ = b;
}

static void addBytes (byte len, const byte* data) {
    while (len-- > 0)
        addToBuf(*data++);
}

static byte dhcp_ready () {
    if (dhcpState == DHCP_STATE_OK && millis() >= leaseStart + leaseTime)
        dhcpState = DHCP_STATE_RENEW;
    return dhcpState == DHCP_STATE_OK;
}

// Main DHCP sending function, either DHCP_STATE_DISCOVER or DHCP_STATE_REQUEST
static void dhcp_send (byte request) {
    if (!EtherCard::isLinkUp())
        return;
    dhcpState = request;
    memset(gPB, 0, UDP_DATA_P + sizeof( DHCPdata ));
    EtherCard::udpPrepare(DHCP_DEST_PORT, EtherCard::myip, DHCP_SRC_PORT);
    EtherCard::copyMac(gPB + ETH_SRC_MAC, EtherCard::mymac);
    EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes);
    gPB[IP_TOTLEN_L_P]=0x82;
    gPB[IP_PROTO_P]=IP_PROTO_UDP_V;
    EtherCard::copyIp(gPB + IP_DST_P, allOnes);
    
    // Build DHCP Packet from buf[UDP_DATA_P]
    DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P);
    dhcpPtr->op = DHCP_BOOTREQUEST;
    dhcpPtr->htype = 1;
    dhcpPtr->hlen = 6;
    dhcpPtr->xid = currentXid;
    EtherCard::copyMac(dhcpPtr->chaddr, EtherCard::mymac);
    
    // options defined as option, length, value
    bufPtr = gPB + UDP_DATA_P + sizeof( DHCPdata );
    // DHCP magic cookie, followed by message type
    static byte cookie[] = { 99, 130, 83, 99, 53, 1 };
    addBytes(sizeof cookie, cookie);
    // addToBuf(53);  // DHCP_STATE_DISCOVER, DHCP_STATE_REQUEST
    // addToBuf(1);   // Length 
    addToBuf(dhcpState);
    
    // Client Identifier Option, this is the client mac address
    addToBuf(61);     // Client identifier
    addToBuf(7);      // Length 
    addToBuf(0x01);   // Ethernet
    addBytes(6, EtherCard::mymac);
    
    addToBuf(12);     // Host name Option
    addToBuf(10);
    addBytes(10, (byte*) hostname);
    
    if( dhcpState == DHCP_STATE_REQUEST) {
        addToBuf(50); // Request IP address
        addToBuf(4);
        addBytes(4, EtherCard::myip);

        // Request using server ip address
        addToBuf(54); // Server IP address
        addToBuf(4);
        addBytes(4, EtherCard::dhcpip);
    }
    
    // Additional info in parameter list - minimal list for what we need
    static byte tail[] = { 55, 3, 1, 3, 6, 255 };
    addBytes(sizeof tail, tail);
    // addToBuf(55);     // Parameter request list
    // addToBuf(3);      // Length 
    // addToBuf(1);      // Subnet mask
    // addToBuf(3);      // Route/Gateway
    // addToBuf(6);      // DNS Server
    // addToBuf(255);    // end option

    // packet size will be under 300 bytes
    EtherCard::udpTransmit((bufPtr - gPB) - UDP_DATA_P);
}

static void have_dhcpoffer (word len) {
    // Map struct onto payload
    DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P);
    // Offered IP address is in yiaddr
    EtherCard::copyIp(EtherCard::myip, dhcpPtr->yiaddr);
    // Scan through variable length option list identifying options we want
    byte *ptr = (byte*) (dhcpPtr + 1) + 4;
    do {
        byte option = *ptr++;
        byte optionLen = *ptr++;
        switch (option) {
            case 1:  EtherCard::copyIp(EtherCard::mymask, ptr);
                     break;
            case 3:  EtherCard::copyIp(EtherCard::gwip, ptr);
                     break;
            case 6:  EtherCard::copyIp(EtherCard::dnsip, ptr);
                     break;
            case 51: leaseTime = 0;
                     for (byte i = 0; i<4; i++)
                         leaseTime = (leaseTime + ptr[i]) << 8;
                     leaseTime *= 1000;      // milliseconds
                     break;
            case 54: EtherCard::copyIp(EtherCard::dhcpip, ptr);
                     break;
        }
        ptr += optionLen;
    } while (ptr < gPB + len);
    dhcp_send(DHCP_STATE_REQUEST);
}

static void have_dhcpack (word /*len*/) {
    dhcpState = DHCP_STATE_OK;
    leaseStart = millis();
}

static void check_for_dhcp_answer (word len) {
    // Map struct onto payload
    DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P);
    if (len >= 70 && gPB[UDP_SRC_PORT_L_P] == DHCP_SRC_PORT &&
            dhcpPtr->op == DHCP_BOOTRESPONSE && dhcpPtr->xid == currentXid ) {
        int optionIndex = UDP_DATA_P + sizeof( DHCPdata ) + 4;
        if (gPB[optionIndex] == 53) {
            switch( gPB[optionIndex+2] ) {
                case DHCP_STATE_OFFER: have_dhcpoffer(len); break;
                case DHCP_STATE_OK:    have_dhcpack(len); break;
            }
        }
    }
}

// use during setup, as this discards all incoming requests until it returns
bool EtherCard::dhcpSetup () {
  currentXid = millis();
  // Set a unique hostname, use Arduino-?? with last octet of mac address
  hostname[8] = 'A' + (mymac[5] >> 4);
  hostname[9] = 'A' + (mymac[5] & 0x0F);
  myip[0] = 0; // force invalid IP address

  // Enable reception of broadcast packets as some DHCP servers
  // use this to send responses
  enableBroadcast();
  for (byte i = 0; i < 3; ++i) {
    dhcpState = DHCP_STATE_INIT;
    word start = millis();
    while (myip[0] == 0 && (word) (millis() - start) < 10000) {
      if (!isLinkUp())
        continue;
      word len = packetReceive();
      if (dhcpState != DHCP_STATE_INIT && (len == 0 || packetLoop(len) > 0))
        continue;
      
      switch (dhcpState) {
          case DHCP_STATE_INIT:
          case DHCP_STATE_RENEW:
              dhcp_send(DHCP_STATE_DISCOVER);
              break;
          case DHCP_STATE_DISCOVER:
          case DHCP_STATE_REQUEST:
              check_for_dhcp_answer(len);
              break;
          case DHCP_STATE_OK:
              ; //TODO wait for lease expiration
      }
    
      if (myip[0] != 0) {
        if (gwip[0] != 0)
          setGwIp(gwip);
        disableBroadcast();
        return true;
      }
    }
  }
  disableBroadcast();
  return false;
}

bool EtherCard::dhcpExpired () {
  return !dhcp_ready() && dhcpState == DHCP_STATE_RENEW;
}