blob: 2c0880ae5484bd7f890b6b11df91ade10d92b028 [file] [log] [blame]
Harald Welte28aa9912014-08-20 22:06:04 +02001/* OpenBSC Abis input driver for ip.access */
2
Harald Welte7bc88bb2017-04-15 19:05:33 +02003/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
Harald Welte28aa9912014-08-20 22:06:04 +02004 * (C) 2010 by Holger Hans Peter Freyther
5 * (C) 2010 by On-Waves
6 *
7 * All Rights Reserved
8 *
9 * This program is free software; you can redistribute it and/or modify
Harald Weltefd5ad172014-10-26 20:47:42 +010010 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
Harald Welte28aa9912014-08-20 22:06:04 +020012 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Weltefd5ad172014-10-26 20:47:42 +010017 * GNU General Public License for more details.
Harald Welte28aa9912014-08-20 22:06:04 +020018 *
Harald Weltefd5ad172014-10-26 20:47:42 +010019 * You should have received a copy of the GNU General Public License
Harald Welte28aa9912014-08-20 22:06:04 +020020 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24#include <unistd.h>
25#include <stdint.h>
26#include <errno.h>
27#include <stdlib.h>
28
29#include <arpa/inet.h>
Holger Hans Peter Freytheree6652b2015-11-09 16:21:19 +000030#include <sys/types.h>
Harald Weltef12d40f2017-02-08 15:46:53 +000031#include <sys/socket.h>
Harald Welte28aa9912014-08-20 22:06:04 +020032
33#include <osmocom/core/msgb.h>
34#include <osmocom/core/talloc.h>
35#include <osmocom/core/logging.h>
36#include <osmocom/core/macaddr.h>
37#include <osmocom/core/select.h>
38
39#include <osmocom/gsm/tlv.h>
40#include <osmocom/gsm/protocol/ipaccess.h>
Harald Weltee3919962014-08-20 22:28:23 +020041#include <osmocom/gsm/ipa.h>
Harald Welte28aa9912014-08-20 22:06:04 +020042
43#define IPA_ALLOC_SIZE 1200
44
45/*
46 * Common propietary IPA messages:
47 * - PONG: in reply to PING.
48 * - ID_REQUEST: first messages once OML has been established.
49 * - ID_ACK: in reply to ID_ACK.
50 */
51static const uint8_t ipa_pong_msg[] = {
52 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_PONG
53};
54
55static const uint8_t ipa_id_ack_msg[] = {
56 0, 1, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_ACK
57};
58
59static const uint8_t ipa_id_req_msg[] = {
60 0, 17, IPAC_PROTO_IPACCESS, IPAC_MSGT_ID_GET,
61 0x01, IPAC_IDTAG_UNIT,
62 0x01, IPAC_IDTAG_MACADDR,
63 0x01, IPAC_IDTAG_LOCATION1,
64 0x01, IPAC_IDTAG_LOCATION2,
65 0x01, IPAC_IDTAG_EQUIPVERS,
66 0x01, IPAC_IDTAG_SWVERSION,
67 0x01, IPAC_IDTAG_UNITNAME,
68 0x01, IPAC_IDTAG_SERNR,
69};
70
71
72static const char *idtag_names[] = {
73 [IPAC_IDTAG_SERNR] = "Serial_Number",
74 [IPAC_IDTAG_UNITNAME] = "Unit_Name",
75 [IPAC_IDTAG_LOCATION1] = "Location_1",
76 [IPAC_IDTAG_LOCATION2] = "Location_2",
77 [IPAC_IDTAG_EQUIPVERS] = "Equipment_Version",
78 [IPAC_IDTAG_SWVERSION] = "Software_Version",
79 [IPAC_IDTAG_IPADDR] = "IP_Address",
80 [IPAC_IDTAG_MACADDR] = "MAC_Address",
81 [IPAC_IDTAG_UNIT] = "Unit_ID",
82};
83
Harald Weltee3919962014-08-20 22:28:23 +020084const char *ipa_ccm_idtag_name(uint8_t tag)
Harald Welte28aa9912014-08-20 22:06:04 +020085{
86 if (tag >= ARRAY_SIZE(idtag_names))
87 return "unknown";
88
89 return idtag_names[tag];
90}
91
Harald Weltee3919962014-08-20 22:28:23 +020092int ipa_ccm_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
Harald Welte28aa9912014-08-20 22:06:04 +020093{
Holger Hans Peter Freytherf558ed42015-06-02 15:52:06 +020094 return ipa_ccm_idtag_parse_off(dec, buf, len, 0);
95}
96
97int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len, const int len_offset)
98{
Harald Welte28aa9912014-08-20 22:06:04 +020099 uint8_t t_len;
100 uint8_t t_tag;
101 uint8_t *cur = buf;
102
103 memset(dec, 0, sizeof(*dec));
104
105 while (len >= 2) {
106 len -= 2;
107 t_len = *cur++;
108 t_tag = *cur++;
109
Holger Hans Peter Freytherf558ed42015-06-02 15:52:06 +0200110 if (t_len < len_offset) {
Max9b4d0652016-11-15 19:21:23 +0100111 LOGP(DLMI, LOGL_ERROR, "minimal offset not included: %d < %d\n", t_len, len_offset);
Holger Hans Peter Freytherf558ed42015-06-02 15:52:06 +0200112 return -EINVAL;
113 }
114
Harald Welte28aa9912014-08-20 22:06:04 +0200115 if (t_len > len + 1) {
Max9b4d0652016-11-15 19:21:23 +0100116 LOGP(DLMI, LOGL_ERROR, "The tag does not fit: %d > %d\n", t_len, len + 1);
Harald Welte28aa9912014-08-20 22:06:04 +0200117 return -EINVAL;
118 }
119
Harald Weltee3919962014-08-20 22:28:23 +0200120 DEBUGPC(DLMI, "%s='%s' ", ipa_ccm_idtag_name(t_tag), cur);
Harald Welte28aa9912014-08-20 22:06:04 +0200121
Holger Hans Peter Freytherf558ed42015-06-02 15:52:06 +0200122 dec->lv[t_tag].len = t_len - len_offset;
Harald Welte28aa9912014-08-20 22:06:04 +0200123 dec->lv[t_tag].val = cur;
124
Holger Hans Peter Freytherf558ed42015-06-02 15:52:06 +0200125 cur += t_len - len_offset;
126 len -= t_len - len_offset;
Harald Welte28aa9912014-08-20 22:06:04 +0200127 }
128 return 0;
129}
130
Harald Weltee3919962014-08-20 22:28:23 +0200131int ipa_parse_unitid(const char *str, struct ipaccess_unit *unit_data)
Harald Welte28aa9912014-08-20 22:06:04 +0200132{
133 unsigned long ul;
134 char *endptr;
135 const char *nptr;
136
137 nptr = str;
138 ul = strtoul(nptr, &endptr, 10);
139 if (endptr <= nptr)
140 return -EINVAL;
141 unit_data->site_id = ul & 0xffff;
142
143 if (*endptr++ != '/')
144 return -EINVAL;
145
146 nptr = endptr;
147 ul = strtoul(nptr, &endptr, 10);
148 if (endptr <= nptr)
149 return -EINVAL;
150 unit_data->bts_id = ul & 0xffff;
151
152 if (*endptr++ != '/')
153 return -EINVAL;
154
155 nptr = endptr;
156 ul = strtoul(nptr, &endptr, 10);
157 if (endptr <= nptr)
158 return -EINVAL;
159 unit_data->trx_id = ul & 0xffff;
160
161 return 0;
162}
163
Harald Weltee3919962014-08-20 22:28:23 +0200164int ipa_ccm_tlv_to_unitdata(struct ipaccess_unit *ud,
Harald Welte28aa9912014-08-20 22:06:04 +0200165 const struct tlv_parsed *tp)
166{
167 int rc = 0;
168
169 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SERNR, 1))
170 ud->serno = talloc_strdup(ud, (char *)
171 TLVP_VAL(tp, IPAC_IDTAG_SERNR));
172
173 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1))
174 ud->unit_name = talloc_strdup(ud, (char *)
175 TLVP_VAL(tp, IPAC_IDTAG_UNITNAME));
176
177 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1))
178 ud->location1 = talloc_strdup(ud, (char *)
179 TLVP_VAL(tp, IPAC_IDTAG_LOCATION1));
180
181 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1))
182 ud->location2 = talloc_strdup(ud, (char *)
183 TLVP_VAL(tp, IPAC_IDTAG_LOCATION2));
184
185 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1))
186 ud->equipvers = talloc_strdup(ud, (char *)
187 TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS));
188
189 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1))
190 ud->swversion = talloc_strdup(ud, (char *)
191 TLVP_VAL(tp, IPAC_IDTAG_SWVERSION));
192
193 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_MACADDR, 17)) {
194 rc = osmo_macaddr_parse(ud->mac_addr, (char *)
195 TLVP_VAL(tp, IPAC_IDTAG_MACADDR));
196 if (rc < 0)
197 goto out;
198 }
199
200 if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNIT, 1))
Harald Weltee3919962014-08-20 22:28:23 +0200201 rc = ipa_parse_unitid((char *)
Harald Welte28aa9912014-08-20 22:06:04 +0200202 TLVP_VAL(tp, IPAC_IDTAG_UNIT), ud);
203
204out:
205 return rc;
206}
207
Harald Welte7bc88bb2017-04-15 19:05:33 +0200208#define IPA_STRING_MAX 64
209
210/*! \brief Generate IPA CCM ID RESP based on list of IEs
211 * \param[in] dev Descriptor describing identity data for response
212 * \param[in] ies_req List of IEIs to include in response
213 * \param[in] num_ies_req Number of IEIs in \a ies_req
214 * \returns Message buffer with IPA CCM ID RESP */
215struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev,
216 const uint8_t *ies_req, unsigned int num_ies_req)
217{
218 struct msgb *msg = ipa_msg_alloc(16);
219 char str[IPA_STRING_MAX];
220 unsigned int i;
221
222 if (!msg)
223 return NULL;
224
225 *msgb_put(msg, 1) = IPAC_MSGT_ID_RESP;
226
227 for (i = 0; i < num_ies_req; i++) {
228 uint8_t *tag;
229
230 str[0] = '\0';
231 switch (ies_req[i]) {
232 case IPAC_IDTAG_UNIT:
233 snprintf(str, sizeof(str), "%u/%u/%u",
234 dev->site_id, dev->bts_id, dev->trx_id);
235 break;
236 case IPAC_IDTAG_MACADDR:
237 snprintf(str, sizeof(str),
238 "%02x:%02x:%02x:%02x:%02x:%02x",
239 dev->mac_addr[0], dev->mac_addr[1],
240 dev->mac_addr[2], dev->mac_addr[3],
241 dev->mac_addr[4], dev->mac_addr[5]);
242 break;
243 case IPAC_IDTAG_LOCATION1:
244 if (dev->location1)
245 strncpy(str, dev->location1, IPA_STRING_MAX);
246 break;
247 case IPAC_IDTAG_LOCATION2:
248 if (dev->location2)
249 strncpy(str, dev->location2, IPA_STRING_MAX);
250 break;
251 case IPAC_IDTAG_EQUIPVERS:
252 if (dev->equipvers)
253 strncpy(str, dev->equipvers, IPA_STRING_MAX);
254 break;
255 case IPAC_IDTAG_SWVERSION:
256 if (dev->swversion)
257 strncpy(str, dev->swversion, IPA_STRING_MAX);
258 break;
259 case IPAC_IDTAG_UNITNAME:
260 if (dev->unit_name) {
261 snprintf(str, sizeof(str), dev->unit_name, IPA_STRING_MAX);
262 } else {
263 snprintf(str, sizeof(str),
264 "%02x-%02x-%02x-%02x-%02x-%02x",
265 dev->mac_addr[0], dev->mac_addr[1],
266 dev->mac_addr[2], dev->mac_addr[3],
267 dev->mac_addr[4], dev->mac_addr[5]);
268 }
269 break;
270 case IPAC_IDTAG_SERNR:
271 if (dev->serno)
272 strncpy(str, dev->serno, IPA_STRING_MAX);
273 break;
274 default:
275 LOGP(DLINP, LOGL_NOTICE,
276 "Unknown ipaccess tag 0x%02x\n", ies_req[i]);
277 msgb_free(msg);
278 return NULL;
279 }
280 str[IPA_STRING_MAX-1] = '\0';
281
282 LOGP(DLINP, LOGL_INFO, " tag %d: %s\n", ies_req[i], str);
283 tag = msgb_put(msg, 3 + strlen(str) + 1);
284 tag[0] = 0x00;
285 tag[1] = 1 + strlen(str) + 1;
286 tag[2] = ies_req[1];
287 memcpy(tag + 3, str, strlen(str) + 1);
288 }
289 ipa_prepend_header(msg, IPAC_PROTO_IPACCESS);
290 return msg;
291}
292
293/*! \brief Generate IPA CCM ID RESP based on requets payload
294 * \param[in] dev Descriptor describing identity data for response
295 * \param[in] data Payload of the IPA CCM ID GET request
296 * \param[in] len Length of \a data in octets
297 * \returns Message buffer with IPA CCM ID RESP */
298struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev,
299 const uint8_t *data, unsigned int len)
300{
301 uint8_t ies[len/2];
302 unsigned int num_ies = 0;
303 const uint8_t *cur = data;
304
Harald Welte8a4895c2017-04-27 10:25:10 +0200305 memset(ies, 0, sizeof(ies));
306
Harald Welte7bc88bb2017-04-15 19:05:33 +0200307 /* build a array of the IEIs */
308 while (len >= 2) {
309 uint8_t t_len, t_tag;
310 len -= 2;
311 t_len = *cur++;
312 t_tag = *cur++;
313
314 if (t_len > len + 1) {
315 LOGP(DLINP, LOGL_ERROR, "IPA CCM tage 0x%02x does not fit\n", t_tag);
316 break;
317 }
318
319 ies[num_ies++] = t_tag;
320
321 cur += t_len;
322 len -= t_len;
323 }
324 return ipa_ccm_make_id_resp(dev, ies, num_ies);
325}
326
Harald Weltee3919962014-08-20 22:28:23 +0200327int ipa_send(int fd, const void *msg, size_t msglen)
Harald Welte28aa9912014-08-20 22:06:04 +0200328{
329 int ret;
330
331 ret = write(fd, msg, msglen);
332 if (ret < 0)
Jacob Erlbecka6be2242014-12-22 10:58:46 +0100333 return -errno;
Harald Welte28aa9912014-08-20 22:06:04 +0200334 if (ret < msglen) {
Harald Weltee3919962014-08-20 22:28:23 +0200335 LOGP(DLINP, LOGL_ERROR, "ipa_send: short write\n");
Harald Welte28aa9912014-08-20 22:06:04 +0200336 return -EIO;
337 }
338 return ret;
339}
340
Harald Weltee3919962014-08-20 22:28:23 +0200341int ipa_ccm_send_pong(int fd)
Harald Welte28aa9912014-08-20 22:06:04 +0200342{
Harald Weltee3919962014-08-20 22:28:23 +0200343 return ipa_send(fd, ipa_pong_msg, sizeof(ipa_pong_msg));
Harald Welte28aa9912014-08-20 22:06:04 +0200344}
345
Harald Weltee3919962014-08-20 22:28:23 +0200346int ipa_ccm_send_id_ack(int fd)
Harald Welte28aa9912014-08-20 22:06:04 +0200347{
Harald Weltee3919962014-08-20 22:28:23 +0200348 return ipa_send(fd, ipa_id_ack_msg, sizeof(ipa_id_ack_msg));
Harald Welte28aa9912014-08-20 22:06:04 +0200349}
350
Harald Weltee3919962014-08-20 22:28:23 +0200351int ipa_ccm_send_id_req(int fd)
Harald Welte28aa9912014-08-20 22:06:04 +0200352{
Harald Weltee3919962014-08-20 22:28:23 +0200353 return ipa_send(fd, ipa_id_req_msg, sizeof(ipa_id_req_msg));
Harald Welte28aa9912014-08-20 22:06:04 +0200354}
355
356/* base handling of the ip.access protocol */
Harald Weltee3919962014-08-20 22:28:23 +0200357int ipa_ccm_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd)
Harald Welte28aa9912014-08-20 22:06:04 +0200358{
359 uint8_t msg_type = *(msg->l2h);
360 int ret;
361
362 switch (msg_type) {
363 case IPAC_MSGT_PING:
Harald Weltee3919962014-08-20 22:28:23 +0200364 ret = ipa_ccm_send_pong(bfd->fd);
Harald Welte28aa9912014-08-20 22:06:04 +0200365 if (ret < 0) {
366 LOGP(DLINP, LOGL_ERROR, "Cannot send PING "
367 "message. Reason: %s\n", strerror(errno));
368 break;
369 }
370 ret = 1;
371 break;
372 case IPAC_MSGT_PONG:
373 DEBUGP(DLMI, "PONG!\n");
374 ret = 1;
375 break;
376 case IPAC_MSGT_ID_ACK:
377 DEBUGP(DLMI, "ID_ACK? -> ACK!\n");
Harald Weltee3919962014-08-20 22:28:23 +0200378 ret = ipa_ccm_send_id_ack(bfd->fd);
Harald Welte28aa9912014-08-20 22:06:04 +0200379 if (ret < 0) {
380 LOGP(DLINP, LOGL_ERROR, "Cannot send ID_ACK "
381 "message. Reason: %s\n", strerror(errno));
382 break;
383 }
384 ret = 1;
385 break;
386 default:
387 /* This is not an IPA PING, PONG or ID_ACK message */
388 ret = 0;
389 break;
390 }
391 return ret;
392}
393
394/* base handling of the ip.access protocol */
Harald Weltee3919962014-08-20 22:28:23 +0200395int ipa_ccm_rcvmsg_bts_base(struct msgb *msg, struct osmo_fd *bfd)
Harald Welte28aa9912014-08-20 22:06:04 +0200396{
397 uint8_t msg_type = *(msg->l2h);
398 int ret = 0;
399
400 switch (msg_type) {
401 case IPAC_MSGT_PING:
Harald Weltee3919962014-08-20 22:28:23 +0200402 ret = ipa_ccm_send_pong(bfd->fd);
Harald Welte28aa9912014-08-20 22:06:04 +0200403 if (ret < 0) {
404 LOGP(DLINP, LOGL_ERROR, "Cannot send PONG "
405 "message. Reason: %s\n", strerror(errno));
406 }
407 break;
408 case IPAC_MSGT_PONG:
409 DEBUGP(DLMI, "PONG!\n");
410 break;
411 case IPAC_MSGT_ID_ACK:
412 DEBUGP(DLMI, "ID_ACK\n");
413 break;
414 }
415 return ret;
416}
417
418
Harald Weltee3919962014-08-20 22:28:23 +0200419void ipa_prepend_header_ext(struct msgb *msg, int proto)
Harald Welte28aa9912014-08-20 22:06:04 +0200420{
421 struct ipaccess_head_ext *hh_ext;
422
423 /* prepend the osmo ip.access header extension */
424 hh_ext = (struct ipaccess_head_ext *) msgb_push(msg, sizeof(*hh_ext));
425 hh_ext->proto = proto;
426}
427
Harald Weltee3919962014-08-20 22:28:23 +0200428void ipa_prepend_header(struct msgb *msg, int proto)
Harald Welte28aa9912014-08-20 22:06:04 +0200429{
430 struct ipaccess_head *hh;
431
432 /* prepend the ip.access header */
433 hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
434 hh->len = htons(msg->len - sizeof(*hh));
435 hh->proto = proto;
436}
437
438int ipa_msg_recv(int fd, struct msgb **rmsg)
439{
440 int rc = ipa_msg_recv_buffered(fd, rmsg, NULL);
441 if (rc < 0) {
442 errno = -rc;
443 rc = -1;
444 }
445 return rc;
446}
447
448int ipa_msg_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg)
449{
450 struct msgb *msg = tmp_msg ? *tmp_msg : NULL;
451 struct ipaccess_head *hh;
452 int len, ret;
453 int needed;
454
455 if (msg == NULL) {
456 msg = ipa_msg_alloc(0);
457 if (msg == NULL) {
458 ret = -ENOMEM;
459 goto discard_msg;
460 }
461 msg->l1h = msg->tail;
462 }
463
464 if (msg->l2h == NULL) {
465 /* first read our 3-byte header */
466 needed = sizeof(*hh) - msg->len;
467 ret = recv(fd, msg->tail, needed, 0);
468 if (ret == 0)
469 goto discard_msg;
470
471 if (ret < 0) {
472 if (errno == EAGAIN || errno == EINTR)
473 ret = 0;
474 else {
475 ret = -errno;
476 goto discard_msg;
477 }
478 }
479
480 msgb_put(msg, ret);
481
482 if (ret < needed) {
483 if (msg->len == 0) {
484 ret = -EAGAIN;
485 goto discard_msg;
486 }
487
488 LOGP(DLINP, LOGL_INFO,
Harald Weltef196a022014-08-21 09:42:03 +0200489 "Received part of IPA message header (%d/%zu)\n",
Harald Welte28aa9912014-08-20 22:06:04 +0200490 msg->len, sizeof(*hh));
491 if (!tmp_msg) {
492 ret = -EIO;
493 goto discard_msg;
494 }
495 *tmp_msg = msg;
496 return -EAGAIN;
497 }
498
499 msg->l2h = msg->tail;
500 }
501
502 hh = (struct ipaccess_head *) msg->data;
503
504 /* then read the length as specified in header */
505 len = ntohs(hh->len);
506
507 if (len < 0 || IPA_ALLOC_SIZE < len + sizeof(*hh)) {
508 LOGP(DLINP, LOGL_ERROR, "bad message length of %d bytes, "
509 "received %d bytes\n", len, msg->len);
510 ret = -EIO;
511 goto discard_msg;
512 }
513
514 needed = len - msgb_l2len(msg);
515
516 if (needed > 0) {
517 ret = recv(fd, msg->tail, needed, 0);
518
519 if (ret == 0)
520 goto discard_msg;
521
522 if (ret < 0) {
523 if (errno == EAGAIN || errno == EINTR)
524 ret = 0;
525 else {
526 ret = -errno;
527 goto discard_msg;
528 }
529 }
530
531 msgb_put(msg, ret);
532
533 if (ret < needed) {
534 LOGP(DLINP, LOGL_INFO,
535 "Received part of IPA message L2 data (%d/%d)\n",
536 msgb_l2len(msg), len);
537 if (!tmp_msg) {
538 ret = -EIO;
539 goto discard_msg;
540 }
541 *tmp_msg = msg;
542 return -EAGAIN;
543 }
544 }
545
546 ret = msgb_l2len(msg);
547
548 if (ret == 0) {
549 LOGP(DLINP, LOGL_INFO,
550 "Discarding IPA message without payload\n");
551 ret = -EAGAIN;
552 goto discard_msg;
553 }
554
555 if (tmp_msg)
556 *tmp_msg = NULL;
557 *rmsg = msg;
558 return ret;
559
560discard_msg:
561 if (tmp_msg)
562 *tmp_msg = NULL;
563 msgb_free(msg);
564 return ret;
565}
566
567struct msgb *ipa_msg_alloc(int headroom)
568{
569 struct msgb *nmsg;
570
571 headroom += sizeof(struct ipaccess_head);
572
573 nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "Abis/IP");
574 if (!nmsg)
575 return NULL;
576 return nmsg;
577}