]> git.rkrishnan.org Git - pihpsdr.git/commitdiff
Added a bootloader for the "ancient" (TCP-snifffing) bootloader protocol.
authorc vw <dl1ycf@darc.de>
Fri, 5 Aug 2022 08:13:12 +0000 (10:13 +0200)
committerc vw <dl1ycf@darc.de>
Fri, 5 Aug 2022 08:13:12 +0000 (10:13 +0200)
bootloader.c [new file with mode: 0644]

diff --git a/bootloader.c b/bootloader.c
new file mode 100644 (file)
index 0000000..09c42cc
--- /dev/null
@@ -0,0 +1,510 @@
+///////////////////////////////////////////////////////////////////////////
+//
+// HPSDR protocol-1 bootloader
+//
+// This program successfully communicates with the protocol-1 bootloader
+// insider the SDR.
+// It must be run as root since it "sniffs" on the Ethernet interface to
+// look for packets from the radio (using the pcap library),
+// so this is "spy-ware" but that's the requirement of the P1 bootloader.
+//
+// This is the reason why you cannot use this program accross a VPN or
+// a managed switch. This is based on illegal (or let's say non-routable)
+// data packets. You either have to use a "dump switch" or (better)
+// a direct-cable-connection.
+//
+// For older ANAN radios, you have to place a jumper on the HPSDR board 
+// to put the board into "bootloader" mode. More recent radios are more
+// comfortable, you simply have to change a switch (at the bottom or
+// the back panel of the case) from "normal" to "bootloader" position.
+//
+// The reason to have a bootloader depending on "non-routable" packets is,
+// that the radio probably does not even need an IP address to communicate
+// with the bootloader. All packets to/from the radio are identified by
+// the (bogus) MAC address 11:22:33:44:55:66 that the radio is using. The
+// main advantage of this "archaic" protocol is that with most ANAN radios,
+// you can use it even if the firmware that you have uploaded last time is
+// corrupt. In the "community slang", this can be used to "un-brick" your
+// radio.
+//
+// The core parts of the program have been taken from John Melton's
+// "bootloader" code from the TAPR github repository, and converted
+// to a command-line program for Linux and MacOS contained in a single file.
+//
+///////////////////////////////////////////////////////////////////////////
+//
+// Arguments:
+//
+// -d            Display IPv4 ethernet adapters present in the system,
+//               together with their IP and MAC address.
+//
+// -i <adapter>  Use specific adapter. The name <adapter> must match
+//               one of the adapters displayed with -d
+//
+// -s <ip-addr>  Set IP addr of radio to <ip-addr>
+//
+// -f <file>     The name of the firmware file to be uploaded
+//
+///////////////////////////////////////////////////////////////////////////
+//
+// -d is the default, it is over-ridden if -i is given.
+//
+// Without specifying valid device using the -i option, the -s and -f options
+// cannot work.
+//
+///////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pcap.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netinet/if_ether.h>
+#include <fcntl.h>
+#include <net/if_dl.h>
+#include <string.h>
+
+//
+// Forward declaration for the two functions that send something to the radio
+//
+void sendRawCommand(pcap_t *handle, unsigned char hw[6], unsigned char command, unsigned char *data, int datalen);
+void sendRawData(pcap_t *handle, unsigned char hw[6], unsigned char *data, off_t ptr);
+
+// HPSDR P1 bootloader command codes (we don't need those related to JTAG)
+
+#define PROGRAM_METIS_FLASH 0x01
+#define ERASE_METIS_FLASH   0x02
+#define READ_METIS_MAC      0x03
+#define READ_METIS_IP       0x04
+#define WRITE_METIS_IP      0x05
+
+// HPSDR bootloader reply codes
+
+#define ERASE_DONE       0x01  // Sent when the ERASE_METIS_FLASH command is completed.
+#define SEND_MORE        0x02  // Sent when the data of the PROGRAM_METIS_FLASH command has been processed.
+#define HAVE_MAC_ADDRESS 0x03  // Sent as response to READ_METIS_MAC
+#define HAVE_IP_ADDRESS  0x04  // Sent as response to READ_METIS_IP
+
+// The state of our program.
+
+#define STATE_QUERYMAC  0  // Query MAC address of radio
+#define STATE_QUERYIP   1  // Query IP address of radio
+#define STATE_SETIP     2  // Set IP addr.     Skipped if -s argument not given
+#define STATE_ERASE     3  // Erase EEPROM.    Skipped if -f argument not given
+#define STATE_PROGRAM   4  // Upload rbf file. Skipped if -f argument not given
+#define STATE_WAIT      5  // Wait for response from radio
+#define STATE_DONE      6  // All done.
+
+
+int main(int argc, char **argv)
+{
+    int i;
+    char *dev=NULL;
+    char errbuf[PCAP_ERRBUF_SIZE];
+    pcap_t* descr;
+    struct pcap_pkthdr hdr;
+    const u_char *packet;
+    unsigned char mymac[6];  // Mac address of host
+    unsigned char myip[4];   // IP  address of host
+    unsigned char  hisip[4]; // IP  address to burn into radio
+
+    pcap_if_t *devlist,*ifp;
+    pcap_addr_t *addr;
+    struct sockaddr *sa;
+    struct sockaddr_dl *link;
+
+    char string[256];
+    int have_addr, have_mac;
+    int do_display, do_lookup;
+    char *rbffile;
+    int rbffd;
+    int timeout;
+    off_t rbflen,rbfxfr,rbfptr;
+    int state;
+    unsigned char *rbfcontent;  // the whole .rbf file (+ padding)
+
+    char *cp;
+
+    do_display=1;
+    do_lookup=0;
+    hisip[0]=0;
+    rbffile=NULL;
+
+    i=1;
+    while (i < argc) {
+      if (!strcmp(argv[i],"-d")) {
+        do_display=1;
+        do_lookup=0;
+      }
+      if (!strcmp(argv[i],"-i") && i+1 < argc) {
+        dev=argv[++i];
+        do_display=0;
+        do_lookup=1;
+      }
+      if (!strcmp(argv[i],"-f") && i+1 < argc) {
+        rbffile=argv[++i];
+      }
+      if (!strcmp(argv[i],"-s") && i+1 < argc) {
+        int i1,i2,i3,i4;
+        i++;
+        if (sscanf(argv[i],"%d.%d.%d.%d",&i1,&i2,&i3,&i4) < 4) {
+          printf("Could not determine IP addr from string %s\n", argv[i]);
+        } else {
+          hisip[0]=i1;
+          hisip[1]=i2;
+          hisip[2]=i3;
+          hisip[3]=i4;
+        }
+      }
+      i++;
+    }
+
+    //
+    // list devices
+    //
+    if (pcap_findalldevs(&devlist, errbuf) == -1) {
+        fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
+        exit(1);
+    }
+    
+    ifp=devlist;
+    while (ifp != NULL) {
+      if (do_lookup && strcmp(ifp->name, dev)) {
+        // we are looking for a specific interface, skip all others
+        ifp=ifp->next;
+        continue;
+      }
+      have_addr=0;
+      have_mac=0;
+      addr=ifp->addresses;
+      while (addr != NULL) {
+        sa=addr->addr;
+        if (sa->sa_family == AF_INET) {
+          int i1, i2, i3, i4;
+          (void) inet_ntop(AF_INET, (void *)&(((struct sockaddr_in *)sa)->sin_addr), string, 256);
+          if (sscanf(string,"%d.%d.%d.%d",&i1,&i2,&i3,&i4) == 4) {
+            myip[0]=i1;
+            myip[1]=i2;
+            myip[2]=i3;
+            myip[3]=i4;
+            if (i > 127) have_addr=1;
+          }
+        }
+        if (sa->sa_family == AF_LINK) {
+          link=(struct sockaddr_dl *)sa->sa_data;
+          unsigned char mac[link->sdl_alen];
+          memcpy(mac, LLADDR(link), link->sdl_alen);
+          if (link->sdl_alen == 6) {
+            mymac[0]=mac[0];
+            mymac[1]=mac[1];
+            mymac[2]=mac[2];
+            mymac[3]=mac[3];
+            mymac[4]=mac[4];
+            mymac[5]=mac[5];
+          }
+          if (link->sdl_alen > 6) {
+            //
+            // This happens on MacOS
+            //
+            mymac[0]=mac[1];
+            mymac[1]=mac[2];
+            mymac[2]=mac[3];
+            mymac[3]=mac[4];
+            mymac[4]=mac[5];
+            mymac[5]=mac[6];
+          }
+          have_mac=1;
+        }
+        addr=addr->next;
+      }
+      if (have_addr && have_mac) {
+        printf("Interface=%-10s Address=(%3d,%3d,%3d,%3d) MAC=%02x:%02x:%02x:%02x:%02x:%02x\n",
+            ifp->name,
+            myip[0], myip[1], myip[2], myip[3],
+            mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
+      }
+      ifp=ifp->next;
+    }
+    pcap_freealldevs(devlist);
+
+    //
+    // If no suitable interface has been specified
+    // (or if this is a "display" run), give up.
+    //
+    if (dev == NULL || !have_addr || !have_mac) {
+      return 8;
+    }
+
+    //
+    //  Check some assumptions about RBF file
+    //
+    rbffd = -1;
+    if (rbffile != NULL) {
+      rbffd=open(rbffile, O_RDONLY);
+      if (rbffd < 0) {
+        perror("Open RBF file");
+      }
+      i=strlen(rbffile);
+      if (i < 5) {
+        rbffd = -1;
+      } else {
+        if (strcmp(rbffile+i-4,".rbf")) {
+          printf("Firmware file name does not end in .rbf, ignoring.\n");
+          rbffd = -1;
+        }
+      }
+      if (rbffd >= 0) {
+        rbflen=lseek(rbffd, 0, SEEK_END);
+        if (rbflen < 100000UL) {
+          printf("RBF file seems unreasonably short!\n");
+          close(rbffd);
+          rbffd=-1;
+        } else {
+          printf("RBF file %s length = %ld bytes\n", rbffile, (long) rbflen);
+          if (rbflen % 256 != 0) {
+            rbfxfr=256 * (rbflen/256 +1);
+          } else {
+            rbfxfr = rbflen;
+          }
+          printf("RBF transfer length = %ld bytes (%d blocks)\n", (long) rbfxfr, (int) (rbfxfr / 256));
+        }
+      }
+    }
+    if (hisip[0] > 127) {
+      printf("Shall burn IP addr = (%d,%d,%d,%d) into radio.\n",hisip[0],hisip[1],hisip[2],hisip[3]);
+    }
+    if (hisip[0] > 127 || rbffd >= 0) {
+      printf("Is this OK (y/n)?\n");
+      i=getchar();
+      if (i != 'y' && i != 'Y') return 0;
+    }
+    //
+    // Read rbf file into memory
+    //
+    if (rbffd >= 0) {
+      rbfcontent=malloc(rbfxfr);
+      if (rbfcontent == NULL) {
+        perror("Allocation of memory for RBF file");
+        return 0;
+      }
+      lseek(rbffd, 0, SEEK_SET);
+      if (read(rbffd, rbfcontent, rbflen) != rbflen) {
+        printf("RBF file read error, terminating\n");
+        return 0;
+      }
+      close(rbffd);
+      // pad with 0xFF up to the next multiple of 256
+      for (i=rbflen; i<rbfxfr; i++) {
+        rbfcontent[i]=0xff;
+      } 
+      rbfptr=0;
+      printf("RBF file read into memory.\n");
+    }
+   
+    //
+    // From this point on we need admin privileges
+    //
+    descr = pcap_open_live(dev,1024,1,10,errbuf);
+    if(descr == NULL) {
+        printf("pcap_open_live(): %s\n",errbuf);
+        exit(1);
+    }
+
+    state=STATE_QUERYMAC;
+    timeout=0;
+    //
+    // We usually use a time-out of 500 msec (that is generous)
+    // except when erasing, since this may take MUCH longer
+    //
+    for (;;) {
+      //
+      // Perform action associated with the state
+      //
+      switch (state) {
+        case STATE_QUERYMAC:
+          sendRawCommand(descr, mymac, READ_METIS_MAC, NULL, 0);
+          timeout=50;
+          state=STATE_WAIT;
+          break;
+        case STATE_QUERYIP:
+           sendRawCommand(descr, mymac, READ_METIS_IP, NULL, 0);
+           timeout=50;
+           state=STATE_WAIT;
+           break;
+        case STATE_SETIP:
+           sendRawCommand(descr, mymac, WRITE_METIS_IP, hisip, 4);
+           printf("New IP address written to board.\n");
+           // Since there will be no answer, wait 100 msecs before proceeding
+           state=STATE_DONE;
+           if (rbffd >= 0) {
+             state=STATE_ERASE;
+           }
+           break;
+        case STATE_ERASE:
+           sendRawCommand(descr, mymac, ERASE_METIS_FLASH, NULL, 0);
+           printf("Erasing (THIS TAKES SOME TIME!)\n");
+           timeout=18000; // up to 3 minutes
+           state=STATE_WAIT;
+           break;
+        case STATE_PROGRAM:
+          sendRawData(descr, mymac, rbfcontent, rbfptr);
+          rbfptr +=256;
+          if (rbfptr % 1024 == 0) {
+            printf("Data sent: %6ld k-Bytes\r", (long) rbfptr / 1024);
+            fflush(stdout);
+          }
+          timeout=50;
+          state=STATE_WAIT;
+          break;
+        case STATE_WAIT:
+          //
+          // Terminate program if timeout is reached.
+          // The "wait" state is changed if a valid response
+          // packet is captured.
+          //
+          timeout--;
+          if (timeout == 0) {
+            printf("TIMEOUT reached - terminating bootloader.\n");
+            return(0);
+          }
+          break;
+        case STATE_DONE:
+          printf("All Done.\n");
+          return (0);
+      }
+
+      //
+      // Obtain next packet
+      //
+      packet = pcap_next(descr,&hdr);
+      if (packet == NULL) continue; // Nothing arrived within time-out
+
+      /* determine if this is a packet from a bootloader to our computer */
+      if (hdr.len > 22 && packet[0] == mymac[0] && packet[1] == mymac[1] && packet[2] == mymac[2]
+                       && packet[3] == mymac[3] && packet[4] == mymac[4] && packet[5] == mymac[5]
+                       && packet[6] == 0x11     && packet[7] == 0x22     && packet[8] == 0x33    
+                       && packet[9] == 0x44     && packet[10]== 0x55     && packet[11]== 0x66    
+                       && packet[12] == 0xef && packet[13] == 0xfe && packet[14] == 0x03) {
+       
+          switch (packet[15]) {
+            case HAVE_MAC_ADDRESS:
+              printf("HPSDR board detected, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", packet[16], packet[17], packet[18], packet[19], packet[20], packet[21]);
+              state=STATE_QUERYIP;
+              break;
+            case HAVE_IP_ADDRESS:
+              printf("Board has IP addr (%d,%d,%d,%d).\n", packet[16], packet[17], packet[18], packet[19]);
+              state=STATE_DONE;
+              if (rbffd >= 0) state=STATE_ERASE;     // if there is something to upload
+              if (hisip[0] > 127) state=STATE_SETIP; // if we want to set an IP address
+              break;
+            case ERASE_DONE:
+              if (rbffd >= 0) {
+                printf("Board erased, about to upload new RBF file.\n");
+                state=STATE_PROGRAM;
+              } else {
+                // this cannot happen
+                state=STATE_DONE;
+              }
+              break;
+            case SEND_MORE:
+              if (rbffd < 0 || rbfptr > rbfxfr) {
+                printf("\nRBF file upload complete.\n");
+                state=STATE_DONE;
+              } else {
+                state=STATE_PROGRAM;
+              }
+              break;
+            default:
+              printf("UNKNOWN REPLY=%d\n", packet[15]);
+              break;
+          }
+      }
+    }
+}
+
+//
+// Send a 62-byte buffer to the radio containing a "command"
+//
+void sendRawCommand(pcap_t *handle, unsigned char hw[6], unsigned char command, unsigned char *data, int datalen) {
+    unsigned char buffer[62];
+    int i;
+
+    /*set the frame header*/
+    buffer[0]=0x11; // use "bogus" radio MAC address for destination
+    buffer[1]=0x22;
+    buffer[2]=0x33;
+    buffer[3]=0x44;
+    buffer[4]=0x55;
+    buffer[5]=0x66;
+
+    buffer[6]=hw[0]; // use own MAC address as source
+    buffer[7]=hw[1];
+    buffer[8]=hw[2];
+    buffer[9]=hw[3];
+    buffer[10]=hw[4];
+    buffer[11]=hw[5];
+
+    buffer[12]=0xEF; // protocol
+    buffer[13]=0xFE;
+
+    buffer[14]=0x03;
+    buffer[15]=command;
+
+    /*fill the frame with 0x00*/
+    for(i=0;i<46;i++) {
+            buffer[i+16]=(unsigned char)0x00;
+    }
+    /* possibly fill in data */
+    for (i=0; i<datalen; i++) {
+      buffer[i+16] = data[i];
+    }
+
+    if(pcap_sendpacket(handle,buffer,62)!=0) {
+         printf("Send Raw failed, command=%d\n", command);
+    }
+}
+
+//
+// Send a 276-byte buffer to the radio that contains data from the rbf file
+//
+void sendRawData(pcap_t *handle, unsigned char hw[6], unsigned char *data, off_t ptr) {
+    unsigned char buffer[276];
+
+
+    /*set the frame header*/
+    buffer[0]=0x11; // use "bogus" MAC address of radio for destination
+    buffer[1]=0x22;
+    buffer[2]=0x33;
+    buffer[3]=0x44;
+    buffer[4]=0x55;
+    buffer[5]=0x66;
+
+    buffer[6]=hw[0]; // use "own" MAC address as source
+    buffer[7]=hw[1];
+    buffer[8]=hw[2];
+    buffer[9]=hw[3];
+    buffer[10]=hw[4];
+    buffer[11]=hw[5];
+
+    buffer[12]=0xEF; // protocol
+    buffer[13]=0xFE;
+
+    buffer[14]=0x03;
+    buffer[15]=PROGRAM_METIS_FLASH;
+
+    buffer[16]=0x00;
+    buffer[17]=0x00;
+    buffer[18]=0x00;
+    buffer[19]=0x00;
+
+    for(int i=0;i<256;i++) {
+        buffer[i+20]=data[i+ptr];
+    }
+
+    if (pcap_sendpacket(handle,buffer,276)!=0) {
+      printf("Send data block failed!\n");
+    }
+}