evapi_dispatch.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /**
  2. * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.com)
  3. *
  4. * This file is part of Kamailio, a free SIP server.
  5. *
  6. * This file is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version
  10. *
  11. *
  12. * This file is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. */
  22. #include <stdio.h>
  23. #include <unistd.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <sys/socket.h>
  27. #include <sys/types.h>
  28. #include <netinet/in.h>
  29. #include <arpa/inet.h>
  30. #include <fcntl.h>
  31. #include <ev.h>
  32. #include "../../sr_module.h"
  33. #include "../../dprint.h"
  34. #include "../../ut.h"
  35. #include "../../lib/kcore/faked_msg.h"
  36. #include "evapi_dispatch.h"
  37. static int _evapi_notify_sockets[2];
  38. static int _evapi_netstring_format = 1;
  39. #define EVAPI_IPADDR_SIZE 64
  40. typedef struct _evapi_client {
  41. int connected;
  42. int sock;
  43. unsigned short af;
  44. unsigned short src_port;
  45. char src_addr[EVAPI_IPADDR_SIZE];
  46. } evapi_client_t;
  47. typedef struct _evapi_env {
  48. int eset;
  49. int conidx;
  50. str msg;
  51. } evapi_env_t;
  52. #define EVAPI_MAX_CLIENTS 8
  53. static evapi_client_t _evapi_clients[EVAPI_MAX_CLIENTS];
  54. typedef struct _evapi_evroutes {
  55. int con_new;
  56. int con_closed;
  57. int msg_received;
  58. } evapi_evroutes_t;
  59. static evapi_evroutes_t _evapi_rts;
  60. /**
  61. *
  62. */
  63. void evapi_env_reset(evapi_env_t *evenv)
  64. {
  65. if(evenv==0)
  66. return;
  67. memset(evenv, 0, sizeof(evapi_env_t));
  68. evenv->conidx = -1;
  69. }
  70. /**
  71. *
  72. */
  73. void evapi_init_environment(int dformat)
  74. {
  75. memset(&_evapi_rts, 0, sizeof(evapi_evroutes_t));
  76. _evapi_rts.con_new = route_get(&event_rt, "evapi:connection-new");
  77. if (_evapi_rts.con_new < 0 || event_rt.rlist[_evapi_rts.con_new] == NULL)
  78. _evapi_rts.con_new = -1;
  79. _evapi_rts.con_closed = route_get(&event_rt, "evapi:connection-closed");
  80. if (_evapi_rts.con_closed < 0 || event_rt.rlist[_evapi_rts.con_closed] == NULL)
  81. _evapi_rts.con_closed = -1;
  82. _evapi_rts.msg_received = route_get(&event_rt, "evapi:message-received");
  83. if (_evapi_rts.msg_received < 0 || event_rt.rlist[_evapi_rts.msg_received] == NULL)
  84. _evapi_rts.msg_received = -1;
  85. _evapi_netstring_format = dformat;
  86. }
  87. /**
  88. *
  89. */
  90. int evapi_run_cfg_route(evapi_env_t *evenv, int rt)
  91. {
  92. int backup_rt;
  93. struct run_act_ctx ctx;
  94. sip_msg_t *fmsg;
  95. sip_msg_t tmsg;
  96. if(evenv==0 || evenv->eset==0) {
  97. LM_ERR("evapi env not set\n");
  98. return -1;
  99. }
  100. if(rt<0)
  101. return 0;
  102. fmsg = faked_msg_next();
  103. memcpy(&tmsg, fmsg, sizeof(sip_msg_t));
  104. fmsg = &tmsg;
  105. evapi_set_msg_env(fmsg, evenv);
  106. backup_rt = get_route_type();
  107. set_route_type(EVENT_ROUTE);
  108. init_run_actions_ctx(&ctx);
  109. run_top_route(event_rt.rlist[rt], fmsg, 0);
  110. set_route_type(backup_rt);
  111. evapi_set_msg_env(fmsg, NULL);
  112. return 0;
  113. }
  114. /**
  115. *
  116. */
  117. int evapi_close_connection(int cidx)
  118. {
  119. if(cidx<0 || cidx>=EVAPI_MAX_CLIENTS)
  120. return -1;
  121. if(_evapi_clients[cidx].connected==1
  122. && _evapi_clients[cidx].sock > 0) {
  123. close(_evapi_clients[cidx].sock);
  124. _evapi_clients[cidx].connected = 0;
  125. _evapi_clients[cidx].sock = 0;
  126. return 0;
  127. }
  128. return -2;
  129. }
  130. /**
  131. *
  132. */
  133. int evapi_cfg_close(sip_msg_t *msg)
  134. {
  135. evapi_env_t *evenv;
  136. if(msg==NULL)
  137. return -1;
  138. evenv = (evapi_env_t*)msg->date;
  139. if(evenv==NULL || evenv->conidx<0 || evenv->conidx>=EVAPI_MAX_CLIENTS)
  140. return -1;
  141. return evapi_close_connection(evenv->conidx);
  142. }
  143. /**
  144. *
  145. */
  146. int evapi_init_notify_sockets(void)
  147. {
  148. if (socketpair(PF_UNIX, SOCK_STREAM, 0, _evapi_notify_sockets) < 0) {
  149. LM_ERR("opening notify stream socket pair\n");
  150. return -1;
  151. }
  152. LM_DBG("inter-process event notification sockets initialized\n");
  153. return 0;
  154. }
  155. /**
  156. *
  157. */
  158. void evapi_close_notify_sockets_child(void)
  159. {
  160. LM_DBG("closing the notification socket used by children\n");
  161. close(_evapi_notify_sockets[1]);
  162. }
  163. /**
  164. *
  165. */
  166. void evapi_close_notify_sockets_parent(void)
  167. {
  168. LM_DBG("closing the notification socket used by parent\n");
  169. close(_evapi_notify_sockets[0]);
  170. }
  171. /**
  172. *
  173. */
  174. int evapi_dispatch_notify(char *obuf, int olen)
  175. {
  176. int i;
  177. int n;
  178. int wlen;
  179. n = 0;
  180. for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
  181. if(_evapi_clients[i].connected==1 && _evapi_clients[i].sock>0) {
  182. wlen = write(_evapi_clients[i].sock, obuf, olen);
  183. if(wlen!=olen) {
  184. LM_DBG("failed to write all packet (%d out of %d) on socket %d index [%d]\n",
  185. wlen, olen, _evapi_clients[i].sock, i);
  186. }
  187. n++;
  188. }
  189. }
  190. return n;
  191. }
  192. /**
  193. *
  194. */
  195. void evapi_recv_client(struct ev_loop *loop, struct ev_io *watcher, int revents)
  196. {
  197. #define CLIENT_BUFFER_SIZE 4096
  198. char rbuffer[CLIENT_BUFFER_SIZE];
  199. ssize_t rlen;
  200. int i, k;
  201. evapi_env_t evenv;
  202. str frame;
  203. if(EV_ERROR & revents) {
  204. perror("received invalid event\n");
  205. return;
  206. }
  207. /* read message from client */
  208. rlen = recv(watcher->fd, rbuffer, CLIENT_BUFFER_SIZE-1, 0);
  209. if(rlen < 0) {
  210. LM_ERR("cannot read the client message\n");
  211. return;
  212. }
  213. for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
  214. if(_evapi_clients[i].connected==1 && _evapi_clients[i].sock==watcher->fd) {
  215. break;
  216. }
  217. }
  218. if(i==EVAPI_MAX_CLIENTS) {
  219. LM_ERR("cannot lookup client socket %d\n", watcher->fd);
  220. return;
  221. }
  222. evapi_env_reset(&evenv);
  223. if(rlen == 0) {
  224. /* client is gone */
  225. evenv.eset = 1;
  226. evenv.conidx = i;
  227. evapi_run_cfg_route(&evenv, _evapi_rts.con_closed);
  228. _evapi_clients[i].connected = 0;
  229. _evapi_clients[i].sock = 0;
  230. ev_io_stop(loop, watcher);
  231. free(watcher);
  232. LM_INFO("client closing connection - pos [%d] addr [%s:%d]\n",
  233. i, _evapi_clients[i].src_addr, _evapi_clients[i].src_port);
  234. return;
  235. }
  236. rbuffer[rlen] = '\0';
  237. LM_NOTICE("{%d} [%s:%d] - received [%.*s]\n",
  238. i, _evapi_clients[i].src_addr, _evapi_clients[i].src_port,
  239. (int)rlen, rbuffer);
  240. evenv.conidx = i;
  241. evenv.eset = 1;
  242. if(_evapi_netstring_format) {
  243. /* netstring decapsulation */
  244. k = 0;
  245. while(k<rlen) {
  246. frame.len = 0;
  247. while(k<rlen) {
  248. if(rbuffer[k]==' ' || rbuffer[k]=='\t'
  249. || rbuffer[k]=='\r' || rbuffer[k]=='\n')
  250. k++;
  251. else break;
  252. }
  253. if(k==rlen) return;
  254. while(k<rlen) {
  255. if(rbuffer[k]>='0' && rbuffer[k]<='9') {
  256. frame.len = frame.len*10 + rbuffer[k] - '0';
  257. } else {
  258. if(rbuffer[k]==':')
  259. break;
  260. /* invalid character - discard the rest */
  261. return;
  262. }
  263. k++;
  264. }
  265. if(k==rlen || frame.len<=0) return;
  266. if(frame.len + k>=rlen) return;
  267. k++;
  268. frame.s = rbuffer + k;
  269. if(frame.s[frame.len]!=',') return;
  270. frame.s[frame.len] = '\0';
  271. k += frame.len ;
  272. evenv.msg.s = frame.s;
  273. evenv.msg.len = frame.len;
  274. evapi_run_cfg_route(&evenv, _evapi_rts.msg_received);
  275. k++;
  276. }
  277. } else {
  278. evenv.msg.s = rbuffer;
  279. evenv.msg.len = rlen;
  280. evapi_run_cfg_route(&evenv, _evapi_rts.msg_received);
  281. }
  282. }
  283. /**
  284. *
  285. */
  286. void evapi_accept_client(struct ev_loop *loop, struct ev_io *watcher, int revents)
  287. {
  288. struct sockaddr caddr;
  289. socklen_t clen = sizeof(caddr);
  290. int csock;
  291. struct ev_io *evapi_client;
  292. int i;
  293. evapi_env_t evenv;
  294. evapi_client = (struct ev_io*) malloc (sizeof(struct ev_io));
  295. if(evapi_client==NULL) {
  296. perror("no more memory\n");
  297. return;
  298. }
  299. if(EV_ERROR & revents) {
  300. perror("received invalid event\n");
  301. return;
  302. }
  303. /* accept new client connection */
  304. csock = accept(watcher->fd, (struct sockaddr *)&caddr, &clen);
  305. if (csock < 0) {
  306. LM_ERR("cannot accept the client\n");
  307. return;
  308. }
  309. for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
  310. if(_evapi_clients[i].connected==0) {
  311. if (caddr.sa_family == AF_INET) {
  312. _evapi_clients[i].src_port = ntohs(((struct sockaddr_in*)&caddr)->sin_port);
  313. if(inet_ntop(AF_INET, &((struct sockaddr_in*)&caddr)->sin_addr,
  314. _evapi_clients[i].src_addr,
  315. EVAPI_IPADDR_SIZE)==NULL) {
  316. LM_ERR("cannot convert ipv4 address\n");
  317. close(csock);
  318. return;
  319. }
  320. } else {
  321. _evapi_clients[i].src_port = ntohs(((struct sockaddr_in6*)&caddr)->sin6_port);
  322. if(inet_ntop(AF_INET6, &((struct sockaddr_in6*)&caddr)->sin6_addr,
  323. _evapi_clients[i].src_addr,
  324. EVAPI_IPADDR_SIZE)==NULL) {
  325. LM_ERR("cannot convert ipv6 address\n");
  326. close(csock);
  327. return;
  328. }
  329. }
  330. _evapi_clients[i].connected = 1;
  331. _evapi_clients[i].sock = csock;
  332. _evapi_clients[i].af = caddr.sa_family;
  333. break;
  334. }
  335. }
  336. if(i==EVAPI_MAX_CLIENTS) {
  337. LM_ERR("too many clients\n");
  338. close(csock);
  339. return;
  340. }
  341. LM_DBG("new connection - pos[%d] from: [%s:%d]\n", i,
  342. _evapi_clients[i].src_addr, _evapi_clients[i].src_port);
  343. evapi_env_reset(&evenv);
  344. evenv.conidx = i;
  345. evenv.eset = 1;
  346. evapi_run_cfg_route(&evenv, _evapi_rts.con_new);
  347. if(_evapi_clients[i].connected == 0)
  348. return;
  349. /* start watcher to read messages from whatchers */
  350. ev_io_init(evapi_client, evapi_recv_client, csock, EV_READ);
  351. ev_io_start(loop, evapi_client);
  352. }
  353. /**
  354. *
  355. */
  356. void evapi_recv_notify(struct ev_loop *loop, struct ev_io *watcher, int revents)
  357. {
  358. str *sbuf;
  359. int rlen;
  360. if(EV_ERROR & revents) {
  361. perror("received invalid event\n");
  362. return;
  363. }
  364. /* read message from client */
  365. rlen = read(watcher->fd, &sbuf, sizeof(str*));
  366. if(rlen != sizeof(str*)) {
  367. LM_ERR("cannot read the sip worker message\n");
  368. return;
  369. }
  370. LM_DBG("received [%p] [%.*s] (%d)\n", sbuf, sbuf->len, sbuf->s, sbuf->len);
  371. evapi_dispatch_notify(sbuf->s, sbuf->len);
  372. shm_free(sbuf);
  373. }
  374. /**
  375. *
  376. */
  377. int evapi_run_dispatcher(char *laddr, int lport)
  378. {
  379. int evapi_srv_sock;
  380. struct sockaddr_in evapi_srv_addr;
  381. struct ev_loop *loop;
  382. struct hostent *h = NULL;
  383. struct ev_io io_server;
  384. struct ev_io io_notify;
  385. LM_DBG("starting dispatcher processing\n");
  386. memset(_evapi_clients, 0, sizeof(evapi_client_t) * EVAPI_MAX_CLIENTS);
  387. loop = ev_default_loop(0);
  388. if(loop==NULL) {
  389. LM_ERR("cannot get libev loop\n");
  390. return -1;
  391. }
  392. h = gethostbyname(laddr);
  393. if (h == NULL || (h->h_addrtype != AF_INET && h->h_addrtype != AF_INET6)) {
  394. LM_ERR("cannot resolve local server address [%s]\n", laddr);
  395. return -1;
  396. }
  397. if(h->h_addrtype == AF_INET) {
  398. evapi_srv_sock = socket(PF_INET, SOCK_STREAM, 0);
  399. } else {
  400. evapi_srv_sock = socket(PF_INET6, SOCK_STREAM, 0);
  401. }
  402. if( evapi_srv_sock < 0 )
  403. {
  404. LM_ERR("cannot create server socket (family %d)\n", h->h_addrtype);
  405. return -1;
  406. }
  407. /* set non-blocking flag */
  408. fcntl(evapi_srv_sock, F_SETFL, fcntl(evapi_srv_sock, F_GETFL) | O_NONBLOCK);
  409. bzero(&evapi_srv_addr, sizeof(evapi_srv_addr));
  410. evapi_srv_addr.sin_family = h->h_addrtype;
  411. evapi_srv_addr.sin_port = htons((short)lport);
  412. evapi_srv_addr.sin_addr = *(struct in_addr*)h->h_addr;
  413. if (bind(evapi_srv_sock, (struct sockaddr*)&evapi_srv_addr,
  414. sizeof(evapi_srv_addr)) < 0) {
  415. LM_ERR("cannot bind to local address and port [%s:%d]\n", laddr, lport);
  416. close(evapi_srv_sock);
  417. return -1;
  418. }
  419. if (listen(evapi_srv_sock, 4) < 0) {
  420. LM_ERR("listen error\n");
  421. close(evapi_srv_sock);
  422. return -1;
  423. }
  424. ev_io_init(&io_server, evapi_accept_client, evapi_srv_sock, EV_READ);
  425. ev_io_start(loop, &io_server);
  426. ev_io_init(&io_notify, evapi_recv_notify, _evapi_notify_sockets[0], EV_READ);
  427. ev_io_start(loop, &io_notify);
  428. while(1) {
  429. ev_loop (loop, 0);
  430. }
  431. return 0;
  432. }
  433. /**
  434. *
  435. */
  436. int evapi_run_worker(int prank)
  437. {
  438. LM_DBG("started worker process: %d\n", prank);
  439. while(1) {
  440. sleep(3);
  441. }
  442. }
  443. /**
  444. *
  445. */
  446. int evapi_relay(str *evdata)
  447. {
  448. #define EVAPI_RELAY_FORMAT "%d:%.*s,"
  449. int len;
  450. int sbsize;
  451. str *sbuf;
  452. LM_DBG("relaying event data [%.*s]\n",
  453. evdata->len, evdata->s);
  454. sbsize = evdata->len;
  455. sbuf = (str*)shm_malloc(sizeof(str) + ((sbsize+32) * sizeof(char)));
  456. if(sbuf==NULL) {
  457. LM_ERR("no more shared memory\n");
  458. return -1;
  459. }
  460. sbuf->s = (char*)sbuf + sizeof(str);
  461. if(_evapi_netstring_format) {
  462. /* netstring encapsulation */
  463. sbuf->len = snprintf(sbuf->s, sbsize+32,
  464. EVAPI_RELAY_FORMAT,
  465. sbsize, evdata->len, evdata->s);
  466. } else {
  467. sbuf->len = snprintf(sbuf->s, sbsize+32,
  468. "%.*s",
  469. evdata->len, evdata->s);
  470. }
  471. if(sbuf->len<=0 || sbuf->len>sbsize+32) {
  472. shm_free(sbuf);
  473. LM_ERR("cannot serialize event\n");
  474. return -1;
  475. }
  476. len = write(_evapi_notify_sockets[1], &sbuf, sizeof(str*));
  477. if(len<=0) {
  478. LM_ERR("failed to pass the pointer to evapi dispatcher\n");
  479. return -1;
  480. }
  481. LM_DBG("sent [%p] [%.*s] (%d)\n", sbuf, sbuf->len, sbuf->s, sbuf->len);
  482. return 0;
  483. }
  484. #if 0
  485. /**
  486. *
  487. */
  488. int evapi_relay(str *event, str *data)
  489. {
  490. #define EVAPI_RELAY_FORMAT "%d:{\n \"event\":\"%.*s\",\n \"data\":%.*s\n},"
  491. int len;
  492. int sbsize;
  493. str *sbuf;
  494. LM_DBG("relaying event [%.*s] data [%.*s]\n",
  495. event->len, event->s, data->len, data->s);
  496. sbsize = sizeof(EVAPI_RELAY_FORMAT) + event->len + data->len - 13;
  497. sbuf = (str*)shm_malloc(sizeof(str) + ((sbsize+32) * sizeof(char)));
  498. if(sbuf==NULL) {
  499. LM_ERR("no more shared memory\n");
  500. return -1;
  501. }
  502. sbuf->s = (char*)sbuf + sizeof(str);
  503. sbuf->len = snprintf(sbuf->s, sbsize+32,
  504. EVAPI_RELAY_FORMAT,
  505. sbsize, event->len, event->s, data->len, data->s);
  506. if(sbuf->len<=0 || sbuf->len>sbsize+32) {
  507. shm_free(sbuf);
  508. LM_ERR("cannot serialize event\n");
  509. return -1;
  510. }
  511. len = write(_evapi_notify_sockets[1], &sbuf, sizeof(str*));
  512. if(len<=0) {
  513. LM_ERR("failed to pass the pointer to evapi dispatcher\n");
  514. return -1;
  515. }
  516. LM_DBG("sent [%p] [%.*s] (%d)\n", sbuf, sbuf->len, sbuf->s, sbuf->len);
  517. return 0;
  518. }
  519. #endif
  520. /**
  521. *
  522. */
  523. int pv_parse_evapi_name(pv_spec_t *sp, str *in)
  524. {
  525. if(sp==NULL || in==NULL || in->len<=0)
  526. return -1;
  527. switch(in->len)
  528. {
  529. case 3:
  530. if(strncmp(in->s, "msg", 3)==0)
  531. sp->pvp.pvn.u.isname.name.n = 1;
  532. else goto error;
  533. break;
  534. case 6:
  535. if(strncmp(in->s, "conidx", 6)==0)
  536. sp->pvp.pvn.u.isname.name.n = 0;
  537. else goto error;
  538. break;
  539. case 7:
  540. if(strncmp(in->s, "srcaddr", 7)==0)
  541. sp->pvp.pvn.u.isname.name.n = 2;
  542. else if(strncmp(in->s, "srcport", 7)==0)
  543. sp->pvp.pvn.u.isname.name.n = 3;
  544. else goto error;
  545. break;
  546. default:
  547. goto error;
  548. }
  549. sp->pvp.pvn.type = PV_NAME_INTSTR;
  550. sp->pvp.pvn.u.isname.type = 0;
  551. return 0;
  552. error:
  553. LM_ERR("unknown PV msrp name %.*s\n", in->len, in->s);
  554. return -1;
  555. }
  556. /**
  557. *
  558. */
  559. int pv_get_evapi(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
  560. {
  561. evapi_env_t *evenv;
  562. if(param==NULL || res==NULL)
  563. return -1;
  564. evenv = (evapi_env_t*)msg->date;
  565. if(evenv==NULL || evenv->conidx<0 || evenv->conidx>=EVAPI_MAX_CLIENTS)
  566. return pv_get_null(msg, param, res);
  567. if(_evapi_clients[evenv->conidx].connected==0
  568. && _evapi_clients[evenv->conidx].sock <= 0)
  569. return pv_get_null(msg, param, res);
  570. switch(param->pvn.u.isname.name.n)
  571. {
  572. case 0:
  573. return pv_get_sintval(msg, param, res, evenv->conidx);
  574. case 1:
  575. if(evenv->msg.s==NULL)
  576. return pv_get_null(msg, param, res);
  577. return pv_get_strval(msg, param, res, &evenv->msg);
  578. case 2:
  579. return pv_get_strzval(msg, param, res,
  580. _evapi_clients[evenv->conidx].src_addr);
  581. case 3:
  582. return pv_get_sintval(msg, param, res,
  583. _evapi_clients[evenv->conidx].src_port);
  584. default:
  585. return pv_get_null(msg, param, res);
  586. }
  587. return 0;
  588. }
  589. /**
  590. *
  591. */
  592. int pv_set_evapi(sip_msg_t *msg, pv_param_t *param, int op,
  593. pv_value_t *val)
  594. {
  595. return 0;
  596. }