mirror of
https://gitea.osmocom.org/cellular-infrastructure/osmo-mgw.git
synced 2025-10-23 08:12:01 +00:00
SMPP: Implement support for MO SMS
Each ESME can have a number of prefix-matching routes, or it can be a 'default route' to whcih all otherwise unknown SMS destinations are routed.
This commit is contained in:
@@ -282,10 +282,18 @@ enum gsm_sms_source_id {
|
||||
|
||||
#define SMS_HDR_SIZE 128
|
||||
#define SMS_TEXT_SIZE 256
|
||||
|
||||
struct gsm_sms_addr {
|
||||
uint8_t ton;
|
||||
uint8_t npi;
|
||||
char addr[21+1];
|
||||
};
|
||||
|
||||
struct gsm_sms {
|
||||
unsigned long long id;
|
||||
struct gsm_subscriber *sender;
|
||||
struct gsm_subscriber *receiver;
|
||||
struct gsm_sms_addr destination;
|
||||
enum gsm_sms_source_id source;
|
||||
|
||||
struct {
|
||||
|
@@ -56,6 +56,11 @@
|
||||
#include <openbsc/chan_alloc.h>
|
||||
#include <openbsc/bsc_api.h>
|
||||
|
||||
#ifdef BUILD_SMPP
|
||||
#include "smpp_smsc.h"
|
||||
extern int smpp_try_deliver(struct gsm_sms *sms);
|
||||
#endif
|
||||
|
||||
#define GSM411_ALLOC_SIZE 1024
|
||||
#define GSM411_ALLOC_HEADROOM 128
|
||||
|
||||
@@ -377,13 +382,21 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m
|
||||
LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
|
||||
rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
|
||||
goto out;
|
||||
} else if (da_len_bytes < 4) {
|
||||
LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n");
|
||||
rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
|
||||
goto out;
|
||||
}
|
||||
memset(address_lv, 0, sizeof(address_lv));
|
||||
memcpy(address_lv, smsp, da_len_bytes);
|
||||
/* mangle first byte to reflect length in bytes, not digits */
|
||||
address_lv[0] = da_len_bytes - 1;
|
||||
|
||||
gsms->destination.ton = (address_lv[1] >> 4) & 7;
|
||||
gsms->destination.npi = address_lv[1] & 0xF;
|
||||
/* convert to real number */
|
||||
gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1);
|
||||
gsm48_decode_bcd_number(gsms->destination.addr,
|
||||
sizeof(gsms->destination.addr), address_lv, 1);
|
||||
smsp += da_len_bytes;
|
||||
|
||||
gsms->protocol_id = *smsp++;
|
||||
@@ -449,8 +462,19 @@ static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *m
|
||||
/* determine gsms->receiver based on dialled number */
|
||||
gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
|
||||
if (!gsms->receiver) {
|
||||
#ifdef BUILD_SMPP
|
||||
rc = smpp_try_deliver(gsms);
|
||||
if (rc == 1) {
|
||||
rc = 1; /* cause 1: unknown subscriber */
|
||||
osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
|
||||
} else if (rc < 0) {
|
||||
rc = 21; /* cause 21: short message transfer rejected */
|
||||
/* FIXME: handle the error somehow? */
|
||||
}
|
||||
#else
|
||||
rc = 1; /* cause 1: unknown subscriber */
|
||||
osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
|
||||
#endif
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@@ -66,7 +66,7 @@ static struct gsm_subscriber *subscr_by_dst(struct gsm_network *net,
|
||||
}
|
||||
|
||||
/*! \brief find a TLV with given tag in list of libsmpp34 TLVs */
|
||||
struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
|
||||
static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
|
||||
{
|
||||
struct tlv_t *t;
|
||||
|
||||
@@ -217,7 +217,6 @@ int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
|
||||
static int smpp_sms_cb(unsigned int subsys, unsigned int signal,
|
||||
void *handler_data, void *signal_data)
|
||||
{
|
||||
struct gsm_network *network = handler_data;
|
||||
struct sms_signal_data *sig_sms = signal_data;
|
||||
struct gsm_sms *sms = sig_sms->sms;
|
||||
int rc = 0;
|
||||
@@ -288,8 +287,98 @@ static int smpp_subscr_cb(unsigned int subsys, unsigned int signal,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms)
|
||||
{
|
||||
struct deliver_sm_t deliver;
|
||||
uint8_t dcs;
|
||||
|
||||
memset(&deliver, 0, sizeof(deliver));
|
||||
deliver.command_length = 0;
|
||||
deliver.command_id = DELIVER_SM;
|
||||
deliver.command_status = ESME_ROK;
|
||||
|
||||
strcpy((char *)deliver.service_type, "CMT");
|
||||
if (esme->acl && esme->acl->deliver_src_imsi) {
|
||||
deliver.source_addr_ton = TON_Subscriber_Number;
|
||||
deliver.source_addr_npi = NPI_Land_Mobile_E212;
|
||||
snprintf((char *)deliver.source_addr,
|
||||
sizeof(deliver.source_addr), "%s",
|
||||
sms->sender->imsi);
|
||||
} else {
|
||||
deliver.source_addr_ton = TON_Network_Specific;
|
||||
deliver.source_addr_npi = NPI_ISDN_E163_E164;
|
||||
snprintf((char *)deliver.source_addr,
|
||||
sizeof(deliver.source_addr), "%s",
|
||||
sms->sender->extension);
|
||||
}
|
||||
|
||||
deliver.dest_addr_ton = sms->destination.ton;
|
||||
deliver.dest_addr_npi = sms->destination.npi;
|
||||
memcpy(deliver.destination_addr, sms->destination.addr,
|
||||
sizeof(deliver.destination_addr));
|
||||
|
||||
deliver.esm_class = 1; /* datagram mode */
|
||||
if (sms->ud_hdr_ind)
|
||||
deliver.esm_class |= 0x40;
|
||||
if (sms->reply_path_req)
|
||||
deliver.esm_class |= 0x80;
|
||||
|
||||
deliver.protocol_id = sms->protocol_id;
|
||||
deliver.priority_flag = 0;
|
||||
deliver.registered_delivery = 0;
|
||||
|
||||
dcs = sms->data_coding_scheme;
|
||||
if (dcs == GSM338_DCS_1111_7BIT ||
|
||||
((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 0)) {
|
||||
uint8_t *src = sms->user_data;
|
||||
uint8_t *dst = deliver.short_message;
|
||||
uint8_t src_byte_len = sms->user_data_len;
|
||||
|
||||
/* SMPP has this strange notion of putting 7bit SMS in
|
||||
* an octet-aligned mode */
|
||||
deliver.data_coding = 0x01;
|
||||
if (sms->ud_hdr_ind) {
|
||||
uint8_t udh_len = sms->user_data[0];
|
||||
src += udh_len + 1;
|
||||
dst += udh_len + 1;
|
||||
src_byte_len -= udh_len + 1;
|
||||
memcpy(dst, sms->user_data, udh_len + 1);
|
||||
deliver.sm_length = udh_len + 1;
|
||||
}
|
||||
deliver.sm_length += gsm_7bit_decode((char *)dst, src, src_byte_len);
|
||||
} else if (dcs == GSM338_DCS_1111_8BIT_DATA ||
|
||||
((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 4)) {
|
||||
deliver.data_coding = 0x02;
|
||||
deliver.sm_length = sms->user_data_len;
|
||||
memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
|
||||
} else if ((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 8) {
|
||||
deliver.data_coding = 0x08; /* UCS-2 */
|
||||
deliver.sm_length = sms->user_data_len;
|
||||
memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
|
||||
}
|
||||
|
||||
return smpp_tx_deliver(esme, &deliver);
|
||||
}
|
||||
|
||||
static struct smsc *g_smsc;
|
||||
|
||||
int smpp_try_deliver(struct gsm_sms *sms)
|
||||
{
|
||||
struct osmo_esme *esme;
|
||||
struct osmo_smpp_addr dst;
|
||||
|
||||
memset(&dst, 0, sizeof(dst));
|
||||
dst.ton = sms->destination.ton;
|
||||
dst.npi = sms->destination.npi;
|
||||
memcpy(dst.addr, sms->destination.addr, sizeof(dst.addr));
|
||||
|
||||
esme = smpp_route(g_smsc, &dst);
|
||||
if (!esme)
|
||||
return 1; /* unknown subscriber */
|
||||
|
||||
return deliver_to_esme(esme, sms);
|
||||
}
|
||||
|
||||
struct smsc *smsc_from_vty(struct vty *v)
|
||||
{
|
||||
/* FIXME: this is ugly */
|
||||
|
@@ -51,6 +51,71 @@ enum emse_bind {
|
||||
ESME_BIND_TX = 0x02,
|
||||
};
|
||||
|
||||
const struct value_string smpp_status_strs[] = {
|
||||
{ ESME_ROK, "No Error" },
|
||||
{ ESME_RINVMSGLEN, "Message Length is invalid" },
|
||||
{ ESME_RINVCMDLEN, "Command Length is invalid" },
|
||||
{ ESME_RINVCMDID, "Invalid Command ID" },
|
||||
{ ESME_RINVBNDSTS, "Incorrect BIND Status for given command" },
|
||||
{ ESME_RALYBND, "ESME Already in Bound State" },
|
||||
{ ESME_RINVPRTFLG, "Invalid Priority Flag" },
|
||||
{ ESME_RINVREGDLVFLG, "Invalid Registered Delivery Flag" },
|
||||
{ ESME_RSYSERR, "System Error" },
|
||||
{ ESME_RINVSRCADR, "Invalid Source Address" },
|
||||
{ ESME_RINVDSTADR, "Invalid Destination Address" },
|
||||
{ ESME_RINVMSGID, "Message ID is invalid" },
|
||||
{ ESME_RBINDFAIL, "Bind failed" },
|
||||
{ ESME_RINVPASWD, "Invalid Password" },
|
||||
{ ESME_RINVSYSID, "Invalid System ID" },
|
||||
{ ESME_RCANCELFAIL, "Cancel SM Failed" },
|
||||
{ ESME_RREPLACEFAIL, "Replace SM Failed" },
|
||||
{ ESME_RMSGQFUL, "Message Queue Full" },
|
||||
{ ESME_RINVSERTYP, "Invalid Service Type" },
|
||||
{ ESME_RINVNUMDESTS, "Invalid number of destinations" },
|
||||
{ ESME_RINVDLNAME, "Invalid Distribution List name" },
|
||||
{ ESME_RINVDESTFLAG, "Destination flag is invalid" },
|
||||
{ ESME_RINVSUBREP, "Invalid submit with replace request" },
|
||||
{ ESME_RINVESMCLASS, "Invalid esm_class field data" },
|
||||
{ ESME_RCNTSUBDL, "Cannot Submit to Distribution List" },
|
||||
{ ESME_RSUBMITFAIL, "submit_sm or submit_multi failed" },
|
||||
{ ESME_RINVSRCTON, "Invalid Source address TON" },
|
||||
{ ESME_RINVSRCNPI, "Invalid Sourec address NPI" },
|
||||
{ ESME_RINVDSTTON, "Invalid Destination address TON" },
|
||||
{ ESME_RINVDSTNPI, "Invalid Desetination address NPI" },
|
||||
{ ESME_RINVSYSTYP, "Invalid system_type field" },
|
||||
{ ESME_RINVREPFLAG, "Invalid replace_if_present field" },
|
||||
{ ESME_RINVNUMMSGS, "Invalid number of messages" },
|
||||
{ ESME_RTHROTTLED, "Throttling error (ESME has exceeded message limits)" },
|
||||
{ ESME_RINVSCHED, "Invalid Scheduled Delivery Time" },
|
||||
{ ESME_RINVEXPIRY, "Invalid message validity period (Expiry time)" },
|
||||
{ ESME_RINVDFTMSGID, "Predefined Message Invalid or Not Found" },
|
||||
{ ESME_RX_T_APPN, "ESME Receiver Temporary App Error Code" },
|
||||
{ ESME_RX_P_APPN, "ESME Receiver Permanent App Error Code" },
|
||||
{ ESME_RX_R_APPN, "ESME Receiver Reject Message Error Code" },
|
||||
{ ESME_RQUERYFAIL, "query_sm request failed" },
|
||||
{ ESME_RINVOPTPARSTREAM,"Error in the optional part of the PDU Body" },
|
||||
{ ESME_ROPTPARNOTALLWD, "Optional Parameter not allowed" },
|
||||
{ ESME_RINVPARLEN, "Invalid Parameter Length" },
|
||||
{ ESME_RMISSINGOPTPARAM,"Expected Optional Parameter missing" },
|
||||
{ ESME_RINVOPTPARAMVAL, "Invalid Optional Parameter Value" },
|
||||
{ ESME_RDELIVERYFAILURE,"Delivery Failure (used for data_sm_resp)" },
|
||||
{ ESME_RUNKNOWNERR, "Unknown Error" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
/*! \brief compare if two SMPP addresses are equal */
|
||||
int smpp_addr_eq(const struct osmo_smpp_addr *a,
|
||||
const struct osmo_smpp_addr *b)
|
||||
{
|
||||
if (a->ton == b->ton &&
|
||||
a->npi == b->npi &&
|
||||
!strcmp(a->addr, b->addr))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
|
||||
const char *sys_id)
|
||||
{
|
||||
@@ -80,28 +145,91 @@ struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id)
|
||||
|
||||
acl->smsc = smsc;
|
||||
strcpy(acl->system_id, sys_id);
|
||||
INIT_LLIST_HEAD(&acl->route_list);
|
||||
|
||||
llist_add(&acl->list, &smsc->acl_list);
|
||||
llist_add_tail(&acl->list, &smsc->acl_list);
|
||||
|
||||
return acl;
|
||||
}
|
||||
|
||||
void smpp_acl_delete(struct osmo_smpp_acl *acl)
|
||||
{
|
||||
struct osmo_esme *esme, *e2;
|
||||
struct smsc *smsc = acl->smsc;
|
||||
struct osmo_smpp_route *r, *r2;
|
||||
|
||||
llist_del(&acl->list);
|
||||
|
||||
llist_for_each_entry_safe(esme, e2, &smsc->esme_list, list) {
|
||||
if (!strcmp(acl->system_id, esme->system_id)) {
|
||||
/* FIXME: drop connection */
|
||||
}
|
||||
/* kill any active ESMEs */
|
||||
if (acl->esme) {
|
||||
struct osmo_esme *esme = acl->esme;
|
||||
osmo_fd_unregister(&esme->wqueue.bfd);
|
||||
close(esme->wqueue.bfd.fd);
|
||||
esme->wqueue.bfd.fd = -1;
|
||||
esme->acl = NULL;
|
||||
smpp_esme_put(esme);
|
||||
}
|
||||
|
||||
/* delete all routes for this ACL */
|
||||
llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
|
||||
llist_del(&r->list);
|
||||
llist_del(&r->global_list);
|
||||
talloc_free(r);
|
||||
}
|
||||
|
||||
talloc_free(acl);
|
||||
}
|
||||
|
||||
static struct osmo_smpp_route *route_alloc(struct osmo_smpp_acl *acl)
|
||||
{
|
||||
struct osmo_smpp_route *r;
|
||||
|
||||
r = talloc_zero(acl, struct osmo_smpp_route);
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
llist_add_tail(&r->list, &acl->route_list);
|
||||
llist_add_tail(&r->global_list, &acl->smsc->route_list);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
|
||||
const struct osmo_smpp_addr *pfx)
|
||||
{
|
||||
struct osmo_smpp_route *r;
|
||||
|
||||
llist_for_each_entry(r, &acl->route_list, list) {
|
||||
if (r->type == SMPP_ROUTE_PREFIX &&
|
||||
smpp_addr_eq(&r->u.prefix, pfx))
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
r = route_alloc(acl);
|
||||
if (!r)
|
||||
return -ENOMEM;
|
||||
r->type = SMPP_ROUTE_PREFIX;
|
||||
r->acl = acl;
|
||||
memcpy(&r->u.prefix, pfx, sizeof(r->u.prefix));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
|
||||
const struct osmo_smpp_addr *pfx)
|
||||
{
|
||||
struct osmo_smpp_route *r, *r2;
|
||||
|
||||
llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
|
||||
if (r->type == SMPP_ROUTE_PREFIX &&
|
||||
smpp_addr_eq(&r->u.prefix, pfx)) {
|
||||
llist_del(&r->list);
|
||||
talloc_free(r);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief increaes the use/reference count */
|
||||
void smpp_esme_get(struct osmo_esme *esme)
|
||||
@@ -120,6 +248,15 @@ static void esme_destroy(struct osmo_esme *esme)
|
||||
talloc_free(esme);
|
||||
}
|
||||
|
||||
static uint32_t esme_inc_seq_nr(struct osmo_esme *esme)
|
||||
{
|
||||
esme->own_seq_nr++;
|
||||
if (esme->own_seq_nr > 0x7fffffff)
|
||||
esme->own_seq_nr = 1;
|
||||
|
||||
return esme->own_seq_nr;
|
||||
}
|
||||
|
||||
/*! \brief decrease the use/reference count, free if it is 0 */
|
||||
void smpp_esme_put(struct osmo_esme *esme)
|
||||
{
|
||||
@@ -140,6 +277,62 @@ esme_by_system_id(const struct smsc *smsc, char *system_id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! \brief try to find a SMPP route (ESME) for given destination */
|
||||
struct osmo_esme *
|
||||
smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest)
|
||||
{
|
||||
struct osmo_smpp_route *r;
|
||||
struct osmo_smpp_acl *acl = NULL;
|
||||
|
||||
DEBUGP(DSMPP, "Looking up route for (%u/%u/%s)\n",
|
||||
dest->ton, dest->npi, dest->addr);
|
||||
|
||||
/* search for a specific route */
|
||||
llist_for_each_entry(r, &smsc->route_list, global_list) {
|
||||
switch (r->type) {
|
||||
case SMPP_ROUTE_PREFIX:
|
||||
DEBUGP(DSMPP, "Checking prefix route (%u/%u/%s)->%s\n",
|
||||
r->u.prefix.ton, r->u.prefix.npi, r->u.prefix.addr,
|
||||
r->acl->system_id);
|
||||
if (r->u.prefix.ton == dest->ton &&
|
||||
r->u.prefix.npi == dest->npi &&
|
||||
!strncmp(r->u.prefix.addr, dest->addr,
|
||||
strlen(r->u.prefix.addr))) {
|
||||
DEBUGP(DSMPP, "Found prefix route ACL\n");
|
||||
acl = r->acl;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (acl)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!acl) {
|
||||
/* check for default route */
|
||||
if (smsc->def_route) {
|
||||
DEBUGP(DSMPP, "Using existing default route\n");
|
||||
acl = smsc->def_route;
|
||||
}
|
||||
}
|
||||
|
||||
if (acl && acl->esme) {
|
||||
struct osmo_esme *esme;
|
||||
DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n");
|
||||
esme = acl->esme;
|
||||
if (esme->bind_flags & ESME_BIND_RX)
|
||||
return esme;
|
||||
else
|
||||
LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, "
|
||||
"but not bound for Rx, discarding MO SMS\n",
|
||||
esme->system_id);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief initialize the libsmpp34 data structure for a response */
|
||||
#define INIT_RESP(type, resp, req) { \
|
||||
@@ -223,12 +416,49 @@ static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _process_bind(struct osmo_esme *esme, uint8_t if_version,
|
||||
uint32_t bind_flags, const char *sys_id,
|
||||
const char *passwd)
|
||||
{
|
||||
struct osmo_smpp_acl *acl;
|
||||
|
||||
if (if_version != SMPP_VERSION)
|
||||
return ESME_RSYSERR;
|
||||
|
||||
if (esme->bind_flags)
|
||||
return ESME_RALYBND;
|
||||
|
||||
esme->smpp_version = if_version;
|
||||
snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id);
|
||||
|
||||
acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
|
||||
if (!esme->smsc->accept_all) {
|
||||
if (!acl) {
|
||||
/* This system is unknown */
|
||||
return ESME_RINVSYSID;
|
||||
} else {
|
||||
if (strlen(acl->passwd) &&
|
||||
strcmp(acl->passwd, passwd)) {
|
||||
return ESME_RINVPASWD;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (acl) {
|
||||
esme->acl = acl;
|
||||
acl->esme = esme;
|
||||
}
|
||||
|
||||
esme->bind_flags = bind_flags;
|
||||
|
||||
return ESME_ROK;
|
||||
}
|
||||
|
||||
|
||||
/*! \brief handle an incoming SMPP BIND RECEIVER */
|
||||
static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg)
|
||||
{
|
||||
struct bind_receiver_t bind;
|
||||
struct bind_receiver_resp_t bind_r;
|
||||
struct osmo_smpp_acl *acl;
|
||||
int rc;
|
||||
|
||||
SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg),
|
||||
@@ -244,41 +474,11 @@ static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg)
|
||||
LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n",
|
||||
bind.system_id, bind.interface_version);
|
||||
|
||||
if (bind.interface_version != SMPP_VERSION) {
|
||||
bind_r.command_status = ESME_RSYSERR;
|
||||
goto err;
|
||||
}
|
||||
rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX,
|
||||
(const char *)bind.system_id, (const char *)bind.password);
|
||||
bind_r.command_status = rc;
|
||||
|
||||
if (esme->bind_flags) {
|
||||
bind_r.command_status = ESME_RALYBND;
|
||||
goto err;
|
||||
}
|
||||
|
||||
esme->smpp_version = bind.interface_version;
|
||||
snprintf(esme->system_id, sizeof(esme->system_id), "%s",
|
||||
bind.system_id);
|
||||
|
||||
acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
|
||||
if (!esme->smsc->accept_all) {
|
||||
if (!acl) {
|
||||
/* This system is unknown */
|
||||
bind_r.command_status = ESME_RINVSYSID;
|
||||
goto err;
|
||||
} else {
|
||||
if (strlen(acl->passwd) &&
|
||||
strcmp(acl->passwd, (char *)bind.password)) {
|
||||
bind_r.command_status = ESME_RINVPASWD;
|
||||
goto err;
|
||||
}
|
||||
esme->acl = acl;
|
||||
if (acl->default_route)
|
||||
esme->smsc->def_route = esme;
|
||||
}
|
||||
}
|
||||
|
||||
esme->bind_flags = ESME_BIND_RX;
|
||||
err:
|
||||
return 0;
|
||||
return PACK_AND_SEND(esme, &bind_r);
|
||||
}
|
||||
|
||||
/*! \brief handle an incoming SMPP BIND TRANSMITTER */
|
||||
@@ -286,7 +486,6 @@ static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
|
||||
{
|
||||
struct bind_transmitter_t bind;
|
||||
struct bind_transmitter_resp_t bind_r;
|
||||
struct osmo_smpp_acl *acl;
|
||||
struct tlv_t tlv;
|
||||
int rc;
|
||||
|
||||
@@ -303,36 +502,9 @@ static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
|
||||
LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n",
|
||||
bind.system_id, bind.interface_version);
|
||||
|
||||
if (bind.interface_version != SMPP_VERSION) {
|
||||
bind_r.command_status = ESME_RSYSERR;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (esme->bind_flags) {
|
||||
bind_r.command_status = ESME_RALYBND;
|
||||
goto err;
|
||||
}
|
||||
|
||||
esme->smpp_version = bind.interface_version;
|
||||
snprintf(esme->system_id, sizeof(esme->system_id), "%s", bind.system_id);
|
||||
|
||||
acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
|
||||
if (!esme->smsc->accept_all) {
|
||||
if (!acl) {
|
||||
/* This system is unknown */
|
||||
bind_r.command_status = ESME_RINVSYSID;
|
||||
goto err;
|
||||
} else {
|
||||
if (strlen(acl->passwd) &&
|
||||
strcmp(acl->passwd, (char *)bind.password)) {
|
||||
bind_r.command_status = ESME_RINVPASWD;
|
||||
goto err;
|
||||
}
|
||||
esme->acl = acl;
|
||||
}
|
||||
}
|
||||
|
||||
esme->bind_flags = ESME_BIND_TX;
|
||||
rc = _process_bind(esme, bind.interface_version, ESME_BIND_TX,
|
||||
(const char *)bind.system_id, (const char *)bind.password);
|
||||
bind_r.command_status = rc;
|
||||
|
||||
/* build response */
|
||||
snprintf((char *)bind_r.system_id, sizeof(bind_r.system_id), "%s",
|
||||
@@ -344,7 +516,6 @@ static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
|
||||
tlv.value.val16 = esme->smpp_version;
|
||||
build_tlv(&bind_r.tlv, &tlv);
|
||||
|
||||
err:
|
||||
return PACK_AND_SEND(esme, &bind_r);
|
||||
}
|
||||
|
||||
@@ -353,7 +524,6 @@ static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg)
|
||||
{
|
||||
struct bind_transceiver_t bind;
|
||||
struct bind_transceiver_resp_t bind_r;
|
||||
struct osmo_smpp_acl *acl;
|
||||
int rc;
|
||||
|
||||
SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg),
|
||||
@@ -369,41 +539,11 @@ static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg)
|
||||
LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n",
|
||||
bind.system_id, bind.interface_version);
|
||||
|
||||
if (bind.interface_version != SMPP_VERSION) {
|
||||
bind_r.command_status = ESME_RSYSERR;
|
||||
goto err;
|
||||
}
|
||||
rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX|ESME_BIND_TX,
|
||||
(const char *)bind.system_id, (const char *)bind.password);
|
||||
bind_r.command_status = rc;
|
||||
|
||||
if (esme->bind_flags) {
|
||||
bind_r.command_status = ESME_RALYBND;
|
||||
goto err;
|
||||
}
|
||||
|
||||
esme->smpp_version = bind.interface_version;
|
||||
snprintf(esme->system_id, sizeof(esme->system_id), "%s", bind.system_id);
|
||||
|
||||
acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
|
||||
if (!esme->smsc->accept_all) {
|
||||
if (!acl) {
|
||||
/* This system is unknown */
|
||||
bind_r.command_status = ESME_RINVSYSID;
|
||||
goto err;
|
||||
} else {
|
||||
if (strlen(acl->passwd) &&
|
||||
strcmp(acl->passwd, (char *)bind.password)) {
|
||||
bind_r.command_status = ESME_RINVPASWD;
|
||||
goto err;
|
||||
}
|
||||
esme->acl = acl;
|
||||
if (acl->default_route)
|
||||
esme->smsc->def_route = esme;
|
||||
}
|
||||
}
|
||||
|
||||
esme->bind_flags |= ESME_BIND_TX | ESME_BIND_RX;
|
||||
|
||||
err:
|
||||
return 0;
|
||||
return PACK_AND_SEND(esme, &bind_r);
|
||||
}
|
||||
|
||||
/*! \brief handle an incoming SMPP UNBIND */
|
||||
@@ -431,8 +571,6 @@ static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg)
|
||||
}
|
||||
|
||||
esme->bind_flags = 0;
|
||||
if (esme->smsc->def_route == esme)
|
||||
esme->smsc->def_route = NULL;
|
||||
err:
|
||||
return PACK_AND_SEND(esme, &unbind_r);
|
||||
}
|
||||
@@ -495,7 +633,7 @@ int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
|
||||
alert.command_length = 0;
|
||||
alert.command_id = ALERT_NOTIFICATION;
|
||||
alert.command_status = ESME_ROK;
|
||||
alert.sequence_number = esme->own_seq_nr++;
|
||||
alert.sequence_number = esme_inc_seq_nr(esme);
|
||||
alert.source_addr_ton = ton;
|
||||
alert.source_addr_npi = npi;
|
||||
snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr);
|
||||
@@ -513,6 +651,36 @@ int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
|
||||
return PACK_AND_SEND(esme, &alert);
|
||||
}
|
||||
|
||||
/* \brief send a DELIVER-SM message to given ESME */
|
||||
int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver)
|
||||
{
|
||||
deliver->sequence_number = esme_inc_seq_nr(esme);
|
||||
|
||||
return PACK_AND_SEND(esme, deliver);
|
||||
}
|
||||
|
||||
/*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */
|
||||
static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg)
|
||||
{
|
||||
struct deliver_sm_resp_t deliver_r;
|
||||
int rc;
|
||||
|
||||
memset(&deliver_r, 0, sizeof(deliver_r));
|
||||
SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg),
|
||||
msgb_length(msg));
|
||||
if (rc < 0) {
|
||||
LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
|
||||
esme->system_id, smpp34_strerror);
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n",
|
||||
esme->system_id, get_value_string(smpp_status_strs,
|
||||
deliver_r.command_status));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! \brief handle an incoming SMPP SUBMIT-SM */
|
||||
static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg)
|
||||
{
|
||||
@@ -580,6 +748,9 @@ static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg)
|
||||
case SUBMIT_SM:
|
||||
rc = smpp_handle_submit(esme, msg);
|
||||
break;
|
||||
case DELIVER_SM_RESP:
|
||||
rc = smpp_handle_deliver_resp(esme, msg);
|
||||
break;
|
||||
case DELIVER_SM:
|
||||
break;
|
||||
case DATA_SM:
|
||||
@@ -698,6 +869,7 @@ static int link_accept_cb(struct smsc *smsc, int fd,
|
||||
|
||||
smpp_esme_get(esme);
|
||||
esme->own_seq_nr = rand();
|
||||
esme_inc_seq_nr(esme);
|
||||
esme->smsc = smsc;
|
||||
osmo_wqueue_init(&esme->wqueue, 10);
|
||||
esme->wqueue.bfd.fd = fd;
|
||||
@@ -746,6 +918,7 @@ int smpp_smsc_init(struct smsc *smsc, uint16_t port)
|
||||
if (smsc->listen_ofd.fd <= 0) {
|
||||
INIT_LLIST_HEAD(&smsc->esme_list);
|
||||
INIT_LLIST_HEAD(&smsc->acl_list);
|
||||
INIT_LLIST_HEAD(&smsc->route_list);
|
||||
smsc->listen_ofd.data = smsc;
|
||||
smsc->listen_ofd.cb = smsc_fd_cb;
|
||||
} else {
|
||||
|
@@ -22,6 +22,12 @@ enum esme_read_state {
|
||||
|
||||
struct osmo_smpp_acl;
|
||||
|
||||
struct osmo_smpp_addr {
|
||||
uint8_t ton;
|
||||
uint8_t npi;
|
||||
char addr[21+1];
|
||||
};
|
||||
|
||||
struct osmo_esme {
|
||||
struct llist_head list;
|
||||
struct smsc *smsc;
|
||||
@@ -48,29 +54,54 @@ struct osmo_esme {
|
||||
struct osmo_smpp_acl {
|
||||
struct llist_head list;
|
||||
struct smsc *smsc;
|
||||
struct osmo_esme *esme;
|
||||
char *description;
|
||||
char system_id[SMPP_SYS_ID_LEN+1];
|
||||
char passwd[SMPP_PASSWD_LEN+1];
|
||||
int default_route;
|
||||
int deliver_src_imsi;
|
||||
struct llist_head route_list;
|
||||
};
|
||||
|
||||
enum osmo_smpp_rtype {
|
||||
SMPP_ROUTE_NONE,
|
||||
SMPP_ROUTE_PREFIX,
|
||||
};
|
||||
|
||||
struct osmo_smpp_route {
|
||||
struct llist_head list; /*!< in acl.route_list */
|
||||
struct llist_head global_list; /*!< in smsc->route_list */
|
||||
struct osmo_smpp_acl *acl;
|
||||
enum osmo_smpp_rtype type;
|
||||
union {
|
||||
struct osmo_smpp_addr prefix;
|
||||
} u;
|
||||
};
|
||||
|
||||
|
||||
struct smsc {
|
||||
struct osmo_fd listen_ofd;
|
||||
struct llist_head esme_list;
|
||||
struct llist_head acl_list;
|
||||
struct llist_head route_list;
|
||||
uint16_t listen_port;
|
||||
char system_id[SMPP_SYS_ID_LEN+1];
|
||||
int accept_all;
|
||||
struct osmo_esme *def_route;
|
||||
struct osmo_smpp_acl *def_route;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
int smpp_addr_eq(const struct osmo_smpp_addr *a,
|
||||
const struct osmo_smpp_addr *b);
|
||||
|
||||
int smpp_smsc_init(struct smsc *smsc, uint16_t port);
|
||||
|
||||
void smpp_esme_get(struct osmo_esme *esme);
|
||||
void smpp_esme_put(struct osmo_esme *esme);
|
||||
|
||||
struct osmo_esme *
|
||||
smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest);
|
||||
|
||||
struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id);
|
||||
struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
|
||||
const char *sys_id);
|
||||
@@ -82,7 +113,13 @@ int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
|
||||
int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
|
||||
const char *addr, uint8_t avail_status);
|
||||
|
||||
int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver);
|
||||
|
||||
int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
|
||||
struct submit_sm_resp_t *submit_r);
|
||||
|
||||
int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
|
||||
const struct osmo_smpp_addr *pfx);
|
||||
int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
|
||||
const struct osmo_smpp_addr *pfx);
|
||||
#endif
|
||||
|
@@ -18,7 +18,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
@@ -195,18 +197,117 @@ DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_esme_route, cfg_esme_route_cmd,
|
||||
"route DESTINATION",
|
||||
"Configure a route for MO-SMS to be sent to this ESME\n"
|
||||
"Destination phone number")
|
||||
static int osmo_is_digits(const char *str)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < strlen(str); i++) {
|
||||
if (!isdigit(str[i]))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct value_string route_errstr[] = {
|
||||
{ -EEXIST, "Route already exists" },
|
||||
{ -ENODEV, "Route does not exist" },
|
||||
{ -ENOMEM, "No memory" },
|
||||
{ -EINVAL, "Invalid" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct value_string smpp_ton_str_short[] = {
|
||||
{ TON_Unknown, "unknown" },
|
||||
{ TON_International, "international" },
|
||||
{ TON_National, "national" },
|
||||
{ TON_Network_Specific, "network" },
|
||||
{ TON_Subscriber_Number,"subscriber" },
|
||||
{ TON_Alphanumeric, "alpha" },
|
||||
{ TON_Abbreviated, "abbrev" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct value_string smpp_npi_str_short[] = {
|
||||
{ NPI_Unknown, "unknown" },
|
||||
{ NPI_ISDN_E163_E164, "isdn" },
|
||||
{ NPI_Data_X121, "x121" },
|
||||
{ NPI_Telex_F69, "f69" },
|
||||
{ NPI_Land_Mobile_E212, "e212" },
|
||||
{ NPI_National, "national" },
|
||||
{ NPI_Private, "private" },
|
||||
{ NPI_ERMES, "ermes" },
|
||||
{ NPI_Internet_IP, "ip" },
|
||||
{ NPI_WAP_Client_Id, "wap" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
|
||||
#define SMPP_ROUTE_STR "Configure a route for MO-SMS to be sent to this ESME\n"
|
||||
#define SMPP_ROUTE_P_STR "Prefix-match route\n"
|
||||
#define SMPP_PREFIX_STR "Destination number prefix\n"
|
||||
|
||||
#define TON_CMD "(unknown|international|national|network|subscriber|alpha|abbrev)"
|
||||
#define NPI_CMD "(unknown|isdn|x121|f69|e212|national|private|ermes|ip|wap)"
|
||||
#define TON_STR "FIXME"
|
||||
#define NPI_STR "FIXME"
|
||||
|
||||
DEFUN(cfg_esme_route_pfx, cfg_esme_route_pfx_cmd,
|
||||
"route prefix " TON_CMD " " NPI_CMD " PREFIX",
|
||||
SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
|
||||
{
|
||||
struct osmo_smpp_acl *acl = vty->index;
|
||||
struct osmo_smpp_addr pfx;
|
||||
int rc;
|
||||
|
||||
/* FIXME: check if DESTINATION is all-digits */
|
||||
/* check if DESTINATION is all-digits */
|
||||
if (!osmo_is_digits(argv[2])) {
|
||||
vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
|
||||
pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
|
||||
snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
|
||||
|
||||
rc = smpp_route_pfx_add(acl, &pfx);
|
||||
if (rc < 0) {
|
||||
vty_out(vty, "%% error adding prefix route: %s%s",
|
||||
get_value_string(route_errstr, rc), VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN(cfg_esme_no_route_pfx, cfg_esme_no_route_pfx_cmd,
|
||||
"no route prefix " TON_CMD " " NPI_CMD " PREFIX",
|
||||
NO_STR SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
|
||||
{
|
||||
struct osmo_smpp_acl *acl = vty->index;
|
||||
struct osmo_smpp_addr pfx;
|
||||
int rc;
|
||||
|
||||
/* check if DESTINATION is all-digits */
|
||||
if (!osmo_is_digits(argv[2])) {
|
||||
vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
|
||||
pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
|
||||
snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
|
||||
|
||||
rc = smpp_route_pfx_del(acl, &pfx);
|
||||
if (rc < 0) {
|
||||
vty_out(vty, "%% error removing prefix route: %s%s",
|
||||
get_value_string(route_errstr, rc), VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
|
||||
DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd,
|
||||
"default-route",
|
||||
"Set this ESME as default-route for all SMS to unknown destinations")
|
||||
@@ -215,6 +316,9 @@ DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd,
|
||||
|
||||
acl->default_route = 1;
|
||||
|
||||
if (!acl->smsc->def_route)
|
||||
acl->smsc->def_route = acl;
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -228,7 +332,7 @@ DEFUN(cfg_no_esme_defaultroute, cfg_esme_no_defaultroute_cmd,
|
||||
|
||||
/* remove currently active default route, if it was created by
|
||||
* this ACL */
|
||||
if (acl->smsc->def_route && acl->smsc->def_route->acl == acl)
|
||||
if (acl->smsc->def_route && acl->smsc->def_route == acl)
|
||||
acl->smsc->def_route = NULL;
|
||||
|
||||
return CMD_SUCCESS;
|
||||
@@ -244,8 +348,11 @@ static void dump_one_esme(struct vty *vty, struct osmo_esme *esme)
|
||||
host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV);
|
||||
|
||||
vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s",
|
||||
esme->system_id, esme->acl->passwd, esme->smpp_version, VTY_NEWLINE);
|
||||
esme->system_id, esme->acl ? esme->acl->passwd : "",
|
||||
esme->smpp_version, VTY_NEWLINE);
|
||||
vty_out(vty, " Connected from: %s:%s%s", host, serv, VTY_NEWLINE);
|
||||
if (esme->smsc->def_route == esme->acl)
|
||||
vty_out(vty, " Is current default route%s", VTY_NEWLINE);
|
||||
}
|
||||
|
||||
DEFUN(show_esme, show_esme_cmd,
|
||||
@@ -261,13 +368,35 @@ DEFUN(show_esme, show_esme_cmd,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
static void write_esme_route_single(struct vty *vty, struct osmo_smpp_route *r)
|
||||
{
|
||||
switch (r->type) {
|
||||
case SMPP_ROUTE_PREFIX:
|
||||
vty_out(vty, " route prefix %s ",
|
||||
get_value_string(smpp_ton_str_short, r->u.prefix.ton));
|
||||
vty_out(vty, "%s %s%s",
|
||||
get_value_string(smpp_npi_str_short, r->u.prefix.npi),
|
||||
r->u.prefix.addr, VTY_NEWLINE);
|
||||
break;
|
||||
case SMPP_ROUTE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void config_write_esme_single(struct vty *vty, struct osmo_smpp_acl *acl)
|
||||
{
|
||||
struct osmo_smpp_route *r;
|
||||
|
||||
vty_out(vty, " esme %s%s", acl->system_id, VTY_NEWLINE);
|
||||
if (strlen(acl->passwd))
|
||||
vty_out(vty, " password %s%s", acl->passwd, VTY_NEWLINE);
|
||||
if (acl->default_route)
|
||||
vty_out(vty, " default-route%s", VTY_NEWLINE);
|
||||
if (acl->deliver_src_imsi)
|
||||
vty_out(vty, " deliver-src-imsi%s", VTY_NEWLINE);
|
||||
|
||||
llist_for_each_entry(r, &acl->route_list, list)
|
||||
write_esme_route_single(vty, r);
|
||||
}
|
||||
|
||||
static int config_write_esme(struct vty *v)
|
||||
@@ -297,7 +426,8 @@ int smpp_vty_init(void)
|
||||
install_default(SMPP_ESME_NODE);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_passwd_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_no_passwd_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_route_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_route_pfx_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd);
|
||||
install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd);
|
||||
install_element(SMPP_ESME_NODE, &ournode_exit_cmd);
|
||||
|
Reference in New Issue
Block a user