pxe.c revision 231035
1/*- 2 * Copyright (c) 2000 Alfred Perlstein <alfred@freebsd.org> 3 * Copyright (c) 2000 Paul Saab <ps@freebsd.org> 4 * Copyright (c) 2000 John Baldwin <jhb@freebsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29#include <sys/cdefs.h> 30__FBSDID("$FreeBSD: stable/9/sys/boot/i386/libi386/pxe.c 231035 2012-02-05 19:30:18Z sbruno $"); 31 32#include <stand.h> 33#include <string.h> 34#include <stdarg.h> 35 36#include <netinet/in_systm.h> 37#include <netinet/in.h> 38#include <netinet/udp.h> 39 40#include <net.h> 41#include <netif.h> 42#include <nfsv2.h> 43#include <iodesc.h> 44 45#include <bootp.h> 46#include <bootstrap.h> 47#include "btxv86.h" 48#include "pxe.h" 49 50/* 51 * Allocate the PXE buffers statically instead of sticking grimy fingers into 52 * BTX's private data area. The scratch buffer is used to send information to 53 * the PXE BIOS, and the data buffer is used to receive data from the PXE BIOS. 54 */ 55#define PXE_BUFFER_SIZE 0x2000 56#define PXE_TFTP_BUFFER_SIZE 512 57static char scratch_buffer[PXE_BUFFER_SIZE]; 58static char data_buffer[PXE_BUFFER_SIZE]; 59 60static pxenv_t *pxenv_p = NULL; /* PXENV+ */ 61static pxe_t *pxe_p = NULL; /* !PXE */ 62static BOOTPLAYER bootplayer; /* PXE Cached information. */ 63 64static int pxe_debug = 0; 65static int pxe_sock = -1; 66static int pxe_opens = 0; 67 68void pxe_enable(void *pxeinfo); 69static void (*pxe_call)(int func); 70static void pxenv_call(int func); 71static void bangpxe_call(int func); 72 73static int pxe_init(void); 74static int pxe_strategy(void *devdata, int flag, daddr_t dblk, 75 size_t size, char *buf, size_t *rsize); 76static int pxe_open(struct open_file *f, ...); 77static int pxe_close(struct open_file *f); 78static void pxe_print(int verbose); 79static void pxe_cleanup(void); 80static void pxe_setnfshandle(char *rootpath); 81 82static void pxe_perror(int error); 83static int pxe_netif_match(struct netif *nif, void *machdep_hint); 84static int pxe_netif_probe(struct netif *nif, void *machdep_hint); 85static void pxe_netif_init(struct iodesc *desc, void *machdep_hint); 86static int pxe_netif_get(struct iodesc *desc, void *pkt, size_t len, 87 time_t timeout); 88static int pxe_netif_put(struct iodesc *desc, void *pkt, size_t len); 89static void pxe_netif_end(struct netif *nif); 90 91extern struct netif_stats pxe_st[]; 92extern u_int16_t __bangpxeseg; 93extern u_int16_t __bangpxeoff; 94extern void __bangpxeentry(void); 95extern u_int16_t __pxenvseg; 96extern u_int16_t __pxenvoff; 97extern void __pxenventry(void); 98 99struct netif_dif pxe_ifs[] = { 100/* dif_unit dif_nsel dif_stats dif_private */ 101 {0, 1, &pxe_st[0], 0} 102}; 103 104struct netif_stats pxe_st[NENTS(pxe_ifs)]; 105 106struct netif_driver pxenetif = { 107 "pxenet", 108 pxe_netif_match, 109 pxe_netif_probe, 110 pxe_netif_init, 111 pxe_netif_get, 112 pxe_netif_put, 113 pxe_netif_end, 114 pxe_ifs, 115 NENTS(pxe_ifs) 116}; 117 118struct netif_driver *netif_drivers[] = { 119 &pxenetif, 120 NULL 121}; 122 123struct devsw pxedisk = { 124 "pxe", 125 DEVT_NET, 126 pxe_init, 127 pxe_strategy, 128 pxe_open, 129 pxe_close, 130 noioctl, 131 pxe_print, 132 pxe_cleanup 133}; 134 135/* 136 * This function is called by the loader to enable PXE support if we 137 * are booted by PXE. The passed in pointer is a pointer to the 138 * PXENV+ structure. 139 */ 140void 141pxe_enable(void *pxeinfo) 142{ 143 pxenv_p = (pxenv_t *)pxeinfo; 144 pxe_p = (pxe_t *)PTOV(pxenv_p->PXEPtr.segment * 16 + 145 pxenv_p->PXEPtr.offset); 146 pxe_call = NULL; 147} 148 149/* 150 * return true if pxe structures are found/initialized, 151 * also figures out our IP information via the pxe cached info struct 152 */ 153static int 154pxe_init(void) 155{ 156 t_PXENV_GET_CACHED_INFO *gci_p; 157 int counter; 158 uint8_t checksum; 159 uint8_t *checkptr; 160 161 if(pxenv_p == NULL) 162 return (0); 163 164 /* look for "PXENV+" */ 165 if (bcmp((void *)pxenv_p->Signature, S_SIZE("PXENV+"))) { 166 pxenv_p = NULL; 167 return (0); 168 } 169 170 /* make sure the size is something we can handle */ 171 if (pxenv_p->Length > sizeof(*pxenv_p)) { 172 printf("PXENV+ structure too large, ignoring\n"); 173 pxenv_p = NULL; 174 return (0); 175 } 176 177 /* 178 * do byte checksum: 179 * add up each byte in the structure, the total should be 0 180 */ 181 checksum = 0; 182 checkptr = (uint8_t *) pxenv_p; 183 for (counter = 0; counter < pxenv_p->Length; counter++) 184 checksum += *checkptr++; 185 if (checksum != 0) { 186 printf("PXENV+ structure failed checksum, ignoring\n"); 187 pxenv_p = NULL; 188 return (0); 189 } 190 191 192 /* 193 * PXENV+ passed, so use that if !PXE is not available or 194 * the checksum fails. 195 */ 196 pxe_call = pxenv_call; 197 if (pxenv_p->Version >= 0x0200) { 198 for (;;) { 199 if (bcmp((void *)pxe_p->Signature, S_SIZE("!PXE"))) { 200 pxe_p = NULL; 201 break; 202 } 203 checksum = 0; 204 checkptr = (uint8_t *)pxe_p; 205 for (counter = 0; counter < pxe_p->StructLength; 206 counter++) 207 checksum += *checkptr++; 208 if (checksum != 0) { 209 pxe_p = NULL; 210 break; 211 } 212 pxe_call = bangpxe_call; 213 break; 214 } 215 } 216 217 printf("\nPXE version %d.%d, real mode entry point ", 218 (uint8_t) (pxenv_p->Version >> 8), 219 (uint8_t) (pxenv_p->Version & 0xFF)); 220 if (pxe_call == bangpxe_call) 221 printf("@%04x:%04x\n", 222 pxe_p->EntryPointSP.segment, 223 pxe_p->EntryPointSP.offset); 224 else 225 printf("@%04x:%04x\n", 226 pxenv_p->RMEntry.segment, pxenv_p->RMEntry.offset); 227 228 gci_p = (t_PXENV_GET_CACHED_INFO *) scratch_buffer; 229 bzero(gci_p, sizeof(*gci_p)); 230 gci_p->PacketType = PXENV_PACKET_TYPE_BINL_REPLY; 231 pxe_call(PXENV_GET_CACHED_INFO); 232 if (gci_p->Status != 0) { 233 pxe_perror(gci_p->Status); 234 pxe_p = NULL; 235 return (0); 236 } 237 bcopy(PTOV((gci_p->Buffer.segment << 4) + gci_p->Buffer.offset), 238 &bootplayer, gci_p->BufferSize); 239 return (1); 240} 241 242 243static int 244pxe_strategy(void *devdata, int flag, daddr_t dblk, size_t size, 245 char *buf, size_t *rsize) 246{ 247 return (EIO); 248} 249 250static int 251pxe_open(struct open_file *f, ...) 252{ 253 va_list args; 254 char *devname; /* Device part of file name (or NULL). */ 255 char temp[FNAME_SIZE]; 256 int error = 0; 257 int i; 258 259 va_start(args, f); 260 devname = va_arg(args, char*); 261 va_end(args); 262 263 /* On first open, do netif open, mount, etc. */ 264 if (pxe_opens == 0) { 265 /* Find network interface. */ 266 if (pxe_sock < 0) { 267 pxe_sock = netif_open(devname); 268 if (pxe_sock < 0) { 269 printf("pxe_open: netif_open() failed\n"); 270 return (ENXIO); 271 } 272 if (pxe_debug) 273 printf("pxe_open: netif_open() succeeded\n"); 274 } 275 if (rootip.s_addr == 0) { 276 /* 277 * Do a bootp/dhcp request to find out where our 278 * NFS/TFTP server is. Even if we dont get back 279 * the proper information, fall back to the server 280 * which brought us to life and a default rootpath. 281 */ 282 bootp(pxe_sock, BOOTP_PXE); 283 if (rootip.s_addr == 0) 284 rootip.s_addr = bootplayer.sip; 285 if (!rootpath[0]) 286 strcpy(rootpath, PXENFSROOTPATH); 287 288 for (i = 0; rootpath[i] != '\0' && i < FNAME_SIZE; i++) 289 if (rootpath[i] == ':') 290 break; 291 if (i && i != FNAME_SIZE && rootpath[i] == ':') { 292 rootpath[i++] = '\0'; 293 if (inet_addr(&rootpath[0]) != INADDR_NONE) 294 rootip.s_addr = inet_addr(&rootpath[0]); 295 bcopy(&rootpath[i], &temp[0], strlen(&rootpath[i])+1); 296 bcopy(&temp[0], &rootpath[0], strlen(&rootpath[i])+1); 297 } 298 printf("pxe_open: server addr: %s\n", inet_ntoa(rootip)); 299 printf("pxe_open: server path: %s\n", rootpath); 300 printf("pxe_open: gateway ip: %s\n", inet_ntoa(gateip)); 301 302 setenv("boot.netif.ip", inet_ntoa(myip), 1); 303 setenv("boot.netif.netmask", intoa(netmask), 1); 304 setenv("boot.netif.gateway", inet_ntoa(gateip), 1); 305 if (bootplayer.Hardware == ETHER_TYPE) { 306 sprintf(temp, "%6D", bootplayer.CAddr, ":"); 307 setenv("boot.netif.hwaddr", temp, 1); 308 } 309 setenv("boot.nfsroot.server", inet_ntoa(rootip), 1); 310 setenv("boot.nfsroot.path", rootpath, 1); 311 setenv("dhcp.host-name", hostname, 1); 312 } 313 } 314 pxe_opens++; 315 f->f_devdata = &pxe_sock; 316 return (error); 317} 318 319static int 320pxe_close(struct open_file *f) 321{ 322 323#ifdef PXE_DEBUG 324 if (pxe_debug) 325 printf("pxe_close: opens=%d\n", pxe_opens); 326#endif 327 328 /* On last close, do netif close, etc. */ 329 f->f_devdata = NULL; 330 /* Extra close call? */ 331 if (pxe_opens <= 0) 332 return (0); 333 pxe_opens--; 334 /* Not last close? */ 335 if (pxe_opens > 0) 336 return(0); 337 338#ifdef LOADER_NFS_SUPPORT 339 /* get an NFS filehandle for our root filesystem */ 340 pxe_setnfshandle(rootpath); 341#endif 342 343 if (pxe_sock >= 0) { 344 345#ifdef PXE_DEBUG 346 if (pxe_debug) 347 printf("pxe_close: calling netif_close()\n"); 348#endif 349 netif_close(pxe_sock); 350 pxe_sock = -1; 351 } 352 return (0); 353} 354 355static void 356pxe_print(int verbose) 357{ 358 if (pxe_call != NULL) { 359 if (*bootplayer.Sname == '\0') { 360 printf(" "IP_STR":%s\n", 361 IP_ARGS(htonl(bootplayer.sip)), 362 bootplayer.bootfile); 363 } else { 364 printf(" %s:%s\n", bootplayer.Sname, 365 bootplayer.bootfile); 366 } 367 } 368 369 return; 370} 371 372static void 373pxe_cleanup(void) 374{ 375#ifdef PXE_DEBUG 376 t_PXENV_UNLOAD_STACK *unload_stack_p = 377 (t_PXENV_UNLOAD_STACK *)scratch_buffer; 378 t_PXENV_UNDI_SHUTDOWN *undi_shutdown_p = 379 (t_PXENV_UNDI_SHUTDOWN *)scratch_buffer; 380#endif 381 382 if (pxe_call == NULL) 383 return; 384 385 pxe_call(PXENV_UNDI_SHUTDOWN); 386 387#ifdef PXE_DEBUG 388 if (pxe_debug && undi_shutdown_p->Status != 0) 389 printf("pxe_cleanup: UNDI_SHUTDOWN failed %x\n", 390 undi_shutdown_p->Status); 391#endif 392 393 pxe_call(PXENV_UNLOAD_STACK); 394 395#ifdef PXE_DEBUG 396 if (pxe_debug && unload_stack_p->Status != 0) 397 printf("pxe_cleanup: UNLOAD_STACK failed %x\n", 398 unload_stack_p->Status); 399#endif 400} 401 402void 403pxe_perror(int err) 404{ 405 return; 406} 407 408#ifdef LOADER_NFS_SUPPORT 409/* 410 * Reach inside the libstand NFS code and dig out an NFS handle 411 * for the root filesystem. 412 */ 413#ifdef OLD_NFSV2 414struct nfs_iodesc { 415 struct iodesc *iodesc; 416 off_t off; 417 u_char fh[NFS_FHSIZE]; 418 /* structure truncated here */ 419}; 420extern struct nfs_iodesc nfs_root_node; 421extern int rpc_port; 422 423static void 424pxe_rpcmountcall() 425{ 426 struct iodesc *d; 427 int error; 428 429 if (!(d = socktodesc(pxe_sock))) 430 return; 431 d->myport = htons(--rpc_port); 432 d->destip = rootip; 433 if ((error = nfs_getrootfh(d, rootpath, nfs_root_node.fh)) != 0) 434 printf("NFS MOUNT RPC error: %d\n", error); 435 nfs_root_node.iodesc = d; 436} 437 438static void 439pxe_setnfshandle(char *rootpath) 440{ 441 int i; 442 u_char *fh; 443 char buf[2 * NFS_FHSIZE + 3], *cp; 444 445 /* 446 * If NFS files were never opened, we need to do mount call 447 * ourselves. Use nfs_root_node.iodesc as flag indicating 448 * previous NFS usage. 449 */ 450 if (nfs_root_node.iodesc == NULL) 451 pxe_rpcmountcall(); 452 453 fh = &nfs_root_node.fh[0]; 454 buf[0] = 'X'; 455 cp = &buf[1]; 456 for (i = 0; i < NFS_FHSIZE; i++, cp += 2) 457 sprintf(cp, "%02x", fh[i]); 458 sprintf(cp, "X"); 459 setenv("boot.nfsroot.nfshandle", buf, 1); 460} 461#else /* !OLD_NFSV2 */ 462 463#define NFS_V3MAXFHSIZE 64 464 465struct nfs_iodesc { 466 struct iodesc *iodesc; 467 off_t off; 468 uint32_t fhsize; 469 u_char fh[NFS_V3MAXFHSIZE]; 470 /* structure truncated */ 471}; 472extern struct nfs_iodesc nfs_root_node; 473extern int rpc_port; 474 475static void 476pxe_rpcmountcall() 477{ 478 struct iodesc *d; 479 int error; 480 481 if (!(d = socktodesc(pxe_sock))) 482 return; 483 d->myport = htons(--rpc_port); 484 d->destip = rootip; 485 if ((error = nfs_getrootfh(d, rootpath, &nfs_root_node.fhsize, 486 nfs_root_node.fh)) != 0) { 487 printf("NFS MOUNT RPC error: %d\n", error); 488 nfs_root_node.fhsize = 0; 489 } 490 nfs_root_node.iodesc = d; 491} 492 493static void 494pxe_setnfshandle(char *rootpath) 495{ 496 int i; 497 u_char *fh; 498 char buf[2 * NFS_V3MAXFHSIZE + 3], *cp; 499 500 /* 501 * If NFS files were never opened, we need to do mount call 502 * ourselves. Use nfs_root_node.iodesc as flag indicating 503 * previous NFS usage. 504 */ 505 if (nfs_root_node.iodesc == NULL) 506 pxe_rpcmountcall(); 507 508 fh = &nfs_root_node.fh[0]; 509 buf[0] = 'X'; 510 cp = &buf[1]; 511 for (i = 0; i < nfs_root_node.fhsize; i++, cp += 2) 512 sprintf(cp, "%02x", fh[i]); 513 sprintf(cp, "X"); 514 setenv("boot.nfsroot.nfshandle", buf, 1); 515 sprintf(buf, "%d", nfs_root_node.fhsize); 516 setenv("boot.nfsroot.nfshandlelen", buf, 1); 517} 518#endif /* OLD_NFSV2 */ 519#endif /* LOADER_NFS_SUPPORT */ 520 521void 522pxenv_call(int func) 523{ 524#ifdef PXE_DEBUG 525 if (pxe_debug) 526 printf("pxenv_call %x\n", func); 527#endif 528 529 bzero(&v86, sizeof(v86)); 530 bzero(data_buffer, sizeof(data_buffer)); 531 532 __pxenvseg = pxenv_p->RMEntry.segment; 533 __pxenvoff = pxenv_p->RMEntry.offset; 534 535 v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; 536 v86.es = VTOPSEG(scratch_buffer); 537 v86.edi = VTOPOFF(scratch_buffer); 538 v86.addr = (VTOPSEG(__pxenventry) << 16) | VTOPOFF(__pxenventry); 539 v86.ebx = func; 540 v86int(); 541 v86.ctl = V86_FLAGS; 542} 543 544void 545bangpxe_call(int func) 546{ 547#ifdef PXE_DEBUG 548 if (pxe_debug) 549 printf("bangpxe_call %x\n", func); 550#endif 551 552 bzero(&v86, sizeof(v86)); 553 bzero(data_buffer, sizeof(data_buffer)); 554 555 __bangpxeseg = pxe_p->EntryPointSP.segment; 556 __bangpxeoff = pxe_p->EntryPointSP.offset; 557 558 v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; 559 v86.edx = VTOPSEG(scratch_buffer); 560 v86.eax = VTOPOFF(scratch_buffer); 561 v86.addr = (VTOPSEG(__bangpxeentry) << 16) | VTOPOFF(__bangpxeentry); 562 v86.ebx = func; 563 v86int(); 564 v86.ctl = V86_FLAGS; 565} 566 567 568time_t 569getsecs() 570{ 571 time_t n = 0; 572 time(&n); 573 return n; 574} 575 576static int 577pxe_netif_match(struct netif *nif, void *machdep_hint) 578{ 579 return 1; 580} 581 582 583static int 584pxe_netif_probe(struct netif *nif, void *machdep_hint) 585{ 586 t_PXENV_UDP_OPEN *udpopen_p = (t_PXENV_UDP_OPEN *)scratch_buffer; 587 588 if (pxe_call == NULL) 589 return -1; 590 591 bzero(udpopen_p, sizeof(*udpopen_p)); 592 udpopen_p->src_ip = bootplayer.yip; 593 pxe_call(PXENV_UDP_OPEN); 594 595 if (udpopen_p->status != 0) { 596 printf("pxe_netif_probe: failed %x\n", udpopen_p->status); 597 return -1; 598 } 599 return 0; 600} 601 602static void 603pxe_netif_end(struct netif *nif) 604{ 605 t_PXENV_UDP_CLOSE *udpclose_p = (t_PXENV_UDP_CLOSE *)scratch_buffer; 606 bzero(udpclose_p, sizeof(*udpclose_p)); 607 608 pxe_call(PXENV_UDP_CLOSE); 609 if (udpclose_p->status != 0) 610 printf("pxe_end failed %x\n", udpclose_p->status); 611} 612 613static void 614pxe_netif_init(struct iodesc *desc, void *machdep_hint) 615{ 616 int i; 617 for (i = 0; i < 6; ++i) 618 desc->myea[i] = bootplayer.CAddr[i]; 619 desc->xid = bootplayer.ident; 620} 621 622static int 623pxe_netif_get(struct iodesc *desc, void *pkt, size_t len, time_t timeout) 624{ 625 return len; 626} 627 628static int 629pxe_netif_put(struct iodesc *desc, void *pkt, size_t len) 630{ 631 return len; 632} 633 634ssize_t 635sendudp(struct iodesc *h, void *pkt, size_t len) 636{ 637 t_PXENV_UDP_WRITE *udpwrite_p = (t_PXENV_UDP_WRITE *)scratch_buffer; 638 bzero(udpwrite_p, sizeof(*udpwrite_p)); 639 640 udpwrite_p->ip = h->destip.s_addr; 641 udpwrite_p->dst_port = h->destport; 642 udpwrite_p->src_port = h->myport; 643 udpwrite_p->buffer_size = len; 644 udpwrite_p->buffer.segment = VTOPSEG(pkt); 645 udpwrite_p->buffer.offset = VTOPOFF(pkt); 646 647 if (netmask == 0 || SAMENET(myip, h->destip, netmask)) 648 udpwrite_p->gw = 0; 649 else 650 udpwrite_p->gw = gateip.s_addr; 651 652 pxe_call(PXENV_UDP_WRITE); 653 654#if 0 655 /* XXX - I dont know why we need this. */ 656 delay(1000); 657#endif 658 if (udpwrite_p->status != 0) { 659 /* XXX: This happens a lot. It shouldn't. */ 660 if (udpwrite_p->status != 1) 661 printf("sendudp failed %x\n", udpwrite_p->status); 662 return -1; 663 } 664 return len; 665} 666 667ssize_t 668readudp(struct iodesc *h, void *pkt, size_t len, time_t timeout) 669{ 670 t_PXENV_UDP_READ *udpread_p = (t_PXENV_UDP_READ *)scratch_buffer; 671 struct udphdr *uh = NULL; 672 673 uh = (struct udphdr *) pkt - 1; 674 bzero(udpread_p, sizeof(*udpread_p)); 675 676 udpread_p->dest_ip = h->myip.s_addr; 677 udpread_p->d_port = h->myport; 678 udpread_p->buffer_size = len; 679 udpread_p->buffer.segment = VTOPSEG(data_buffer); 680 udpread_p->buffer.offset = VTOPOFF(data_buffer); 681 682 pxe_call(PXENV_UDP_READ); 683 684#if 0 685 /* XXX - I dont know why we need this. */ 686 delay(1000); 687#endif 688 if (udpread_p->status != 0) { 689 /* XXX: This happens a lot. It shouldn't. */ 690 if (udpread_p->status != 1) 691 printf("readudp failed %x\n", udpread_p->status); 692 return -1; 693 } 694 bcopy(data_buffer, pkt, udpread_p->buffer_size); 695 uh->uh_sport = udpread_p->s_port; 696 return udpread_p->buffer_size; 697} 698