본문 바로가기
DSP, MCU/STM32 (ARM Cortex-M)

STM32 ] TFTP Server

by eteo 2023. 4. 14.

 

 

Board : STM32F429ZI (Nucleo 144)

STM32CubeIDE : version 1.10.1

Firmware Package : FW_F4 V1.27.1

 

 

 

 

아래 경로 예제의 tftpserver.h, tftpserver.c 참조

C:\Users\jo\STM32Cube\Repository\STM32Cube_FW_F4_V1.27.1\Projects\STM324x9I_EVAL\Applications\LwIP\LwIP_TFTP_Server

 

 

STM32보드를 TFTP 서버로 운용하며 클라이언트의 요청을 받아서, 클라이언트로부터 이더넷을 통해 파일을 수신해 보드에 연결된 USB MSC에 저장 또는 USB에 저장된 파일을 이더넷을 통해 클라이언트에게 송신

 

 

 

순서대로 살펴보자.

 

 

void tftpd_init(void)

void tftpd_init(void)
{
  err_t err;
  unsigned port = 69;

  /* create a new UDP PCB structure  */
  UDPpcb = udp_new();
  if (UDPpcb)
  {  
    /* Bind this PCB to port 69  */
    err = udp_bind(UDPpcb, IP_ADDR_ANY, port);
    if (err == ERR_OK)
    {    
      /* TFTP server start  */
      udp_recv(UDPpcb, recv_callback_tftp, NULL);
    }
  }
}

 

메인에서 LWIP init 끝나고 초기화 부분에 호출한다.

먼저 udp pcb를 생성하고 69번 포트에 bind한다.

그리고 recv_callback_tftp() 콜백함수를 등록한다. 이제 69번 포트로 들어오는 패킷은 recv_callback_tftp 함수가 호출된다.

 

 

 

 

 

 

 

void recv_callback_tftp(void *arg, struct udp_pcb *upcb, struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)

void recv_callback_tftp(void *arg, struct udp_pcb *upcb, struct pbuf *pkt_buf,
                        const ip_addr_t *addr, u16_t port)
{
  /* process new connection request */
  process_tftp_request(pkt_buf, addr, port);

  /* free pbuf */
  pbuf_free(pkt_buf);
}

 

콜백함수 안에서 처리해도 될 텐데 함수화 해두었다. process_tftp_request() 함수를 통해 수신패킷을 처리했으니 pbuf_free() 해준다.

 

 

 

 

 

 

 

 

 

 

 process_tftp_request() 함수를 보기전에, tftputils 파일에는 패킷에서 특정 필드를 파싱하거나 또는 특정 필드 값을 설정하는 함수가 있다. 그냥 ftfpserver 파일 위에 만들어놔도 될 것같은데 분리해뒀다.

 

tftp_opcode tftp_decode_op(char *buf)

tftp_opcode tftp_decode_op(char *buf)
{
  return (tftp_opcode)(buf[1]);
}

 

TFTP 메시지에서 opcode를 디코드한다. opcode 는 메시지에서 앞의 2byte를 차지하는데 네트워크오더는 빅엔디안 이니까 buf[1]을까서 보면 된다. enum인 tftp_opcode로 캐스팅해서 리턴한다.

 

 

u16_t tftp_extract_block(char *buf)

u16_t tftp_extract_block(char *buf)
{
  u16_t *b = (u16_t*)buf;
  return ntohs(b[1]);
}

 

block number의 경우 DATA 패킷 또는 ACK패킷에서 opcode 다음에오는 두번째 2byte에 위치한다. uint16_t 포인터로 접근해서 [1] index의 값을 네트워크 오더에서 호스트 오더인 리틀엔디안으로 바꿔 리턴한다. 예를들어 0x0001이라면 0x1000 리턴

 

 

 

void tftp_extract_filename(char *fname, char *buf)

void tftp_extract_filename(char *fname, char *buf)
{
  strcpy(fname, buf + 2);
}

 

filename은 RRQ/WRQ 패킷에서 opcode 다음에 온다. buf+2 부터 null문자까지 strcpy한다.

 

 

void tftp_set_opcode(char *buffer, tftp_opcode opcode)

void tftp_set_opcode(char *buffer, tftp_opcode opcode)
{

  buffer[0] = 0;
  buffer[1] = (u8_t)opcode;
}

 

opcode를 패킷에 설정한다. 빅 엔디안이라 buf[0]은 항상 0이고 buff[1]에 채워넣으면 된다.

 

 

 

void tftp_set_errorcode(char *buffer, tftp_errorcode errCode)

void tftp_set_errorcode(char *buffer, tftp_errorcode errCode)
{

  buffer[2] = 0;
  buffer[3] = (u8_t)errCode;
}

 

ERROR 패킷에서 opcode 다음에오는 2byte에 위치한 에러코드를 설정한다.

 

 

 

 

 

void tftp_set_errormsg(char * buffer, char* errormsg)

void tftp_set_errormsg(char * buffer, char* errormsg)
{
  strcpy(buffer + 4, errormsg);
}

 

ERROR패킷에서 문자열 에러메시지를 null문자까지 포함해 카피한다.

 

 

void tftp_set_block(char* packet, u16_t block)

void tftp_set_block(char* packet, u16_t block)
{

  u16_t *p = (u16_t *)packet;
  p[1] = htons(block);
}

 

블락넘버 설정 uint16_t 블락넘버 값을 빅엔디안으로 바꿔 opcode 다음에 오는 2byte에 대입한다.

 

 

 

void tftp_set_data_message(char* packet, char* buf, int buflen)

void tftp_set_data_message(char* packet, char* buf, int buflen)
{
  memcpy(packet + 4, buf, buflen);
}

 

DATA 패킷의 data 부분 복사

 

 

 

u32_t tftp_is_correct_ack(char *buf, int block)

u32_t tftp_is_correct_ack(char *buf, int block)
{
  /* first make sure this is a data ACK packet */
  if (tftp_decode_op(buf) != TFTP_ACK)
    return 0;

  /* then compare block numbers */
  if (block != tftp_extract_block(buf))
    return 0;

  return 1;
}

 

올바른 ACK인지 확인. 먼저 opcode가 ACK인지 보고, 파라미터로 들어온 기대한 block number와 일치하는지 확인한다.

 

 

 

 

 

 

 

 

 

 

void process_tftp_request(struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)

void process_tftp_request(struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)
{
  tftp_opcode op = tftp_decode_op(pkt_buf->payload);
  char FileName[30];
  struct udp_pcb *upcb;
  err_t err;

  /* create new UDP PCB structure */
  upcb = udp_new();
  if (!upcb)
  {     
    /* Error creating PCB. Out of Memory  */
    return;
  }

  /* bind to port 0 to receive next available free port */
  /* NOTE:  This is how TFTP works.  There is a UDP PCB for the standard port
   * 69 which al transactions begin communication on, however all subsequent
   * transactions for a given "stream" occur on another port!  */
  err = udp_bind(upcb, IP_ADDR_ANY, 0);
  if (err != ERR_OK)
  {    
    /* Unable to bind to port   */
    return;
  }
  switch (op)
  {
    case TFTP_RRQ:/* TFTP RRQ (read request) */
    {
      /* Read the name of the file asked by the client to be sent from the SD card */
      tftp_extract_filename(FileName, pkt_buf->payload);

      /* Could not open filesystem */
      if(f_mount(&filesystem, (TCHAR const*)"", 0) != FR_OK)
      {
        return;
      }
      /* Could not open the selected directory */
      if (f_opendir(&dir_1, "/") != FR_OK)
      {
        return;
      }
      /* Start the TFTP read mode*/
      tftp_process_read(upcb, addr, port, FileName);
      break;
    } 

    case TFTP_WRQ: /* TFTP WRQ (write request) */
    {
      /* Read the name of the file asked by the client to be received and written in the SD card */
      tftp_extract_filename(FileName, pkt_buf->payload);
  
      /* Could not open filesystem */
      if(f_mount(&filesystem, (TCHAR const*)"", 0) != FR_OK)
      {
        return;
      }
        
      /* If Could not open the selected directory */
      if (f_opendir(&dir_2, "/") != FR_OK)
      {
        return;
      }
        
      /* Start the TFTP write mode*/
      tftp_process_write(upcb, addr, port, FileName);
      break;
    }
    default: /* TFTP unknown request op */
      /* send generic access violation message */
      tftp_send_error_message(upcb, addr, port, TFTP_ERR_ACCESS_VIOLATION);
      udp_remove(upcb);

      break;
  }
}

 

먼저 udp 컨트롤 블락을 만들고 udp_bind(upcb, IP_ADDR_ANY, 0); 하고 있는데 이렇게 port부분에 0을 넣으면 next available port와 bind 된다.

 

RFC1350문서를 보면 초기연결 부분에 TID라는 개념이 나오는데 클라이언트가 서버한테 최초 RRQ/WRQ 요청을 보낼 땐 서버의 69번 포트로 보낸다. 이때 클라이언트는 OS로부터 자동 할당받은 랜덤 포트를 사용해 보낼텐데 이게 클라이언트의 TID가 된다. RRQ/WRQ요청을 받은 서버는 또 free한 port를 찾아서 bind 후 클라이언트의 TID에 응답을 보내고 이때의 next available port가 서버의 TID가 된다. 이후 transaction은 이 서버와 클라이언트의 TID를 통해 이루어진다.

 

그 다음 opcode 필드를 디코드해서 알아내고 RRQ/WRQ가 아니라면 서버의 69번 포트로 엉뚱한 메시지를 보냈다는 거니 에러메시지를 송신한 뒤 read/write transaction을 위해 생성했던 udp pcb를 remove한다.

 

만약 RRQ/WRQ가 맞다면 마운트를 하고 루트 디렉터리를 여는데 만약 파일시스템을 다른 코드에서도 쓰고있었다면 다른객체에 마운트되는 거니 주의가 필요하다. 이후 tftp_process_read/write() 함수를 호출하며 파라미터로 생성한 udp pcb와 addr, port, 추출한 FileName을 같이 넘긴다.

여기선 안하고 있지만 FileName 버퍼 크기를 초과하진 않는지 미리 검사하려면 pbuf_memfind()함수를 쓰면 된다.

 

pbuf_free()는 recv_callback_tftp() 콜백함수에서 하니까 여기선 하지 않는다.

 

 

 

 

 

 

 

 

int tftp_process_write(struct udp_pcb *upcb, const ip_addr_t *to, unsigned short to_port, char *FileName)

int tftp_process_write(struct udp_pcb *upcb, const ip_addr_t *to, unsigned short to_port, char *FileName)
{
  tftp_connection_args *args = NULL;

  /* Can not create file */
  if (f_open(&file_CR, (const TCHAR*)FileName, FA_CREATE_ALWAYS|FA_WRITE) != FR_OK)
  {
    tftp_send_error_message(upcb, to, to_port, TFTP_ERR_NOTDEFINED);

    tftp_cleanup_wr(upcb, args);

    return 0;
  }

  args = mem_malloc(sizeof *args);
  if (!args)
  {
    tftp_send_error_message(upcb, to, to_port, TFTP_ERR_NOTDEFINED);

    tftp_cleanup_wr(upcb, args);

    return 0;
  }

  args->op = TFTP_WRQ;
  args->to_ip.addr = to->addr;
  args->to_port = to_port;
  /* the block # used as a positive response to a WRQ is _always_ 0!!! (see RFC1350)  */
  args->block = 0;
  args->tot_bytes = 0;

  /* set callback for receives on this UDP PCB  */
  udp_recv(upcb, wrq_recv_callback, args);

  /* initiate the write transaction by sending the first ack */
  tftp_send_ack_packet(upcb, to, to_port, args->block);

  return 0;
}

 

그 다음 write processing 함수를 먼저 보자. 여기까지도 다 69번 포트로 바인드된 콜백함수가 호출되고 일어나는 일이다. 먼저 커넥션 관리를 위한 구조체 포인터를 만들고 읽기 모드로 파일을 오픈하고, lwIP의 mem_malloc으로 공간을 동적할당한다.

 

만약 이 과정에 실패가 있다면 에러메시지를 송신하고 커넥션 유지를 위해 만들었던 udp pcb와 동적할당한 args 변수를 처리하기 위한 함수를 호출한다. 

 

커넥션유지를 위한 args 변수에는 opcode, to_ip, to_port, block number 등을 기억시키고 tot_byte를 초기화한다.

 

마지막으로 next available port와 바인드된 upd pcb로 wrq_recv_callback() 함수를 등록하고 클라이언트가 WRQ 패킷 송신시 사용했던 포트로 block #0으로 ACK를 보낸다.

 

그리고 udp_recv()의 세번째 매개변수는 사용자 정의 데이터 포인터인데, 이는 사용자가 콜백함수에서 사용할 수 있는 데이터를 전달하여 수신한 데이터를 처리하고 다음 동작에 필요한 정보를 전달하기 위해 사용된다. 아까 동적할당한 args 포인터를 넘긴다.

 

 

 

 

void tftp_cleanup_rd(struct udp_pcb *upcb, tftp_connection_args *args)

void tftp_cleanup_wr(struct udp_pcb *upcb, tftp_connection_args *args)

void tftp_cleanup_rd(struct udp_pcb *upcb, tftp_connection_args *args)
{
  /* close the filesystem */
  f_close(&file_SD);
  f_mount(NULL, (TCHAR const*)"",0);
  /* Free the tftp_connection_args structure reserved for */
  mem_free(args);

  /* Disconnect the udp_pcb*/  
  udp_disconnect(upcb);

  /* close the connection */
  udp_remove(upcb);

  udp_recv(UDPpcb, recv_callback_tftp, NULL);
}

void tftp_cleanup_wr(struct udp_pcb *upcb, tftp_connection_args *args)
{
  /* close the filesystem */
  f_close(&file_CR);
  f_mount(NULL, (TCHAR const*)"",0);
  /* Free the tftp_connection_args structure reserved for */
  mem_free(args);

  /* Disconnect the udp_pcb*/
  udp_disconnect(upcb);

  /* close the connection */
  udp_remove(upcb);

  /* reset the callback function */
  udp_recv(UDPpcb, recv_callback_tftp, NULL);
}

 

이게 udp 컨트롤 블락 remove와 동적할당된 memory free, 파일 닫기와 unmount를 위한 cleanup 함수이다.

왜 read랑 write를 따로 만들었는지 모르겠다. FIL변수 중 파일이 오픈되어있는지 확인할 수 있는 fs 포인터가 있어서 그걸로 확인하고 열렸으면 닫기 하면 될 것 같다.

그리고 전역변수인 udp pcb로 69번 포트랑 바인딩된 콜백함수는 한번 등록되면 유지되서 굳이 다시 등록해줄 필요는 없는것 같다.

 

 

 

 

 

 

 

void wrq_recv_callback(void *arg, struct udp_pcb *upcb, struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)

void wrq_recv_callback(void *arg, struct udp_pcb *upcb, struct pbuf *pkt_buf, const ip_addr_t *addr, u16_t port)
{
  tftp_connection_args *args = (tftp_connection_args *)arg;
  int n = 0;

  /* we expect to receive only one pbuf (pbuf size should be 
     configured > max TFTP frame size */
  if (pkt_buf->len != pkt_buf->tot_len)
  {
    return;
  }

  /* Does this packet have any valid data to write? */
  if ((pkt_buf->len > TFTP_DATA_PKT_HDR_LEN) &&
      (tftp_extract_block(pkt_buf->payload) == (args->block + 1)))
  {
    /* write the received data to the file */
    f_write(&file_CR, (char*)pkt_buf->payload + TFTP_DATA_PKT_HDR_LEN, pkt_buf->len - TFTP_DATA_PKT_HDR_LEN, (UINT*)&n);

    if (n <= 0)
    {
      tftp_send_error_message(upcb, addr, port, TFTP_ERR_FILE_NOT_FOUND);
      /* close the connection */
      tftp_cleanup_wr(upcb, args); /* close the connection */
    }
    
    /* update our block number to match the block number just received */
    args->block++;
    
    /* update total bytes  */
    (args->tot_bytes) += (pkt_buf->len - TFTP_DATA_PKT_HDR_LEN);
  }
  else if (tftp_extract_block(pkt_buf->payload) == (args->block + 1))
  {
    /* update our block number to match the block number just received  */
    args->block++;
  }

  /* Send the appropriate ACK pkt (the block number sent in the ACK pkt echoes
   * the block number of the DATA pkt we just received - see RFC1350)
   * NOTE!: If the DATA pkt we received did not have the appropriate block
   * number, then the args->block (our block number) is never updated and
   * we simply send "duplicate ACK" which has the same block number as the
   * last ACK pkt we sent.  This lets the host know that we are still waiting
   * on block number args->block+1. 
   */
  tftp_send_ack_packet(upcb, addr, port, args->block);

  /* If the last write returned less than the maximum TFTP data pkt length,
   * then we've received the whole file and so we can quit (this is how TFTP
   * signals the end of a transfer!)
   */
  if (pkt_buf->len < TFTP_DATA_PKT_LEN_MAX)
  {
    tftp_cleanup_wr(upcb, args);
    pbuf_free(pkt_buf);
  }
  else
  {
    pbuf_free(pkt_buf);
    return;
  }
}

 

이 함수가 호출됐다는 것은 아까 서버가 보낸 block #0 ACK 패킷을 클라이언트가 받았고, 이때 서버가 사용한 포트로 클라이언트가 응답 메시지를 보냈다는 뜻이다.

 

파라미터로 args 포인터를 받는다. pkt_buf->len != pkt_buf->tot_len 은 수신한 패킷 버퍼가 하나가 아니라 체인으로 여러개 연결되어있다는 뜻인데 서버와 클라이언트가 응답을 주고 받는 TFTP 프로토콜을 생각할 때 서버가 기대한 상황은 아니다. 

단, 네트워크에서 전송된 데이터가 큰 경우엔, 하나의 pbuf로 모든 데이터를 수신하지 못하고 여러 개의 pbuf로 분할되어 수신될 수 있다. 이런 경우 보통은 첫번째 pbuf에서 데이터를 처리하고 다음 pbuf로 이동해서 계속 처리한다. 이런 일을 막기 위해 옵션에서 PBUF_POOL_SIZE가 512보다 충분히 큰지 확인할 필요가 있다. 참고로 PBUF_POOL_SIZE는 데이터 뿐만아니라 메타 데이터를 포함한 struct pbuf의 총 크기를 의미하기 때문에 512보다 더 커야한다.

MX툴에서 설정할 수 있는데 default가 592이고 다른값으로 설정하면 lwipopts.h 파일에 자동생성 코드가 생긴다.

 

다음으로 수신한 pbuf의 payload 길이가 4를 초과하고 추출한 block number가 기대한 값과 일치한다면 f_write() 데이터 부분을 쓰고 블락번호를 증가한다. f_write()호출후 written bytes가 0이하라면 파일쓰기 문제가 있다는 뜻이니 cleanup함수를 호출한다.

여기서 TFTP헤더 길이만큼 포인터를 증가시키고 pbuf->len에서 헤더길이를 빼고 있는데 lwip 라이브러리 함수인 pbuf_remove_header()를 쓰면 더 간단하다.

 

주의할 것은 이게 다 ISR에서 일어나는 일이나 루틴에서 파일시스템을 사용하고 있는 코드가 없는지 확인해야 한다. 아니면 어차피 한쪽이 뭔가를 보내야 다른쪽에서 응답이 오는 구조라서 ISR에서는 플래그만 켜고 루틴으로 빼서 처리하는 것도 좋을것 같다.

 

만약 블락넘버는 일치하는데 data가 없는 경우, 지난 데이터패킷에 512byte를 보내고 딱 맞게 끝났거나 아님 첨부터 빈파일을 보낸 경우가 되겠다. 블락번호만 증가시킨다.

 

다음 증가한 블락번호로 ACK패킷을 보내고 이번에 수신한 pbuf가 512+4byte보다 작은 경우, 이는 파일 전송의 종료를 의미하니 cleanup 함수를 호출하고 처리한 pbuf를 free한다.

 

 

 

 

 

 

 

 

int tftp_process_read(struct udp_pcb *upcb, const ip_addr_t *to, unsigned short to_port, char* FileName)

int tftp_process_read(struct udp_pcb *upcb, const ip_addr_t *to, unsigned short to_port, char* FileName)
{
  tftp_connection_args *args = NULL;

  /* If Could not open the file which will be transmitted  */
  if (f_open(&file_SD, (const TCHAR*)FileName, FA_READ) != FR_OK)
  {
    tftp_send_error_message(upcb, to, to_port, TFTP_ERR_FILE_NOT_FOUND);

    tftp_cleanup_rd(upcb, args);

    return 0;
  }
  
  args = mem_malloc(sizeof *args);
  /* If we aren't able to allocate memory for a "tftp_connection_args" */
  if (!args)
  {
    /* unable to allocate memory for tftp args  */
    tftp_send_error_message(upcb, to, to_port, TFTP_ERR_NOTDEFINED);

    /* no need to use tftp_cleanup_rd because no "tftp_connection_args" struct has been malloc'd   */
    tftp_cleanup_rd(upcb, args);

    return 0;
  }

  /* initialize connection structure  */
  args->op = TFTP_RRQ;
  args->to_ip.addr = to->addr;
  args->to_port = to_port;
  args->block = 1; /* block number starts at 1 (not 0) according to RFC1350  */
  args->tot_bytes = 0;


  /* set callback for receives on this UDP PCB  */
  udp_recv(upcb, rrq_recv_callback, args);

  /* initiate the transaction by sending the first block of data,
    further blocks will be sent when ACKs are received */

  tftp_send_next_block(upcb, args, to, to_port);

  return 1;
}

 

read processing 함수, 69번 포트로 바인드된 콜백함수가 호출되고 RRQ 패킷인 경우 처리하는 과정이다.

 

비슷하다. 다른것은 block number를 1로 초기화한다. 왜냐하면 서버가 RRQ패킷을 받으면 응답으로 바로 #1 DATA 패킷을 보내기 때문이다. read transaction을 위한 콜백함수를 호출하고 동적할당한 args 포인터를 매개변수로 넘긴다. 그리고 DATA 패킷 전송을 위한 함수를 호출한다.

 

 

 

 

 

 

 

void tftp_send_next_block(struct udp_pcb *upcb, tftp_connection_args *args, const ip_addr_t *to_ip, u16_t to_port)

void tftp_send_next_block(struct udp_pcb *upcb, tftp_connection_args *args,
                          const ip_addr_t *to_ip, u16_t to_port)
{
  /* Function to read 512 bytes from the file to send (file_SD), put them
   * in "args->data" and return the number of bytes read */
   f_read(&file_SD, (uint8_t*)args->data, TFTP_DATA_LEN_MAX, (UINT*)(&args->data_len));

  /*   NOTE: We need to send data packet even if args->data_len = 0*/
 
  /* sEndTransferthe data */
  tftp_send_data_packet(upcb, to_ip, to_port, args->block, args->data, args->data_len);

}

 

f_read()로 512byte만큼 읽어서 잠시 args->data에 넣어두고, 실제 읽은 바이트를 args->data_len에 저장한 뒤 DATA 패킷 전송을 위한 다음 함수를 호출한다.

이것 역시 ISR에서 파일시스템을 조작하는 일이라 주의가 필요하다.

 

 

 

 

int tftp_send_data_packet(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, unsigned short block, char *buf, int buflen)

int tftp_send_data_packet(struct udp_pcb *upcb, const ip_addr_t *to, int to_port, unsigned short block,
                          char *buf, int buflen)
{
  char packet[TFTP_DATA_PKT_LEN_MAX]; /* (512+4) bytes */

  /* Set the opcode 3 in the 2 first bytes */
  tftp_set_opcode(packet, TFTP_DATA);
  /* Set the block numero in the 2 second bytes */
  tftp_set_block(packet, block);
  /* Set the data message in the n last bytes */
  tftp_set_data_message(packet, buf, buflen);
  /* Send DATA packet */
  return tftp_send_message(upcb, to, to_port, packet, buflen + 4);
}

 

DATA 패킷을 담기 위한 512+4byte 버퍼를 만든다. opcode와 block number 설정하고 args->data에 담아뒀던 데이터를 이 버퍼로 복사한 뒤 TFTP 메시지 전송을 위한 마지막 함수를 호출한다.

 

 

 

 

 

 

 

err_t tftp_send_message(struct udp_pcb *upcb, const ip_addr_t *to_ip, unsigned short to_port, char *buf, unsigned short buflen)

err_t tftp_send_message(struct udp_pcb *upcb, const ip_addr_t *to_ip, unsigned short to_port, char *buf, unsigned short buflen)
{
  err_t err;
  struct pbuf *pkt_buf; /* Chain of pbuf's to be sent */

  /* PBUF_TRANSPORT - specifies the transport layer */
  pkt_buf = pbuf_alloc(PBUF_TRANSPORT, buflen, PBUF_POOL);

  if (!pkt_buf)      /*if the packet pbuf == NULL exit and end transmission */
    return ERR_MEM;

  /* Copy the original data buffer over to the packet buffer's payload */
  memcpy(pkt_buf->payload, buf, buflen);

  /* Sending packet by UDP protocol */
  err = udp_sendto(upcb, pkt_buf, to_ip, to_port);

  /* free the buffer pbuf */
  pbuf_free(pkt_buf);

  return err;
}

 

DATA 패킷이든 ERR 패킷이든 ACK 패킷이든 전부 최종적으로 이 함수에 있는 udp_sendto()를 통해 전송된다.

먼저 struct pbuf를 만들고 pbuf_alloc()을 통해 payload 길이만큼 동적할당한다. 여기선 PBUF_POOL을 쓰고 있는데 수신에는 PBUF_POOL쓰고 송신엔 PBUF_RAM을 쓰는게 더 적합하다. 문서상에도 나와있다.

 

아무튼 기존 payload를 담아뒀던 버퍼를 pbuf->payload에 복사하고 udp_sendto() 함수를 통해 pbuf를 송신한다. RRQ를 보낸 클라이언트의 ip, port로 송신하게 된다. 이후 송신을 위해 생성했던 pbuf를 free한다.

 

 

 

 

 

 

void rrq_recv_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)

void rrq_recv_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p,
                       const ip_addr_t *addr, u16_t port)
{
  /* Get our connection state  */
  tftp_connection_args *args = (tftp_connection_args *)arg;

  if (tftp_is_correct_ack(p->payload, args->block))
  {
    /* increment block # */
    args->block++;
  }
  else
  {
    /* we did not receive the expected ACK, so
       do not update block #. This causes the current block to be resent. */
  }

  /* if the last read returned less than the requested number of bytes
   * (i.e. TFTP_DATA_LEN_MAX), then we've sent the whole file and we can quit
   */
  if (args->data_len < TFTP_DATA_LEN_MAX)
  {
    /* Clean the connection*/
    tftp_cleanup_rd(upcb, args);

    pbuf_free(p);
  }

  /* if the whole file has not yet been sent then continue  */
  tftp_send_next_block(upcb, args, addr, port);

  pbuf_free(p);
}

 

다음 RRQ 콜백함수이다. RRQ를 받은 서버가 #1 DATA 패킷을 보내고 이를 수신한 클라이언트가 응답으로 서버 TID로 ACK를 보내면 이 함수가 호출되게 된다. 올바른 ACK인지 확인하고 블락번호를 증가한다. 

만약 지난 번에 f_read()로 실제 파일을 읽은 byte가 512보다 작다면 끝에 도달한 뒤 클라이언트로 부터 last ACK를 받았다는 뜻이므로 cleanup 함수를 호출한다.

그리고 next data block을 읽는 것은 올바른 ACK일 때만 수행하는게 맞을 것 같다.

 

 

 

 

 

 

 

열심히 분석하긴 했는데 참고용으로만 쓰고 첨부터 다시 짰다. 파일시스템을 인터럽트안에서 조작하는건 아닌거 같아서