diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 296 | ||||
| -rw-r--r-- | src/tftp.rs | 3 |
2 files changed, 4 insertions, 295 deletions
diff --git a/src/main.rs b/src/main.rs index 3d86a8e..07e4eec 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -347,223 +347,18 @@ fn write_boot_ack(xid: u32, chaddr: [u8; 16], client_uuid: Option<Vec<u8>>) -> R | |||
| 347 | Ok(writer) | 347 | Ok(writer) |
| 348 | } | 348 | } |
| 349 | 349 | ||
| 350 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 351 | struct InvalidTftpOp(u16); | ||
| 352 | |||
| 353 | impl std::fmt::Display for InvalidTftpOp { | ||
| 354 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 355 | write!(f, "invalid tftp opcode '{}'", self.0) | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | impl std::error::Error for InvalidTftpOp {} | ||
| 360 | |||
| 361 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 362 | enum TftpOp { | ||
| 363 | ReadRequest, | ||
| 364 | WriteRequest, | ||
| 365 | Data, | ||
| 366 | Ack, | ||
| 367 | Error, | ||
| 368 | Oack, | ||
| 369 | } | ||
| 370 | |||
| 371 | impl Into<u16> for TftpOp { | ||
| 372 | fn into(self) -> u16 { | ||
| 373 | match self { | ||
| 374 | TftpOp::ReadRequest => 1, | ||
| 375 | TftpOp::WriteRequest => 2, | ||
| 376 | TftpOp::Data => 3, | ||
| 377 | TftpOp::Ack => 4, | ||
| 378 | TftpOp::Error => 5, | ||
| 379 | TftpOp::Oack => 6, | ||
| 380 | } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | impl TryFrom<u16> for TftpOp { | ||
| 385 | type Error = InvalidTftpOp; | ||
| 386 | |||
| 387 | fn try_from(value: u16) -> std::result::Result<Self, InvalidTftpOp> { | ||
| 388 | match value { | ||
| 389 | 1 => Ok(Self::ReadRequest), | ||
| 390 | 2 => Ok(Self::WriteRequest), | ||
| 391 | 3 => Ok(Self::Data), | ||
| 392 | 4 => Ok(Self::Ack), | ||
| 393 | 5 => Ok(Self::Error), | ||
| 394 | 6 => Ok(Self::Oack), | ||
| 395 | unknown => Err(InvalidTftpOp(unknown)), | ||
| 396 | } | ||
| 397 | } | ||
| 398 | } | ||
| 399 | |||
| 400 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
| 401 | enum TftpMode { | ||
| 402 | NetAscii, | ||
| 403 | Octet, | ||
| 404 | Mail, | ||
| 405 | } | ||
| 406 | |||
| 407 | #[derive(Debug)] | ||
| 408 | struct TftpRequestPacket { | ||
| 409 | filename: String, | ||
| 410 | mode: String, | ||
| 411 | tsize: Option<u64>, | ||
| 412 | blksize: Option<u64>, | ||
| 413 | } | ||
| 414 | |||
| 415 | #[derive(Debug)] | ||
| 416 | struct TftpDataPacket { | ||
| 417 | block: u16, | ||
| 418 | data: Vec<u8>, | ||
| 419 | } | ||
| 420 | |||
| 421 | #[derive(Debug)] | ||
| 422 | struct TftpAckPacket { | ||
| 423 | block: u16, | ||
| 424 | } | ||
| 425 | |||
| 426 | #[derive(Debug)] | ||
| 427 | struct TftpErrorPacket { | ||
| 428 | code: u16, | ||
| 429 | message: String, | ||
| 430 | } | ||
| 431 | |||
| 432 | fn tftp_request_packet_parse(cursor: &mut Cursor<&[u8]>) -> Result<TftpRequestPacket> { | ||
| 433 | let filename = read_null_terminated_string(cursor)?; | ||
| 434 | let mode = read_null_terminated_string(cursor)?; | ||
| 435 | let mut tsize = None; | ||
| 436 | let mut blksize = None; | ||
| 437 | while let Ok(opt_name) = read_null_terminated_string(cursor) { | ||
| 438 | if opt_name.is_empty() { | ||
| 439 | break; | ||
| 440 | } | ||
| 441 | let opt_data = read_null_terminated_string(cursor)?; | ||
| 442 | match opt_name.as_str() { | ||
| 443 | "tsize" => tsize = Some(opt_data.parse::<u64>().unwrap()), | ||
| 444 | "blksize" => blksize = Some(opt_data.parse::<u64>().unwrap()), | ||
| 445 | _ => eprintln!("unknown tftp request option '{opt_name}'"), | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | Ok(TftpRequestPacket { | ||
| 450 | filename, | ||
| 451 | mode, | ||
| 452 | tsize, | ||
| 453 | blksize, | ||
| 454 | }) | ||
| 455 | } | ||
| 456 | |||
| 457 | fn tftp_data_packet_write(writer: &mut Vec<u8>, block: u16, data: Vec<u8>) -> Result<()> { | ||
| 458 | write_u16(writer, TftpOp::Data.into())?; | ||
| 459 | write_u16(writer, block)?; | ||
| 460 | write_buf(writer, &data)?; | ||
| 461 | Ok(()) | ||
| 462 | } | ||
| 463 | |||
| 464 | fn tftp_oack_packet_write( | ||
| 465 | writer: &mut Vec<u8>, | ||
| 466 | tsize: Option<u64>, | ||
| 467 | blksize: Option<u64>, | ||
| 468 | ) -> Result<()> { | ||
| 469 | write_u16(writer, TftpOp::Oack.into())?; | ||
| 470 | |||
| 471 | // Only include options that were requested by the client | ||
| 472 | if let Some(blksize_val) = blksize { | ||
| 473 | write_buf(writer, b"blksize")?; | ||
| 474 | write_u8(writer, 0)?; // null terminator | ||
| 475 | let blksize_str = blksize_val.to_string(); | ||
| 476 | write_buf(writer, blksize_str.as_bytes())?; | ||
| 477 | write_u8(writer, 0)?; // null terminator | ||
| 478 | } | ||
| 479 | |||
| 480 | if let Some(tsize_val) = tsize { | ||
| 481 | write_buf(writer, b"tsize")?; | ||
| 482 | write_u8(writer, 0)?; // null terminator | ||
| 483 | let tsize_str = tsize_val.to_string(); | ||
| 484 | write_buf(writer, tsize_str.as_bytes())?; | ||
| 485 | write_u8(writer, 0)?; // null terminator | ||
| 486 | } | ||
| 487 | |||
| 488 | Ok(()) | ||
| 489 | } | ||
| 490 | |||
| 491 | #[derive(Debug)] | ||
| 492 | enum TftpPacket { | ||
| 493 | Request(TftpRequestPacket), | ||
| 494 | Data(TftpDataPacket), | ||
| 495 | Ack(TftpAckPacket), | ||
| 496 | Error(TftpErrorPacket), | ||
| 497 | } | ||
| 498 | |||
| 499 | fn tftp_packet_parse(cursor: &mut Cursor<&[u8]>) -> Result<TftpPacket> { | ||
| 500 | let op = TftpOp::try_from(read_u16(cursor)?).unwrap(); | ||
| 501 | match op { | ||
| 502 | TftpOp::ReadRequest | TftpOp::WriteRequest => { | ||
| 503 | let filename = read_null_terminated_string(cursor)?; | ||
| 504 | let mode = read_null_terminated_string(cursor)?; | ||
| 505 | let mut tsize = None; | ||
| 506 | let mut blksize = None; | ||
| 507 | |||
| 508 | while let Ok(opt_name) = read_null_terminated_string(cursor) { | ||
| 509 | if opt_name.is_empty() { | ||
| 510 | break; | ||
| 511 | } | ||
| 512 | let opt_data = read_null_terminated_string(cursor)?; | ||
| 513 | match opt_name.as_str() { | ||
| 514 | "tsize" => tsize = Some(opt_data.parse::<u64>().unwrap()), | ||
| 515 | "blksize" => blksize = Some(opt_data.parse::<u64>().unwrap()), | ||
| 516 | _ => eprintln!("unknown tftp request option '{opt_name}'"), | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | Ok(TftpPacket::Request(TftpRequestPacket { | ||
| 521 | filename, | ||
| 522 | mode, | ||
| 523 | tsize, | ||
| 524 | blksize, | ||
| 525 | })) | ||
| 526 | } | ||
| 527 | TftpOp::Data => { | ||
| 528 | let block = read_u16(cursor)?; | ||
| 529 | let mut data = Vec::new(); | ||
| 530 | cursor.read_to_end(&mut data)?; | ||
| 531 | Ok(TftpPacket::Data(TftpDataPacket { block, data })) | ||
| 532 | } | ||
| 533 | TftpOp::Ack => { | ||
| 534 | let block = read_u16(cursor)?; | ||
| 535 | Ok(TftpPacket::Ack(TftpAckPacket { block })) | ||
| 536 | } | ||
| 537 | TftpOp::Error => { | ||
| 538 | let code = read_u16(cursor)?; | ||
| 539 | let message = read_null_terminated_string(cursor)?; | ||
| 540 | Ok(TftpPacket::Error(TftpErrorPacket { code, message })) | ||
| 541 | } | ||
| 542 | TftpOp::Oack => { | ||
| 543 | // OACK parsing not implemented for now | ||
| 544 | Err(std::io::Error::new( | ||
| 545 | std::io::ErrorKind::Unsupported, | ||
| 546 | "OACK parsing not implemented", | ||
| 547 | )) | ||
| 548 | } | ||
| 549 | } | ||
| 550 | } | ||
| 551 | |||
| 552 | fn main() { | 350 | fn main() { |
| 553 | let socket67 = UdpSocket::bind("0.0.0.0:67").unwrap(); | 351 | let socket67 = UdpSocket::bind("0.0.0.0:67").unwrap(); |
| 554 | socket67.set_broadcast(true).unwrap(); | 352 | socket67.set_broadcast(true).unwrap(); |
| 555 | socket67.set_nonblocking(true).unwrap(); | 353 | socket67.set_nonblocking(true).unwrap(); |
| 556 | 354 | ||
| 557 | let socket69 = UdpSocket::bind("0.0.0.0:69").unwrap(); | ||
| 558 | socket69.set_broadcast(false).unwrap(); | ||
| 559 | socket69.set_nonblocking(true).unwrap(); | ||
| 560 | |||
| 561 | let socket4011 = UdpSocket::bind("0.0.0.0:4011").unwrap(); | 355 | let socket4011 = UdpSocket::bind("0.0.0.0:4011").unwrap(); |
| 562 | socket4011.set_broadcast(true).unwrap(); | 356 | socket4011.set_broadcast(true).unwrap(); |
| 563 | socket4011.set_nonblocking(true).unwrap(); | 357 | socket4011.set_nonblocking(true).unwrap(); |
| 564 | 358 | ||
| 565 | let mut last_blksize = 512u64; | 359 | std::thread::spawn(|| { |
| 566 | let mut current_file = String::new(); | 360 | tftp::serve("tftp").unwrap(); |
| 361 | }); | ||
| 567 | 362 | ||
| 568 | loop { | 363 | loop { |
| 569 | let mut buf = [0u8; 1500]; | 364 | let mut buf = [0u8; 1500]; |
| @@ -575,91 +370,6 @@ fn main() { | |||
| 575 | } else if let Ok((n, addr)) = socket4011.recv_from(&mut buf) { | 370 | } else if let Ok((n, addr)) = socket4011.recv_from(&mut buf) { |
| 576 | println!("Received {} bytes from {} on port 4011", n, addr); | 371 | println!("Received {} bytes from {} on port 4011", n, addr); |
| 577 | handle_packet_4011(&buf[..n], &socket4011, addr); | 372 | handle_packet_4011(&buf[..n], &socket4011, addr); |
| 578 | } else if let Ok((n, addr)) = socket69.recv_from(&mut buf) { | ||
| 579 | let mut cursor = Cursor::new(&buf[..n]); | ||
| 580 | |||
| 581 | let packet = tftp_packet_parse(&mut cursor).unwrap(); | ||
| 582 | println!("Received TFTP request from {addr}: {packet:#?}"); | ||
| 583 | |||
| 584 | let mut response = Vec::default(); | ||
| 585 | match packet { | ||
| 586 | TftpPacket::Request(tftp_request_packet) => { | ||
| 587 | println!( | ||
| 588 | "Request options: tsize={:?}, blksize={:?}", | ||
| 589 | tftp_request_packet.tsize, tftp_request_packet.blksize | ||
| 590 | ); | ||
| 591 | |||
| 592 | let filepath = format!("tftp/{}", tftp_request_packet.filename); | ||
| 593 | current_file = filepath.clone(); | ||
| 594 | let meta = std::fs::metadata(&filepath).unwrap(); | ||
| 595 | let actual_file_size = meta.len(); | ||
| 596 | |||
| 597 | // Only send OACK if client sent options | ||
| 598 | if tftp_request_packet.tsize.is_some() || tftp_request_packet.blksize.is_some() | ||
| 599 | { | ||
| 600 | if let Some(blksize) = tftp_request_packet.blksize { | ||
| 601 | last_blksize = blksize; | ||
| 602 | } | ||
| 603 | |||
| 604 | let tsize_response = if tftp_request_packet.tsize.is_some() { | ||
| 605 | Some(actual_file_size) | ||
| 606 | } else { | ||
| 607 | None | ||
| 608 | }; | ||
| 609 | |||
| 610 | tftp_oack_packet_write( | ||
| 611 | &mut response, | ||
| 612 | tsize_response, | ||
| 613 | tftp_request_packet.blksize, | ||
| 614 | ) | ||
| 615 | .unwrap(); | ||
| 616 | } else { | ||
| 617 | // No options, send first data block directly | ||
| 618 | let contents = std::fs::read(&filepath).unwrap(); | ||
| 619 | let block_size = 512; | ||
| 620 | let first_block = if contents.len() > block_size { | ||
| 621 | contents[..block_size].to_vec() | ||
| 622 | } else { | ||
| 623 | contents | ||
| 624 | }; | ||
| 625 | |||
| 626 | tftp_data_packet_write(&mut response, 1, first_block).unwrap(); | ||
| 627 | } | ||
| 628 | } | ||
| 629 | TftpPacket::Data(tftp_data_packet) => { | ||
| 630 | println!("Received DATA packet: block {}", tftp_data_packet.block); | ||
| 631 | } | ||
| 632 | TftpPacket::Ack(tftp_ack_packet) => { | ||
| 633 | println!("Received ACK packet: block {}", tftp_ack_packet.block); | ||
| 634 | |||
| 635 | let contents = std::fs::read(¤t_file).unwrap(); | ||
| 636 | let next_block = tftp_ack_packet.block + 1; | ||
| 637 | let start_offset = (next_block - 1) as u64 * last_blksize; | ||
| 638 | let end_offset = next_block as u64 * last_blksize; | ||
| 639 | let prev_start_offset = (next_block.saturating_sub(2)) as u64 * last_blksize; | ||
| 640 | let prev_remain = contents.len() - prev_start_offset as usize; | ||
| 641 | if prev_remain as u64 >= last_blksize || tftp_ack_packet.block == 0 { | ||
| 642 | let end = std::cmp::min(end_offset as usize, contents.len()); | ||
| 643 | let block_data = contents[start_offset as usize..end].to_vec(); | ||
| 644 | println!("sending tftp data packet with {} bytes", block_data.len()); | ||
| 645 | tftp_data_packet_write(&mut response, next_block, block_data).unwrap(); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | TftpPacket::Error(tftp_error_packet) => { | ||
| 649 | println!( | ||
| 650 | "Received ERROR packet: code {}, message: {}", | ||
| 651 | tftp_error_packet.code, tftp_error_packet.message | ||
| 652 | ); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | // | ||
| 656 | // let filepath = format!("tftp/{}", request.filename); | ||
| 657 | // let meta = std::fs::metadata(&filepath).unwrap(); | ||
| 658 | // let contents = std::fs::read(&filepath).unwrap(); | ||
| 659 | // let mut response = Vec::default(); | ||
| 660 | if !response.is_empty() { | ||
| 661 | socket69.send_to(&response, addr).unwrap(); | ||
| 662 | } | ||
| 663 | } else { | 373 | } else { |
| 664 | std::thread::sleep(std::time::Duration::from_millis(10)); | 374 | std::thread::sleep(std::time::Duration::from_millis(10)); |
| 665 | } | 375 | } |
diff --git a/src/tftp.rs b/src/tftp.rs index 681d036..d986a44 100644 --- a/src/tftp.rs +++ b/src/tftp.rs | |||
| @@ -305,7 +305,7 @@ pub fn serve(dir: impl AsRef<Path>) -> Result<()> { | |||
| 305 | }; | 305 | }; |
| 306 | 306 | ||
| 307 | Some(TftpPacket::OAck(TftpOAckPacket { | 307 | Some(TftpPacket::OAck(TftpOAckPacket { |
| 308 | tsize: req.tsize, | 308 | tsize: tsize_response, |
| 309 | blksize: req.blksize, | 309 | blksize: req.blksize, |
| 310 | })) | 310 | })) |
| 311 | } else { | 311 | } else { |
| @@ -361,7 +361,6 @@ pub fn serve(dir: impl AsRef<Path>) -> Result<()> { | |||
| 361 | println!("Sending to {addr}: {response:#?}"); | 361 | println!("Sending to {addr}: {response:#?}"); |
| 362 | response.write(&mut writer).unwrap(); | 362 | response.write(&mut writer).unwrap(); |
| 363 | let (response, _) = writer.split(); | 363 | let (response, _) = writer.split(); |
| 364 | println!("Sending {} bytes to {addr}: {response:#?}", response.len()); | ||
| 365 | socket.send_to(&response, addr).unwrap(); | 364 | socket.send_to(&response, addr).unwrap(); |
| 366 | } | 365 | } |
| 367 | } | 366 | } |
