1
0
This repository has been archived on 2024-07-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2024-07-22 01:58:46 -03:00

587 lines
16 KiB
C

/* kernel.c - Processing of incoming kernel messages */
/* Written 1995-2000 by Werner Almesberger, EPFL-LRC/ICA */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <atm.h>
#include <linux/atmsvc.h>
#include <linux/atmdev.h>
#include "atmd.h"
#include "uni.h"
#include "qlib.h"
#include <q.out.h>
#include "proto.h"
#include "sap.h"
#include "io.h"
#include "policy.h"
#include "timeout.h"
#define COMPONENT "KERNEL"
static int send_setup(SOCKET *sock)
{
static unsigned long call_ref = 0;
struct sockaddr_atmsvc *local;
SOCKET *walk;
Q_DSC dsc;
int error,size;
do {
if (++call_ref == 0x7fffff) call_ref = 1;
for (walk = sockets; walk; walk = walk->next)
if (walk->call_ref == call_ref) break;
}
while (walk);
sock->call_ref = call_ref;
q_create(&dsc,q_buffer,MAX_Q_MSG);
local = NULL;
error = 0;
sock->sig = route_remote(&sock->remote);
if (sock->sig && sock->sig->mode == sm_switch) local = &sock->local;
if (atmsvc_addr_in_use(sock->local) && !local) {
sock->sig = route_local(&sock->local);
if (sock->sig) local = &sock->local;
else {
error = -EADDRNOTAVAIL;
diag(COMPONENT,DIAG_WARN,"local address no longer available");
}
}
else {
if (!sock->sig) error = -EHOSTUNREACH;
else {
if (!local) local = get_local(sock->sig);
if (!local) local = get_local(NULL);
if (local) sock->local = *local;
else {
error = -EADDRNOTAVAIL;
diag(COMPONENT,DIAG_ERROR,"no local address");
}
}
}
if (local) {
if (*local->sas_addr.pub) {
q_assign(&dsc,QF_cgpn_plan,ATM_NP_E164);
q_assign(&dsc,QF_cgpn_type,ATM_TON_INTRNTNL);
q_write(&dsc,QF_cgpn,(void *) local->sas_addr.pub,
strlen(local->sas_addr.pub));
}
else {
q_assign(&dsc,QF_cgpn_plan,ATM_NP_AEA);
q_assign(&dsc,QF_cgpn_type,ATM_TON_UNKNOWN);
q_write(&dsc,QF_cgpn,(void *) local->sas_addr.prv,ATM_ESA_LEN);
}
}
q_assign(&dsc,QF_call_ref,call_ref);
q_assign(&dsc,QF_msg_type,ATM_MSG_SETUP);
q_assign(&dsc,QF_aal_type,5); /* AAL 5 */
#if defined(UNI30) || defined(DYNAMIC_UNI)
if (!error && sock->sig->uni == S_UNI30)
q_assign(&dsc,QF_aal_mode,1); /* Message mode - LANE seems to really
want this */
#endif
q_assign(&dsc,QF_sscs_type,0); /* unspecified - LANE wants this */
if (!error)
error = sap_encode(&dsc,&sock->remote,&sock->sap,&sock->qos,
sock->sig->uni,sock->sig->max_rate);
q_assign(&dsc,QF_bearer_class,16); /* BCOB-X */
q_assign(&dsc,QF_upcc,0); /* p2p */
#if !defined(UNI30) || defined(DYNAMIC_UNI)
if (!error && sock->sig->uni != S_UNI30)
q_assign(&dsc,QF_qos_cs,Q2931_CS_ITU);
#endif
q_assign(&dsc,QF_qos_fw,0); /* QOS 0 */
q_assign(&dsc,QF_qos_bw,0);
if (!error && sock->sig->mode != sm_user) {
if (!atmpvc_addr_in_use(sock->pvc)) {
int vpci,vci;
if (sock->sig->mode == sm_switch)
diag(COMPONENT,DIAG_FATAL,"No CI allocator (use -A)");
vpci = 0;
sock->pvc.sap_addr.itf = get_itf(sock->sig,&vpci);
sock->pvc.sap_addr.vpi = vpci;
vci = get_vci(sock->pvc.sap_addr.itf);
if (vci < 0) {
(void) q_close(&dsc);
return vci;
}
sock->pvc.sap_addr.vci = vci;
}
q_assign(&dsc,QF_vpi,sock->pvc.sap_addr.vpi);
q_assign(&dsc,QF_vci,sock->pvc.sap_addr.vci);
}
if ((size = q_close(&dsc)) < 0) error = -EINVAL;
else if (!error) to_signaling(sock->sig,q_buffer,size);
return error;
}
static int send_connect(SOCKET *sock)
{
Q_DSC dsc;
int size;
q_create(&dsc,q_buffer,MAX_Q_MSG);
q_assign(&dsc,QF_msg_type,ATM_MSG_CONNECT);
q_assign(&dsc,QF_call_ref,sock->call_ref);
if (sock->ep_ref >= 0) q_assign(&dsc,QF_ep_ref,sock->ep_ref);
if (sock->ep_ref <= 0) { /* no AAL parameters if ep_ref present and != 0 */
q_assign(&dsc,QF_aal_type,5);
#if defined(UNI30) || defined(DYNAMIC_UNI)
if (sock->sig->uni == S_UNI30)
q_assign(&dsc,QF_aal_mode,1); /* Message mode - LANE seems to
really want this */
#endif
q_assign(&dsc,QF_sscs_type,0); /* unspecified - LANE wants this */
q_assign(&dsc,QF_fw_max_sdu,sock->qos.rxtp.max_sdu);
q_assign(&dsc,QF_bw_max_sdu,sock->qos.txtp.max_sdu);
}
if ((size = q_close(&dsc)) >= 0) to_signaling(sock->sig,q_buffer,size);
return 0;
}
#if defined(Q2963_1) || defined(DYNAMIC_UNI)
static void send_modify_request(SOCKET *sock)
{
Q_DSC dsc;
int size;
q_create(&dsc,q_buffer,MAX_Q_MSG);
q_assign(&dsc,QF_msg_type,ATM_MSG_MODIFY_REQ);
q_assign(&dsc,QF_call_ref,sock->call_ref);
if (sock->new_qos.txtp.traffic_class)
q_assign(&dsc,QF_fw_pcr_01,SELECT_TOP_PCR(sock->new_qos.txtp));
if (sock->new_qos.rxtp.traffic_class)
q_assign(&dsc,QF_bw_pcr_01,SELECT_TOP_PCR(sock->new_qos.rxtp));
if ((size = q_close(&dsc)) >= 0) to_signaling(sock->sig,q_buffer,size);
}
static void send_modify_ack(SOCKET *sock)
{
Q_DSC dsc;
int size;
q_create(&dsc,q_buffer,MAX_Q_MSG);
q_assign(&dsc,QF_msg_type,ATM_MSG_MODIFY_ACK);
q_assign(&dsc,QF_call_ref,sock->call_ref);
if (sock->qos.rxtp.traffic_class != ATM_NONE &&
sock->qos.rxtp.max_pcr < sock->new_qos.rxtp.max_pcr) {
q_assign(&dsc,QF_type_of_report,ATM_TOR_MOD_CONF);
START_TIMER(sock,T361);
}
if ((size = q_close(&dsc)) >= 0) to_signaling(sock->sig,q_buffer,size);
}
#endif
static void dispatch(SOCKET *sock,struct atmsvc_msg *msg)
{
int error;
switch (msg->type) {
case as_bind: /* only in NULL state */
if (sock) break;
if (msg->svc.sas_family != AF_ATMSVC) {
SEND_ERROR(msg->vcc,-EAFNOSUPPORT);
return;
}
if (!atmsvc_addr_in_use(msg->svc))
#ifdef BE_PICKY_ABOUT_BINDING_LOCAL_WILDCARD_ADDRESSES
if (!get_local(NULL)) SEND_ERROR(msg->vcc,-EADDRNOTAVAIL);
else
#endif
send_kernel(msg->vcc,kptr_null,as_okay,0,NULL,NULL,
get_local(NULL),NULL,NULL);
else {
if (!route_local(&msg->svc)) {
SEND_ERROR(msg->vcc,-EADDRNOTAVAIL);
return;
}
send_kernel(msg->vcc,kptr_null,as_okay,0,NULL,NULL,NULL,NULL,
NULL);
}
return;
case as_connect: /* NULL state only */
if (sock) break;
if (!allow(&msg->svc,ACL_OUT)) {
SEND_ERROR(msg->vcc,-EACCES);
return;
}
sock = new_sock(msg->vcc);
sock->local = msg->local;
sock->remote = msg->svc;
sock->qos = msg->qos;
sock->sap = msg->sap;
sock->state = ss_connecting;
sock->pvc = msg->pvc;
error = send_setup(sock);
if (error) {
SEND_ERROR(msg->vcc,error);
free_sock(sock);
return;
}
START_TIMER(sock,T303);
new_state(sock,ss_connecting);
return;
case as_accept:
if (sock->state == ss_zombie) {
SEND_ERROR(msg->vcc,-ECONNABORTED); /* -ERESTARTSYS ? */
free_sock(sock);
return;
}
if (sock->state != ss_indicated && sock->state != ss_proceeding)
break;
if (sock->state == ss_indicated && sock->sig->mode != sm_user)
diag(COMPONENT,DIAG_FATAL,"No CI allocator (use -A)");
error = send_connect(sock);
if (!error) {
START_TIMER(sock,T313);
new_state(sock,ss_accepting);
return;
}
SEND_ERROR(sock->id,error);
send_release(sock,0); /* @@@ */
START_TIMER(sock,T308_1);
new_state(sock,ss_wait_rel);
return;
case as_reject: /* ZOMBIE, INDICATED, or PROCEEDING */
switch (sock->state) {
case ss_indicated:
send_release_complete(sock->sig,sock->call_ref,
ATM_CV_CALL_REJ);
/* fall through */
case ss_zombie:
free_sock(sock);
return;
case ss_proceeding:
send_release(sock,ATM_CV_CALL_REJ);
/* @@@ should use msg->reply */
START_TIMER(sock,T308_1);
new_state(sock,ss_wait_rel);
return;
default:
break;
}
break;
case as_listen: /* NULL */
if (sock) break;
if (msg->svc.sas_family != AF_ATMSVC) {
SEND_ERROR(msg->vcc,-EAFNOSUPPORT);
return;
}
if (msg->qos.aal != ATM_AAL5) {
SEND_ERROR(msg->vcc,-EINVAL);
return;
}
if (lookup_sap(&msg->svc,&msg->sap,&msg->qos,NULL,NULL,NULL,1)) {
SEND_ERROR(msg->vcc,-EADDRINUSE);
return;
}
sock = new_sock(msg->vcc);
sock->local = msg->svc;
sock->sap = msg->sap;
sock->qos = msg->qos;
send_kernel(sock->id,kptr_null,as_okay,0,NULL,NULL,NULL,NULL,NULL);
sock->state = ss_listening;
return;
case as_close: /* all but INDICATED, PROCEEDING, ZOMBIE, and WAIT_REL */
if (sock && (sock->state == ss_indicated ||
sock->state == ss_proceeding || sock->state == ss_zombie ||
sock->state == ss_wait_rel)) break;
switch (sock ? sock->state : ss_null) {
case ss_listening:
send_close(sock);
if (sock->listen) new_state(sock,ss_listen_zombie);
else free_sock(sock);
return;
case ss_zombie:
send_close(sock);
/* fall through */
case ss_wait_close:
free_sock(sock);
/* fall through */
case ss_null:
case ss_rel_req:
return;
case ss_connecting:
case ss_accepting:
#if defined(Q2963_1) || defined(DYNAMIC_UNI)
case ss_mod_req:
#endif
STOP_TIMER(sock);
/* fall through */
#if defined(Q2963_1) || defined(DYNAMIC_UNI)
case ss_mod_lcl:
case ss_mod_rcv:
case ss_mod_fin_ok:
case ss_mod_fin_fail:
case ss_mod_fin_ack:
#endif
case ss_connected:
#if defined(Q2963_1) || defined(DYNAMIC_UNI)
if (timer_handler(sock->conn_timer) == on_T361)
STOP_TIMER(sock);
#endif
if (sock->state == ss_connected)
diag(COMPONENT,DIAG_INFO,"Active close (CR 0x%06X)",
sock->call_ref);
send_release(sock,
#ifdef DYNAMIC_UNI
sock->sig->uni != S_UNI30 ? ATM_CV_NORMAL_CLEAR :
ATM_CV_NORMAL_UNSPEC
#else
#if defined(UNI31) || defined(UNI40)
ATM_CV_NORMAL_CLEAR
#else
ATM_CV_NORMAL_UNSPEC
#endif
#endif
);
START_TIMER(sock,T308_1);
new_state(sock,ss_rel_req);
return;
case ss_rel_ind:
send_release_complete(sock->sig,sock->call_ref,0); /* @@@ */
free_sock(sock);
return;
default:
break;
}
break;
case as_identify:
if (sock->state != ss_indicated && sock->state != ss_proceeding)
break;
if (!atmpvc_addr_in_use(msg->pvc)) {
if (sock->sig->mode == sm_switch)
diag(COMPONENT,DIAG_FATAL,"No CI allocator (use -A)");
return;
}
if (sock->sig->mode == sm_net)
diag(COMPONENT,DIAG_FATAL,"CI allocation role conflict");
sock->pvc = msg->pvc;
if (send_call_proceeding(sock))
diag(COMPONENT,DIAG_FATAL,"s_c_p failed");
new_state(sock,ss_proceeding);
return;
#if defined(Q2963_1) || defined(DYNAMIC_UNI)
case as_modify:
if (sock && !(sock->sig->uni & S_Q2963_1)) {
send_kernel(sock->id,kptr_null,as_okay,-ENOPROTOOPT,NULL,NULL,
NULL,NULL,NULL);
return;
}
if (sock && (sock->state == ss_mod_lcl ||
sock->state == ss_mod_req || sock->state == ss_mod_rcv ||
sock->state == ss_mod_fin_ok || sock->state == ss_mod_fin_fail ||
sock->state == ss_mod_fin_ack)) {
send_kernel(sock->id,kptr_null,as_okay,-EALREADY,NULL,NULL,
NULL,NULL,NULL);
return;
}
if (!sock || sock->state != ss_connected || !sock->owner) {
send_kernel(sock->id,kptr_null,as_okay,-EBADFD,NULL,NULL,NULL,
NULL,NULL);
return;
}
if (sock->qos.txtp.traffic_class != msg->qos.txtp.traffic_class ||
sock->qos.rxtp.traffic_class != msg->qos.rxtp.traffic_class ||
(sock->qos.txtp.traffic_class &&
(sock->qos.txtp.max_sdu != msg->qos.txtp.max_sdu)) ||
(sock->qos.rxtp.traffic_class &&
(sock->qos.rxtp.max_sdu != msg->qos.rxtp.max_sdu))) {
/* @@@ may do more checking */
send_kernel(sock->id,kptr_null,as_okay,-EINVAL,NULL,NULL,NULL,
NULL,NULL);
return;
}
sock->new_qos = msg->qos;
send_kernel(sock->id,kptr_null,as_modify,
ATM_MF_INC_RSV | ATM_MF_DEC_SHP,NULL,NULL,NULL,NULL,&msg->qos);
new_state(sock,ss_mod_lcl);
return;
case as_okay:
switch (sock ? sock->state : ss_null) {
case ss_mod_lcl:
send_modify_request(sock);
START_TIMER(sock,T360);
new_state(sock,ss_mod_req);
return;
case ss_mod_rcv:
send_modify_ack(sock);
sock->qos = sock->new_qos;
new_state(sock,ss_connected);
return;
case ss_mod_fin_ok:
send_kernel(sock->id,kptr_null,as_okay,0,NULL,NULL,NULL,
NULL,NULL);
new_state(sock,ss_connected);
return;
case ss_mod_fin_fail:
send_kernel(sock->id,kptr_null,as_okay,sock->error,NULL,
NULL,NULL,NULL,NULL);
sock->error = 0;
/* fall through */
case ss_mod_fin_ack:
new_state(sock,ss_connected);
/* fall through */
default:
return; /* ignore stray as_okay */
}
case as_error:
switch (sock ? sock->state : ss_null) {
case ss_mod_lcl:
send_kernel(sock->id,kptr_null,as_okay,msg->reply,NULL,
NULL,NULL,NULL,NULL);
new_state(sock,ss_connected);
return;
case ss_mod_rcv:
send_modify_reject(sock,ATM_CV_RES_UNAVAIL);
new_state(sock,ss_connected);
return;
case ss_mod_fin_ok:
diag(COMPONENT,DIAG_ERROR,"QOS commit failed");
send_kernel(sock->id,kptr_null,as_okay,0,NULL,NULL,NULL,
NULL,NULL);
/* @@@ clear call instead ? */
new_state(sock,ss_connected);
return;
case ss_mod_fin_fail:
diag(COMPONENT,DIAG_ERROR,"QOS rollback failed");
send_kernel(sock->id,kptr_null,as_okay,sock->error,NULL,
NULL,NULL,NULL,NULL);
sock->error = 0;
/* @@@ clear call instead ? */
new_state(sock,ss_connected);
return;
case ss_mod_fin_ack:
/* @@@ maybe we should even clear the call now */
diag(COMPONENT,DIAG_ERROR,"QOS commit failed");
new_state(sock,ss_connected);
return;
default:
return; /* ignore stray as_error */
}
#endif
default:
diag(COMPONENT,DIAG_WARN,"invalid message %d",(int) msg->type);
return;
}
diag(COMPONENT,DIAG_WARN,"message %s is incompatible with state %s (%d)",
as_name[msg->type],state_name[sock ? sock->state : ss_null],
(int) (sock ? sock->state : ss_null));
}
static void dispatch_listen(SOCKET *sock,struct atmsvc_msg *msg)
{
SOCKET *next;
if (!sock) {
diag(COMPONENT,DIAG_WARN,"message %s is incompatible with state %s "
"(%d)",as_name[msg->type],state_name[ss_null],ss_null);
return;
}
if (!(next = sock->listen)) {
diag(COMPONENT,DIAG_WARN,
"socket 0x%lx got accept/reject/identify with empty listen queue",
msg->vcc);
return;
}
sock->listen = next->listen;
if (sock->state == ss_listen_zombie && !sock->listen) free_sock(sock);
next->listen = NULL;
next->id = msg->vcc;
dispatch(next,msg);
}
void sync_addr(VPCI *vpci)
{
char buf[MAX_ATM_ADDR_LEN+1];
LOCAL_ADDR *walk;
(void) get_addr(vpci->itf,vpci->local_addr);
for (walk = vpci->local_addr; walk->state != ls_unused; walk++) {
if (atm2text(buf,MAX_ATM_ADDR_LEN+1,(struct sockaddr *)
&walk->addr,pretty) < 0) strcpy(buf,"<invalid>");
switch (walk->state) {
case ls_added:
diag(COMPONENT,DIAG_INFO,"Added local ATM address %s at "
"itf %d",buf,vpci->itf);
walk->state = ls_same;
break;
case ls_removed:
diag(COMPONENT,DIAG_INFO,"Removed local ATM address %s at "
"itf %d",buf,vpci->itf);
/*
* walk->state = ls_unused;
*
* This is probably wrong. What if we delete an entry from the middle of
* the list ? @@@
*/
/* @@@ delete SVCs using that address ? */
break;
default:
break;
}
}
}
void from_kernel(struct atmsvc_msg *msg,int size)
{
void (*dispatcher)(SOCKET *,struct atmsvc_msg *);
SOCKET *curr;
if (msg->type == as_itf_notify) {
itf_reload(msg->pvc.sap_addr.itf);
return;
}
if (msg->type == as_terminate) {
#if 0 /* need to pass some ID ... @@@ */
if (mode != sm_switch) {
diag(COMPONENT,DIAG_ERROR,"Ignoring as_terminate received in %s "
"mode",mode == sm_user ? "USER" : "NETWORK");
return;
}
clear_all_calls(sig);
#endif
stop = 1;
diag(COMPONENT,DIAG_INFO,"Going down on as_terminate");
/*
* Fix this - we need to shut it down more gracefully.
*/
return;
}
if (!kptr_eq(&msg->listen_vcc,&kptr_null) && (msg->type == as_accept ||
msg->type == as_reject || msg->type == as_identify)) {
dispatcher = dispatch_listen;
for (curr = sockets; curr; curr = curr->next)
if (kptr_eq(&msg->listen_vcc,&curr->id) &&
(curr->state == ss_listening || curr->state == ss_listen_zombie))
break;
}
else {
dispatcher = dispatch;
for (curr = sockets; curr; curr = curr->next)
if (kptr_eq(&msg->vcc,&curr->id)) break;
}
diag(COMPONENT,DIAG_DEBUG,"FROM KERNEL: %s for socket %p (%s/%s) "
"in state %s",as_name[msg->type],curr,kptr_print(&msg->vcc),
kptr_print(&msg->listen_vcc),state_name[curr ? curr->state : ss_null]);
dispatcher(curr,msg);
}