/* Ethernet Transport Service for RPC ts_eth.c ** ================================== ** ** This module provides a transport service for the RPC system developed ** by CERN/DD Online group in 1986-90. ** ** This particular module uses a raw ethernet packet interface, by implementing ** its own protocol. See the RPC User Manual for details of address syntax. ** ** See also the RPC internals manual, which describes how modules ** similar to this one work, also giving flow charts and message formats. ** ** Known defficiencies: ** ** o Explicit references to fields within the rpc message body, instead ** of always using the marshalling macros ** ** History: ** 30 Apr 90 Code extracted from general ts.c module. ** ** Compilation switches: ** ** AST The operating system supports asynchronous procedures. ** ** ETHERNET This RPC system runs over raw ethernet. Mandatory. */ /* Module parameters: ** ----------------- ** ** These may be undefined and redefined by syspec.h */ # define WILDCARD '*' /* Wildcard used in addressing */ # define INITIAL_RETRY_TIME 500 # define BACKOFF_LIMIT 3000 /* RPC default protocol type numbers: */ # define RPC_PTYPE_H 80 # define RPC_PTYPE_L 80 # define PRIO_PTYPE_H 81 # define PRIO_PTYPE_L 80 #include #include "syspec.h" #ifndef ETHERNET # define ETHERNET /* Force declarations which we will need */ #endif #define TS /* Require visibility of TS data structures */ #define TS_INTERNALS /* Require visibility of TS data structures */ #include "rpcrts.h" /* Define all general RPC data structures */ #include "rpc_code.h" /* Coding convention macros */ /* ** Definitions for pointers */ #define NIL_MESSAGE (rpc_message *)0 extern char *malloc(); extern void free(); extern char *strncpy(); /* External Routines ** ----------------- */ extern void rpc_new(); /* Allocate rpc_message */ extern void rpc_dispose(); /* Deallocate rpc_message */ extern void rpc_get_string(); /* Get Pascal or C string parameter */ #ifdef AST /* Always used by CATS !!!!!! */ extern void sys_setast(); /* En/Disable ASTs */ rpc_status ts_when_receive(); /* forward (ASTs only) */ #endif BOOLEAN lance_enabled; /* Comment removed to compile with AST option needed for TSCATS routines */ /* ** Ethernet access routines ** ** These are the low-level routines which provide access at the packet level. */ #ifdef __STDC__ extern rpc_status rpc_eth_init(); /* Power-on initialise */ extern rpc_status rpc_eth_open(); /* Initialise socket */ extern rpc_status rpc_eth_close(); /* Deinitialise socket */ extern rpc_status rpc_eth_send(); /* Transmit one packet */ extern rpc_status rpc_eth_receive(); /* Synchronous receive */ extern rpc_status rpc_eth_when_receive(); /* Queue async. receive */ extern rpc_status rpc_eth_my_address( /* Return host address */ rpc_ethernet_address host); /* Buffer host address */ #else extern rpc_status rpc_eth_init(); /* Power-on initialise */ extern rpc_status rpc_eth_open(); /* Initialise socket */ extern rpc_status rpc_eth_close(); /* Deinitialise socket */ extern rpc_status rpc_eth_send(); /* Transmit one packet */ extern rpc_status rpc_eth_receive(); /* Synchronous receive */ extern rpc_status rpc_eth_when_receive(); /* Queue async. receive */ extern rpc_status rpc_eth_my_address(); /* Return host address */ #endif /* UTILITY ROUTINES ** */ /* I/O routines used for trace output ** ** ** Print ethernet address as XX_XX_XX_XX_XX_XX ** ---------------------- ** ** On entry, ** e points to the address in binary as 6 bytes ** rpc_trace must be TRUE */ #ifdef ETHERNET #ifdef __STDC__ void write_ethernet_address(rpc_ethernet_address e) #else void write_ethernet_address(e) rpc_ethernet_address e; #endif { register int i; for (i =0; i<5; i++) { UTRACE(tfp, "%02x_", e[i]); } UTRACE(tfp, "%02x", e[5]); } #endif /* Ethernet-Specific Routines ** -------------------------- */ /* Parse hexadecimal character hex_digit() ** --------------------------- */ rpc_byte hex_digit(pp, pstatus) char **pp; /* Pointer is moved on */ rpc_status *pstatus; /* Updated only if bad */ { register char ch = *(*pp)++; if ((ch>='0')&&(ch<='9')) return (rpc_byte)(ch-'0'); if ((ch>='A')&&(ch<='F')) return (rpc_byte)((ch-'A')+10); if ((ch>='a')&&(ch<='f')) return (rpc_byte)((ch-'a')+10); *pstatus = RPC_S_BAD_ETHERNET_ADDRESS; return 0; } /* Parse hexadecimal byte (two characters) hex_byte() ** ---------------------- */ rpc_byte hex_byte(pp, pstatus) char **pp; /* Pointer is moved on */ rpc_status *pstatus; /* Updated only if bad */ { rpc_byte msbyte; if ((**pp == '_') || (**pp == '-')) (*pp)++; /* Skip _ or - */ msbyte = hex_digit(pp, pstatus) * 16; return msbyte + hex_digit(pp, pstatus); } /* Parse Ethernet Address eth_parse() ** ---------------------- ** ** This sets the ethernet addres, protocol type, and the masks from the ** ethernet address string. The obsolete form "123456123456.priority" ** is allowed for "123456123456_5150". ** ** Note that the hex_byte routine stored any hex errors into the status ** word as we go. */ #ifdef __STDC__ PRIVATE rpc_status eth_parse(socket_type soc, char * tsap) #else PRIVATE rpc_status eth_parse(soc, tsap) socket_type soc; char *tsap; #endif { int j; /* Byte counter */ char *p = tsap; /* Pointer into string */ rpc_status status = RPC_S_NORMAL; /* Wildcard address: */ if (*p == WILDCARD) { p++; for (j = 0; j<6; j++) { soc->mdp.soc_ether.soc_addr_filter[j] = 0; soc->mdp.soc_ether.soc_addr_mask[j] = 0; } /* for */ /* Specific address: */ } else { for (j = 0; j<6 ; j++) { soc->mdp.soc_ether.soc_rx_dest_addr[j] = hex_byte(&p, &status); soc->mdp.soc_ether.soc_addr_filter[j] = soc->mdp.soc_ether.soc_rx_dest_addr[j]; soc->mdp.soc_ether.soc_addr_mask[j] = 255; } /*for*/ } /*if else*/ /* Parse protocol type: */ soc->mdp.soc_ether.soc_type_mask = -1; if (*p == 0) { soc->mdp.soc_ether.soc_type_filter.l = RPC_PTYPE_L; soc->mdp.soc_ether.soc_type_filter.h = RPC_PTYPE_H; } else if (*p == '.') { soc->mdp.soc_ether.soc_type_filter.l = PRIO_PTYPE_L; soc->mdp.soc_ether.soc_type_filter.h = PRIO_PTYPE_H; } else if (*p == WILDCARD) { soc->mdp.soc_ether.soc_type_filter.l = 0; soc->mdp.soc_ether.soc_type_filter.h = 0; soc->mdp.soc_ether.soc_type_mask = 0; } else { soc->mdp.soc_ether.soc_type_filter.h = hex_byte(&p, &status); soc->mdp.soc_ether.soc_type_filter.l = hex_byte(&p, &status); } /*else*/ return status; } /* eth_parse */ /* Return RPC address of this socket eth_my_address() ** --------------------------------- ** ** This routine returns a string indicating the RPC address of the socket. ** Do not confuse this with rpc_eth_my_address which gives the ethernet ** address in binary of the node. ** ** On entry, ** soc should be a valid socket pointer ** addrstr points to a buffer to contain a string. ** addrlen indicates the length of the string buffer ** ** On exit, ** *addrstr has the contact address for an external node wishing to contact ** this socket written into it, zero terminated, without the ** protocol on the end. */ #ifdef __STDC__ rpc_status eth_my_address( socket_type soc, chr * addrstr, int addrlen) #else rpc_status eth_my_address(soc, addrstr, addrlen) socket_type soc; char * addrstr; int addrlen; #endif { rpc_ethernet_address host; rpc_status status; /* Format: AA0004002F58_5051 is 17 characters +1 for terminator */ if (addrlen < 18) return RPC_S_BUFFER_OVERFLOW; status = rpc_eth_my_address(host); /* Get binary of host address */ if (BAD(status)) return status; sprintf(addrstr, "%02x%02x%02x%02x%02x%02x_%02x%02x", host[0], host[1], host[2], host[3], host[4], host[5], soc->mdp.soc_ether.soc_type_filter.h, soc->mdp.soc_ether.soc_type_filter.l); addrstr[17]=0; /* terminate it */ return status; } /* Return RPC address of peer eth_peer_address() ** -------------------------- ** ** On entry, ** soc should be a valid socket pointer ** address points to a buffer to contain an rpc_name. ** ** On exit, ** *address has the contact address for the peer node. ** blank filled. ** 26 useful characters in the address. */ #ifdef __STDC__ rpc_status eth_peer_address( socket_type soc, char * addrstr, int addrlen) #else rpc_status eth_peer_address(soc, addrstr, addrlen) socket_type soc; char * addrstr; int addrlen; #endif { rpc_byte *peer; if (addrlen < 19) return RPC_S_BUFFER_OVERFLOW; if (soc->mdp.soc_ether.soc_addr_filter[0]) /* Not wildcard */ peer = soc->mdp.soc_ether.soc_addr_filter; else peer = soc->mdp.soc_ether.soc_rx_source_addr; /* If wild, last sender */ sprintf(addrstr, "%02x%02x%02x%02x%02x%02x_%02x%02x", peer[0], peer[1], peer[2], peer[3], peer[4], peer[5], soc->mdp.soc_ether.soc_type_filter.h, soc->mdp.soc_ether.soc_type_filter.l); addrstr[17]=0; /* terminate it */ return RPC_S_NORMAL; } /* Check whether two ethernet addresses are the same ** ** */ BOOLEAN same_address(a, b) rpc_ethernet_address a, b; { return (BOOLEAN)( (a[0] == b[0]) && (a[1] == b[1]) && (a[2] == b[2]) && (a[3] == b[3]) && (a[4] == b[4]) && (a[5] == b[5])); } /*************************************************************************** ** ** Function: Check whether a message arriving is a duplicate, ** and if so, take the apropriate action. ** */ #ifdef __STDC__ BOOLEAN duplicate( rpc_message_pointer pmessage) #else BOOLEAN duplicate(pmessage) rpc_message_pointer pmessage; #endif { rpc_message_pointer scan; register rpc_message *mes = pmessage; if ( (mes->body.which == RETURN_MESSAGE) || (mes->body.which == REJECT_MESSAGE)) { if (mes->body.cal.call_transaction_id.h == 1) { register struct socket_struct *soc = mes->m_socket; if (soc->soc_last_call_sent != NIL_MESSAGE) if (mes->body.cal.call_transaction_id.l != soc->soc_last_call_sent->body.cal.call_transaction_id.l) return TRUE; } } else { if (mes->body.which == CALL_MESSAGE) { if (mes->body.cal.call_transaction_id.h == 1) { scan = mes->m_socket->soc_last_reply_sent; for(;;) { if (scan) { if (same_address(scan->header.ether.dest, mes->header.ether.source)) { break; } else { scan = scan->m_next; } } else break; } /*for*/; if (scan) if (mes->body.cal.call_transaction_id.l != 0) if (mes->body.cal.call_transaction_id.l == scan->body.ret.return_transaction_id.l) { CTRACE(tfp, "RPC/TS: Retransmitting reply\n"); mes->m_status = rpc_eth_send(scan); return TRUE; } return FALSE; } } } return FALSE; } #ifdef AST /* Service Arrival of Asynchronous Message eth_ast_service() ** --------------------------------------- ** * This procedure is called asynchonously when a message arrives. * The procedure deals with duplicate messages, and calls the user AST * service procedure if the message is not a duplicate. * * On entry, * The only parameter is the received message, with the status and * socket number filled in. */ PUBLIC void eth_ast_service(message) rpc_message *message; { socket_type socket; rpc_message_pointer pmessage; { register rpc_message * mes = message; register struct socket_struct *soc = mes->m_socket; CTRACE(tfp, "RPC/TS/Eth: Async. message received.\n"); pmessage = 0; if (duplicate(pmessage)) { mes->m_index = RPC_BUFFER_SIZE; (void) rpc_eth_when_receive(mes); /* Ignore status */ } else { memcpy(soc->mdp.soc_ether.soc_rx_dest_addr, mes->header.ether.dest, 6); memcpy(soc->mdp.soc_ether.soc_rx_source_addr, mes->header.ether.source, 6); memcpy(&soc->mdp.soc_ether.soc_rx_protocol_type, &mes->header.ether.ptype, 2); (*soc->soc_user_ast)(message); /* Call USER AST */ } } } #endif /* Send a message over the given socket eth_write() ** ------------------------------------ ** ** On entry, ** pmessage points to a pointer to the message, with fields: ** m_next (undefined) ** m_socket valid ** m_index Length of message to be sent in bytes, ** exclusing any bytes needed for the protocol. ** m_status (undefined) ** ** On exit, ** ** The pointer to the message may have been changed. In this case, ** it will point to a message with the m_socket and m_status fields ** set, but without the data. This routine is therefore DESTRUCTIVE. ** ** returns: Status of operation. */ #ifdef __STDC__ PRIVATE rpc_status eth_write(rpc_message_pointer *ppmessage) #else PRIVATE rpc_status eth_write(ppmessage) rpc_message_pointer *ppmessage; #endif { rpc_status status; register rpc_message *mes = *ppmessage; register struct socket_struct *soc = mes->m_socket; status = check_socket(soc); if (BAD(status)) return status; if (rpc_trace) { UTRACE(tfp, "RPC/eth: Sending message, "); hex_message(mes, mes->m_index); } /* If this is a wildcard socket, send this packet back to the sender ** of the last packet received. Otherwise, send it to the address for ** which the socket was set up. */ if (soc->mdp.soc_ether.soc_addr_mask[0] == 0) { memcpy( mes->header.ether.dest, soc->mdp.soc_ether.soc_rx_source_addr, 6); memcpy( &mes->header.ether.ptype.h, &soc->mdp.soc_ether.soc_rx_protocol_type.h, 2); } else { memcpy( mes->header.ether.dest, soc->mdp.soc_ether.soc_addr_filter, 6); memcpy( &mes->header.ether.ptype.h, &soc->mdp.soc_ether.soc_type_filter.h, 2); } /* If the message is an outgoing call, then the transaction identifier ** if generated. */ if (mes->body.which == CALL_MESSAGE) { register courier_word *tid = &mes->body.cal.call_transaction_id; tid->h = 1; tid->l = soc->soc_next_call_tid; soc->soc_next_call_tid = soc->soc_next_call_tid % 64 + 1; } status = rpc_eth_send(mes); /* If the message was a reply, then we should save it in case we need to ** retransmit it. Replies are stored on a list, as we must separately keep ** replies to different addresses. */ if ((mes->body.which == RETURN_MESSAGE) || (mes->body.which == REJECT_MESSAGE)) { rpc_message_pointer scan, last, temp_pointer; last = NIL_MESSAGE; temp_pointer = NIL_MESSAGE; scan = soc->soc_last_reply_sent; do { if (scan == NIL_MESSAGE) { if (rpc_trace) { UTRACE(tfp, "RPC/eth: First reply to "); write_ethernet_address(mes->header.ether.dest); UTRACE(tfp, "\n"); } /*if*/ temp_pointer = *ppmessage; temp_pointer->m_next = NIL_MESSAGE; *ppmessage = (rpc_message *)malloc( (unsigned)(sizeof(*(*ppmessage)))); } else if (same_address((*ppmessage)->header.ether.dest, scan->header.ether.dest)) { temp_pointer = (*ppmessage); (*ppmessage) = scan; temp_pointer->m_next = (*ppmessage)->m_next; } else { last = scan; scan = scan->m_next; } } while (!(temp_pointer != NIL_MESSAGE)); if (last == NIL_MESSAGE) soc->soc_last_reply_sent = temp_pointer; else last->m_next = temp_pointer; (*ppmessage)->m_socket = temp_pointer->m_socket; } /* If the message is an outgoing call, we stored just the last call sent. ** (If it's not a call or a return, it is not known) */ else if ((mes->body.which == CALL_MESSAGE)) { rpc_message_pointer temp_pointer; if (soc->soc_last_call_sent == NIL_MESSAGE) rpc_new(&soc->soc_last_call_sent, RPC_BUFFER_SIZE); temp_pointer = (*ppmessage); (*ppmessage) = soc->soc_last_call_sent; soc->soc_last_call_sent = temp_pointer; (*ppmessage)->m_socket = soc->soc_last_call_sent->m_socket; } (*ppmessage)->m_status = status; return status; } /* Receive message eth_read() ** --------------- ** ** This procedure waits for a message to arrive on a socket and returns the ** message. A suitable message buffer is passed to the routine, but a ** different one may be returned. If the socket passed is the master socket ** for mutiple connections, then the message recieved will carry the socket ** number of the slave socket on which the message actually arrived. ** (Ethernet does not use slave sockets) ** ** On entry: ** ppmessage is pointer to pointer to buffer, fields: ** m_socket The socket to be used - possibly a master socket ** m_index don't care ** m_status don't care ** m_next don't care ** ** timeout -1 for infinite, 0 for poll, else in units of 10ms ** ** On exit, ** returns The status of the operation. ** *pmessage may have been modified to point to another message ** from the pool. Fields: ** m_socket The (possible slave) socket the msg came on. ** m_status The status of the operation (= return value). ** m_index The number of bytes recieved if good status ** m_next junk. ** */ #ifdef __STDC__ rpc_status eth_read( rpc_message_pointer *ppmessage, int timeout) #else rpc_status eth_read(ppmessage, timeout) rpc_message_pointer *ppmessage; int timeout; #endif { rpc_status status; int waited; /* Time waited so far */ int backoff; /* Time to wait next time, which doubles */ register rpc_message *mes = *ppmessage; register struct socket_struct *soc = mes->m_socket; if (soc->soc_last_call_sent == NULL) /* If nothing to retransmit */ backoff = timeout; /* Wait for given time */ else { waited = 0; backoff = INITIAL_RETRY_TIME; } for(;;) { if (soc->soc_last_call_sent != NULL) if (timeout >= 0) if (waited + backoff > timeout) { backoff = timeout - waited; waited = waited + backoff; } /* Wait for a packet to come in which is not a duplication of one we already ** have: */ do { CTRACE(tfp, "RPC/eth: Waiting for ethernet packet\n"); status = rpc_eth_receive(ppmessage, backoff); if (BAD(status)) break; } while (duplicate(*ppmessage)); /* We take a copy of the last header received */ memcpy(soc->mdp.soc_ether.soc_rx_dest_addr, (*ppmessage)->header.ether.dest, 6+6+2); /* Decide whether to retransmit a previous call: */ if (status != RPC_S_TIMEOUT) break; if (soc->soc_last_call_sent == NIL_MESSAGE) break; if (((timeout < 0) || (waited < timeout))) { if (backoff < BACKOFF_LIMIT / 2) backoff = backoff + backoff; else backoff = BACKOFF_LIMIT; status = rpc_eth_send(soc->soc_last_call_sent); CTRACE(tfp, "RPC/TS: *** Retransmitted last call.\n"); } else { break; } } /*for*/; /* When we successfully receive a reply, we can throw away the call: */ if (GOOD(status)) if (soc->soc_last_call_sent != NIL_MESSAGE) if (((*ppmessage)->body.which == RETURN_MESSAGE) || ((*ppmessage)->body.which == REJECT_MESSAGE)) { rpc_dispose(soc->soc_last_call_sent); soc->soc_last_call_sent = NIL_MESSAGE; } if (rpc_trace) if (GOOD(status)) { UTRACE(tfp, "RPC/eth: (synchronous) Message received OK, "); hex_message(*ppmessage, (*ppmessage)->m_index); } else { UTRACE(tfp, "RPC/eth: Receive FAILED, status = %lx\n", status); } (*ppmessage)->m_status = status; return status; } /* Asynchronous Receive ** -------------------- ** ** On entry, ** pmessage is the address of a suitable message buffer ** action is the address of the routine to be called ** user_1 is not used at the moment. ** ** On exit, ** the parameters are stored only. Exits immediately. ** ** On arrival of the next message: ** The routine *action() is called, and passed the message as a parameter. ** If the buffer *pmessage is not used, it will be reused elsewhere and ** a different buffer passed to *action(). */ #ifdef __STDC__ rpc_status eth_aread( rpc_message_pointer pmessage, rpc_pointer action, rpc_integer user_1) #else rpc_status eth_aread(pmessage, action, user_1) rpc_message_pointer pmessage; rpc_pointer action; rpc_integer user_1; #endif { #ifdef AST pmessage->m_socket->soc_user_ast = action; return rpc_eth_when_receive(pmessage); /* Call system-depenedent code */ #else return(RPC_S_NOT_IMPLEMENTED); #endif } /* eth_aread */ /* Open a transport connection eth_open() ** --------------------------- ** ** On entry, ** soc points to a recently created socket, fields as follows: ** ** soc_last_call_sent NULL ** soc_last_reply_sent NULL ** soc_next-call_tid 0 ** soc_protocol valid pointer to protocol structure ** ** and otherwise uninitialised. ** ** tsap points to a zero-terminated string representing the address ** (with no .ETHERNET on it) */ #ifdef __STDC__ rpc_status eth_open(socket_type soc, char * tsap) #else rpc_status eth_open(soc, tsap) socket_type soc; char *tsap; #endif { rpc_status status; status = eth_parse(soc, tsap); if (BAD(status)) return status; return rpc_eth_open(soc); } /* Close a Transport Connection eth_close() ** ---------------------------- ** ** On entry, ** soc Must be a socket opened by eth_open(). ** ** On exit, ** returns a status of the close operation. */ /* In fact rpc_eth_close is used instead of this routine. */ #ifdef NOT_DEFINED #ifdef __STDC__ rpc_status eth_open(socket_type soc) #else rpc_status eth_open(soc) socket_type soc; #endif { return rpc_eth_close(soc); } /* eth_close */ #endif /* Data structure defining this medium: ** ----------------------------------- */ rpc_protocol eth_protocol = { 0, /* Link (not yet used) */ "ethernet", /* Name of medium used in address strings */ 0, /* No, not reliable */ eth_open, rpc_eth_close, /* Here we use the system-dependent routine */ eth_write, eth_read, eth_aread, eth_my_address, eth_peer_address, }; /* Register this protocol with the RPC system ** ------------------------------------------ */ #ifdef __STDC__ rpc_status use_ethernet(void) #else rpc_status use_ethernet() #endif { rpc_eth_init(); /* Initialise any machine-dependent stuff */ rpc_use(ð_protocol); }