/* * ip_nat_snmp_basic.c * * Basic SNMP Application Layer Gateway * * This IP NAT module is intended for use with SNMP network * discovery and monitoring applications where target networks use * conflicting private address realms. * * Static NAT is used to remap the networks from the view of the network * management system at the IP layer, and this module remaps some application * layer addresses to match. * * The simplest form of ALG is performed, where only tagged IP addresses * are modified. The module does not need to be MIB aware and only scans * messages at the ASN.1/BER level. * * Currently, only SNMPv1 and SNMPv2 are supported. * * More information on ALG and associated issues can be found in * RFC 2962 * * The ASB.1/BER parsing code is derived from the gxsnmp package by Gregory * McLean & Jochen Friedrich, stripped down for use in the kernel. * * Copyright (c) 2000 RP Internet (www.rpi.net.au). * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: James Morris * * Updates: * 2000-08-06: Convert to new helper API (Harald Welte). * */ #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("James Morris "); MODULE_DESCRIPTION("Basic SNMP Application Layer Gateway"); #define SNMP_PORT 161 #define SNMP_TRAP_PORT 162 #define NOCT1(n) (u_int8_t )((n) & 0xff) static int debug; static DEFINE_SPINLOCK(snmp_lock); /* * Application layer address mapping mimics the NAT mapping, but * only for the first octet in this case (a more flexible system * can be implemented if needed). */ struct oct1_map { u_int8_t from; u_int8_t to; }; /***************************************************************************** * * Basic ASN.1 decoding routines (gxsnmp author Dirk Wisse) * *****************************************************************************/ /* Class */ #define ASN1_UNI 0 /* Universal */ #define ASN1_APL 1 /* Application */ #define ASN1_CTX 2 /* Context */ #define ASN1_PRV 3 /* Private */ /* Tag */ #define ASN1_EOC 0 /* End Of Contents */ #define ASN1_BOL 1 /* Boolean */ #define ASN1_INT 2 /* Integer */ #define ASN1_BTS 3 /* Bit String */ #define ASN1_OTS 4 /* Octet String */ #define ASN1_NUL 5 /* Null */ #define ASN1_OJI 6 /* Object Identifier */ #define ASN1_OJD 7 /* Object Description */ #define ASN1_EXT 8 /* External */ #define ASN1_SEQ 16 /* Sequence */ #define ASN1_SET 17 /* Set */ #define ASN1_NUMSTR 18 /* Numerical String */ #define ASN1_PRNSTR 19 /* Printable String */ #define ASN1_TEXSTR 20 /* Teletext String */ #define ASN1_VIDSTR 21 /* Video String */ #define ASN1_IA5STR 22 /* IA5 String */ #define ASN1_UNITIM 23 /* Universal Time */ #define ASN1_GENTIM 24 /* General Time */ #define ASN1_GRASTR 25 /* Graphical String */ #define ASN1_VISSTR 26 /* Visible String */ #define ASN1_GENSTR 27 /* General String */ /* Primitive / Constructed methods*/ #define ASN1_PRI 0 /* Primitive */ #define ASN1_CON 1 /* Constructed */ /* * Error codes. */ #define ASN1_ERR_NOERROR 0 #define ASN1_ERR_DEC_EMPTY 2 #define ASN1_ERR_DEC_EOC_MISMATCH 3 #define ASN1_ERR_DEC_LENGTH_MISMATCH 4 #define ASN1_ERR_DEC_BADVALUE 5 /* * ASN.1 context. */ struct asn1_ctx { int error; /* Error condition */ unsigned char *pointer; /* Octet just to be decoded */ unsigned char *begin; /* First octet */ unsigned char *end; /* Octet after last octet */ }; /* * Octet string (not null terminated) */ struct asn1_octstr { unsigned char *data; unsigned int len; }; static void asn1_open(struct asn1_ctx *ctx, unsigned char *buf, unsigned int len) { ctx->begin = buf; ctx->end = buf + len; ctx->pointer = buf; ctx->error = ASN1_ERR_NOERROR; } static unsigned char asn1_octet_decode(struct asn1_ctx *ctx, unsigned char *ch) { if (ctx->pointer >= ctx->end) { ctx->error = ASN1_ERR_DEC_EMPTY; return 0; } *ch = *(ctx->pointer)++; return 1; } static unsigned char asn1_tag_decode(struct asn1_ctx *ctx, unsigned int *tag) { unsigned char ch; *tag = 0; do { if (!asn1_octet_decode(ctx, &ch)) return 0; *tag <<= 7; *tag |= ch & 0x7F; } while ((ch & 0x80) == 0x80); return 1; } static unsigned char asn1_id_decode(struct asn1_ctx *ctx, unsigned int *cls, unsigned int *con, unsigned int *tag) { unsigned char ch; if (!asn1_octet_decode(ctx, &ch)) return 0; *cls = (ch & 0xC0) >> 6; *con = (ch & 0x20) >> 5; *tag = (ch & 0x1F); if (*tag == 0x1F) { if (!asn1_tag_decode(ctx, tag)) return 0; } return 1; } static unsigned char asn1_length_decode(struct asn1_ctx *ctx, unsigned int *def, unsigned int *len) { unsigned char ch, cnt; if (!asn1_octet_decode(ctx, &ch)) return 0; if (ch == 0x80) *def = 0; else { *def = 1; if (ch < 0x80) *len = ch; else { cnt = (unsigned char) (ch & 0x7F); *len = 0; while (cnt > 0) { if (!asn1_octet_decode(ctx, &ch)) return 0; *len <<= 8; *len |= ch; cnt--; } } } return 1; } static unsigned char asn1_header_decode(struct asn1_ctx *ctx, unsigned char **eoc, unsigned int *cls, unsigned int *con, unsigned int *tag) { unsigned int def, len; if (!asn1_id_decode(ctx, cls, con, tag)) return 0; if (!asn1_length_decode(ctx, &def, &len)) return 0; if (def) *eoc = ctx->pointer + len; else *eoc = NULL; return 1; } static unsigned char asn1_eoc_decode(struct asn1_ctx *ctx, unsigned char *eoc) { unsigned char ch; if (eoc == 0) { if (!asn1_octet_decode(ctx, &ch)) return 0; if (ch != 0x00) { ctx->error = ASN1_ERR_DEC_EOC_MISMATCH; return 0; } if (!asn1_octet_decode(ctx, &ch)) return 0; if (ch != 0x00) { ctx->error = ASN1_ERR_DEC_EOC_MISMATCH; return 0; } return 1; } else { if (ctx->pointer != eoc) { ctx->error = ASN1_ERR_DEC_LENGTH_MISMATCH; return 0; } return 1; } } static unsigned char asn1_null_decode(struct asn1_ctx *ctx, unsigned char *eoc) { ctx->pointer = eoc; return 1; } static unsigned char asn1_long_decode(struct asn1_ctx *ctx, unsigned char *eoc, long *integer) { unsigned char ch; unsigned int len; if (!asn1_octet_decode(ctx, &ch)) return 0; *integer = (signed char) ch; len = 1; while (ctx->pointer < eoc) { if (++len > sizeof (long)) { ctx->error = ASN1_ERR_DEC_BADVALUE; return 0; } if (!asn1_octet_decode(ctx, &ch)) return 0; *integer <<= 8; *integer |= ch; } return 1; } static unsigned char asn1_uint_decode(struct asn1_ctx *ctx, unsigned char *eoc, unsigned int *integer) { unsigned char ch; unsigned int len; if (!asn1_octet_decode(ctx, &ch)) return 0; *integer = ch; if (ch == 0) len = 0; else len = 1; while (ctx->pointer < eoc) { if (++len > sizeof (unsigned int)) { ctx->error = ASN1_ERR_DEC_BADVALUE; return 0; } if (!asn1_octet_decode(ctx, &ch)) return 0; *integer <<= 8; *integer |= ch; } return 1; } static unsigned char asn1_ulong_decode(struct asn1_ctx *ctx, unsigned char *eoc, unsigned long *integer) { unsigned char ch; unsigned int len; if (!asn1_octet_decode(ctx, &ch)) return 0; *integer = ch; if (ch == 0) len = 0; else len = 1; while (ctx->pointer < eoc) { if (++len > sizeof (unsigned long)) { ctx->error = ASN1_ERR_DEC_BADVALUE; return 0; } if (!asn1_octet_decode(ctx, &ch)) return 0; *integer <<= 8; *integer |= ch; } return 1; } static unsigned char asn1_octets_decode(struct asn1_ctx *ctx, unsigned char *eoc, unsigned char **octets, unsigned int *len) { unsigned char *ptr; *len = 0; *octets = kmalloc(eoc - ctx->pointer, GFP_ATOMIC); if (*octets == NULL) { if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } ptr = *octets; while (ctx->pointer < eoc) { if (!asn1_octet_decode(ctx, (unsigned char *)ptr++)) { kfree(*octets); *octets = NULL; return 0; } (*len)++; } return 1; } static unsigned char asn1_subid_decode(struct asn1_ctx *ctx, unsigned long *subid) { unsigned char ch; *subid = 0; do { if (!asn1_octet_decode(ctx, &ch)) return 0; *subid <<= 7; *subid |= ch & 0x7F; } while ((ch & 0x80) == 0x80); return 1; } static unsigned char asn1_oid_decode(struct asn1_ctx *ctx, unsigned char *eoc, unsigned long **oid, unsigned int *len) { unsigned long subid; unsigned int size; unsigned long *optr; size = eoc - ctx->pointer + 1; *oid = kmalloc(size * sizeof(unsigned long), GFP_ATOMIC); if (*oid == NULL) { if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } optr = *oid; if (!asn1_subid_decode(ctx, &subid)) { kfree(*oid); *oid = NULL; return 0; } if (subid < 40) { optr [0] = 0; optr [1] = subid; } else if (subid < 80) { optr [0] = 1; optr [1] = subid - 40; } else { optr [0] = 2; optr [1] = subid - 80; } *len = 2; optr += 2; while (ctx->pointer < eoc) { if (++(*len) > size) { ctx->error = ASN1_ERR_DEC_BADVALUE; kfree(*oid); *oid = NULL; return 0; } if (!asn1_subid_decode(ctx, optr++)) { kfree(*oid); *oid = NULL; return 0; } } return 1; } /***************************************************************************** * * SNMP decoding routines (gxsnmp author Dirk Wisse) * *****************************************************************************/ /* SNMP Versions */ #define SNMP_V1 0 #define SNMP_V2C 1 #define SNMP_V2 2 #define SNMP_V3 3 /* Default Sizes */ #define SNMP_SIZE_COMM 256 #define SNMP_SIZE_OBJECTID 128 #define SNMP_SIZE_BUFCHR 256 #define SNMP_SIZE_BUFINT 128 #define SNMP_SIZE_SMALLOBJECTID 16 /* Requests */ #define SNMP_PDU_GET 0 #define SNMP_PDU_NEXT 1 #define SNMP_PDU_RESPONSE 2 #define SNMP_PDU_SET 3 #define SNMP_PDU_TRAP1 4 #define SNMP_PDU_BULK 5 #define SNMP_PDU_INFORM 6 #define SNMP_PDU_TRAP2 7 /* Errors */ #define SNMP_NOERROR 0 #define SNMP_TOOBIG 1 #define SNMP_NOSUCHNAME 2 #define SNMP_BADVALUE 3 #define SNMP_READONLY 4 #define SNMP_GENERROR 5 #define SNMP_NOACCESS 6 #define SNMP_WRONGTYPE 7 #define SNMP_WRONGLENGTH 8 #define SNMP_WRONGENCODING 9 #define SNMP_WRONGVALUE 10 #define SNMP_NOCREATION 11 #define SNMP_INCONSISTENTVALUE 12 #define SNMP_RESOURCEUNAVAILABLE 13 #define SNMP_COMMITFAILED 14 #define SNMP_UNDOFAILED 15 #define SNMP_AUTHORIZATIONERROR 16 #define SNMP_NOTWRITABLE 17 #define SNMP_INCONSISTENTNAME 18 /* General SNMP V1 Traps */ #define SNMP_TRAP_COLDSTART 0 #define SNMP_TRAP_WARMSTART 1 #define SNMP_TRAP_LINKDOWN 2 #define SNMP_TRAP_LINKUP 3 #define SNMP_TRAP_AUTFAILURE 4 #define SNMP_TRAP_EQPNEIGHBORLOSS 5 #define SNMP_TRAP_ENTSPECIFIC 6 /* SNMPv1 Types */ #define SNMP_NULL 0 #define SNMP_INTEGER 1 /* l */ #define SNMP_OCTETSTR 2 /* c */ #define SNMP_DISPLAYSTR 2 /* c */ #define SNMP_OBJECTID 3 /* ul */ #define SNMP_IPADDR 4 /* uc */ #define SNMP_COUNTER 5 /* ul */ #define SNMP_GAUGE 6 /* ul */ #define SNMP_TIMETICKS 7 /* ul */ #define SNMP_OPAQUE 8 /* c */ /* Additional SNMPv2 Types */ #define SNMP_UINTEGER 5 /* ul */ #define SNMP_BITSTR 9 /* uc */ #define SNMP_NSAP 10 /* uc */ #define SNMP_COUNTER64 11 /* ul */ #define SNMP_NOSUCHOBJECT 12 #define SNMP_NOSUCHINSTANCE 13 #define SNMP_ENDOFMIBVIEW 14 union snmp_syntax { unsigned char uc[0]; /* 8 bit unsigned */ char c[0]; /* 8 bit signed */ unsigned long ul[0]; /* 32 bit unsigned */ long l[0]; /* 32 bit signed */ }; struct snmp_object { unsigned long *id; unsigned int id_len; unsigned short type; unsigned int syntax_len; union snmp_syntax syntax; }; struct snmp_request { unsigned long id; unsigned int error_status; unsigned int error_index; }; struct snmp_v1_trap { unsigned long *id; unsigned int id_len; unsigned long ip_address; /* pointer */ unsigned int general; unsigned int specific; unsigned long time; }; /* SNMP types */ #define SNMP_IPA 0 #define SNMP_CNT 1 #define SNMP_GGE 2 #define SNMP_TIT 3 #define SNMP_OPQ 4 #define SNMP_C64 6 /* SNMP errors */ #define SERR_NSO 0 #define SERR_NSI 1 #define SERR_EOM 2 static inline void mangle_address(unsigned char *begin, unsigned char *addr, const struct oct1_map *map, u_int16_t *check); struct snmp_cnv { unsigned int class; unsigned int tag; int syntax; }; static struct snmp_cnv snmp_conv [] = { {ASN1_UNI, ASN1_NUL, SNMP_NULL}, {ASN1_UNI, ASN1_INT, SNMP_INTEGER}, {ASN1_UNI, ASN1_OTS, SNMP_OCTETSTR}, {ASN1_UNI, ASN1_OTS, SNMP_DISPLAYSTR}, {ASN1_UNI, ASN1_OJI, SNMP_OBJECTID}, {ASN1_APL, SNMP_IPA, SNMP_IPADDR}, {ASN1_APL, SNMP_CNT, SNMP_COUNTER}, /* Counter32 */ {ASN1_APL, SNMP_GGE, SNMP_GAUGE}, /* Gauge32 == Unsigned32 */ {ASN1_APL, SNMP_TIT, SNMP_TIMETICKS}, {ASN1_APL, SNMP_OPQ, SNMP_OPAQUE}, /* SNMPv2 data types and errors */ {ASN1_UNI, ASN1_BTS, SNMP_BITSTR}, {ASN1_APL, SNMP_C64, SNMP_COUNTER64}, {ASN1_CTX, SERR_NSO, SNMP_NOSUCHOBJECT}, {ASN1_CTX, SERR_NSI, SNMP_NOSUCHINSTANCE}, {ASN1_CTX, SERR_EOM, SNMP_ENDOFMIBVIEW}, {0, 0, -1} }; static unsigned char snmp_tag_cls2syntax(unsigned int tag, unsigned int cls, unsigned short *syntax) { struct snmp_cnv *cnv; cnv = snmp_conv; while (cnv->syntax != -1) { if (cnv->tag == tag && cnv->class == cls) { *syntax = cnv->syntax; return 1; } cnv++; } return 0; } static unsigned char snmp_object_decode(struct asn1_ctx *ctx, struct snmp_object **obj) { unsigned int cls, con, tag, len, idlen; unsigned short type; unsigned char *eoc, *end, *p; unsigned long *lp, *id; unsigned long ul; long l; *obj = NULL; id = NULL; if (!asn1_header_decode(ctx, &eoc, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ) return 0; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OJI) return 0; if (!asn1_oid_decode(ctx, end, &id, &idlen)) return 0; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) { kfree(id); return 0; } if (con != ASN1_PRI) { kfree(id); return 0; } if (!snmp_tag_cls2syntax(tag, cls, &type)) { kfree(id); return 0; } switch (type) { case SNMP_INTEGER: len = sizeof(long); if (!asn1_long_decode(ctx, end, &l)) { kfree(id); return 0; } *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC); if (*obj == NULL) { kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } (*obj)->syntax.l[0] = l; break; case SNMP_OCTETSTR: case SNMP_OPAQUE: if (!asn1_octets_decode(ctx, end, &p, &len)) { kfree(id); return 0; } *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC); if (*obj == NULL) { kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } memcpy((*obj)->syntax.c, p, len); kfree(p); break; case SNMP_NULL: case SNMP_NOSUCHOBJECT: case SNMP_NOSUCHINSTANCE: case SNMP_ENDOFMIBVIEW: len = 0; *obj = kmalloc(sizeof(struct snmp_object), GFP_ATOMIC); if (*obj == NULL) { kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } if (!asn1_null_decode(ctx, end)) { kfree(id); kfree(*obj); *obj = NULL; return 0; } break; case SNMP_OBJECTID: if (!asn1_oid_decode(ctx, end, (unsigned long **)&lp, &len)) { kfree(id); return 0; } len *= sizeof(unsigned long); *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC); if (*obj == NULL) { kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } memcpy((*obj)->syntax.ul, lp, len); kfree(lp); break; case SNMP_IPADDR: if (!asn1_octets_decode(ctx, end, &p, &len)) { kfree(id); return 0; } if (len != 4) { kfree(p); kfree(id); return 0; } *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC); if (*obj == NULL) { kfree(p); kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } memcpy((*obj)->syntax.uc, p, len); kfree(p); break; case SNMP_COUNTER: case SNMP_GAUGE: case SNMP_TIMETICKS: len = sizeof(unsigned long); if (!asn1_ulong_decode(ctx, end, &ul)) { kfree(id); return 0; } *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC); if (*obj == NULL) { kfree(id); if (net_ratelimit()) printk("OOM in bsalg (%d)\n", __LINE__); return 0; } (*obj)->syntax.ul[0] = ul; break; default: kfree(id); return 0; } (*obj)->syntax_len = len; (*obj)->type = type; (*obj)->id = id; (*obj)->id_len = idlen; if (!asn1_eoc_decode(ctx, eoc)) { kfree(id); kfree(*obj); *obj = NULL; return 0; } return 1; } static unsigned char snmp_request_decode(struct asn1_ctx *ctx, struct snmp_request *request) { unsigned int cls, con, tag; unsigned char *end; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) return 0; if (!asn1_ulong_decode(ctx, end, &request->id)) return 0; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) return 0; if (!asn1_uint_decode(ctx, end, &request->error_status)) return 0; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) return 0; if (!asn1_uint_decode(ctx, end, &request->error_index)) return 0; return 1; } /* * Fast checksum update for possibly oddly-aligned UDP byte, from the * code example in the draft. */ static void fast_csum(unsigned char *csum, const unsigned char *optr, const unsigned char *nptr, int odd) { long x, old, new; x = csum[0] * 256 + csum[1]; x =~ x & 0xFFFF; if (odd) old = optr[0] * 256; else old = optr[0]; x -= old & 0xFFFF; if (x <= 0) { x--; x &= 0xFFFF; } if (odd) new = nptr[0] * 256; else new = nptr[0]; x += new & 0xFFFF; if (x & 0x10000) { x++; x &= 0xFFFF; } x =~ x & 0xFFFF; csum[0] = x / 256; csum[1] = x & 0xFF; } /* * Mangle IP address. * - begin points to the start of the snmp messgae * - addr points to the start of the address */ static inline void mangle_address(unsigned char *begin, unsigned char *addr, const struct oct1_map *map, u_int16_t *check) { if (map->from == NOCT1(*addr)) { u_int32_t old; if (debug) memcpy(&old, (unsigned char *)addr, sizeof(old)); *addr = map->to; /* Update UDP checksum if being used */ if (*check) { unsigned char odd = !((addr - begin) % 2); fast_csum((unsigned char *)check, &map->from, &map->to, odd); } if (debug) printk(KERN_DEBUG "bsalg: mapped %u.%u.%u.%u to " "%u.%u.%u.%u\n", NIPQUAD(old), NIPQUAD(*addr)); } } static unsigned char snmp_trap_decode(struct asn1_ctx *ctx, struct snmp_v1_trap *trap, const struct oct1_map *map, u_int16_t *check) { unsigned int cls, con, tag, len; unsigned char *end; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OJI) return 0; if (!asn1_oid_decode(ctx, end, &trap->id, &trap->id_len)) return 0; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) goto err_id_free; if (!((cls == ASN1_APL && con == ASN1_PRI && tag == SNMP_IPA) || (cls == ASN1_UNI && con == ASN1_PRI && tag == ASN1_OTS))) goto err_id_free; if (!asn1_octets_decode(ctx, end, (unsigned char **)&trap->ip_address, &len)) goto err_id_free; /* IPv4 only */ if (len != 4) goto err_addr_free; mangle_address(ctx->begin, ctx->pointer - 4, map, check); if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) goto err_addr_free; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) goto err_addr_free; if (!asn1_uint_decode(ctx, end, &trap->general)) goto err_addr_free; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) goto err_addr_free; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) goto err_addr_free; if (!asn1_uint_decode(ctx, end, &trap->specific)) goto err_addr_free; if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) goto err_addr_free; if (!((cls == ASN1_APL && con == ASN1_PRI && tag == SNMP_TIT) || (cls == ASN1_UNI && con == ASN1_PRI && tag == ASN1_INT))) goto err_addr_free; if (!asn1_ulong_decode(ctx, end, &trap->time)) goto err_addr_free; return 1; err_id_free: kfree(trap->id); err_addr_free: kfree((unsigned long *)trap->ip_address); return 0; } /***************************************************************************** * * Misc. routines * *****************************************************************************/ static void hex_dump(unsigned char *buf, size_t len) { size_t i; for (i = 0; i < len; i++) { if (i && !(i % 16)) printk("\n"); printk("%02x ", *(buf + i)); } printk("\n"); } /* * Parse and mangle SNMP message according to mapping. * (And this is the fucking 'basic' method). */ static int snmp_parse_mangle(unsigned char *msg, u_int16_t len, const struct oct1_map *map, u_int16_t *check) { unsigned char *eoc, *end; unsigned int cls, con, tag, vers, pdutype; struct asn1_ctx ctx; struct asn1_octstr comm; struct snmp_object **obj; if (debug > 1) hex_dump(msg, len); asn1_open(&ctx, msg, len); /* * Start of SNMP message. */ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ) return 0; /* * Version 1 or 2 handled. */ if (!asn1_header_decode(&ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT) return 0; if (!asn1_uint_decode (&ctx, end, &vers)) return 0; if (debug > 1) printk(KERN_DEBUG "bsalg: snmp version: %u\n", vers + 1); if (vers > 1) return 1; /* * Community. */ if (!asn1_header_decode (&ctx, &end, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OTS) return 0; if (!asn1_octets_decode(&ctx, end, &comm.data, &comm.len)) return 0; if (debug > 1) { unsigned int i; printk(KERN_DEBUG "bsalg: community: "); for (i = 0; i < comm.len; i++) printk("%c", comm.data[i]); printk("\n"); } kfree(comm.data); /* * PDU type */ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &pdutype)) return 0; if (cls != ASN1_CTX || con != ASN1_CON) return 0; if (debug > 1) { unsigned char *pdus[] = { [SNMP_PDU_GET] = "get", [SNMP_PDU_NEXT] = "get-next", [SNMP_PDU_RESPONSE] = "response", [SNMP_PDU_SET] = "set", [SNMP_PDU_TRAP1] = "trapv1", [SNMP_PDU_BULK] = "bulk", [SNMP_PDU_INFORM] = "inform", [SNMP_PDU_TRAP2] = "trapv2" }; if (pdutype > SNMP_PDU_TRAP2) printk(KERN_DEBUG "bsalg: bad pdu type %u\n", pdutype); else printk(KERN_DEBUG "bsalg: pdu: %s\n", pdus[pdutype]); } if (pdutype != SNMP_PDU_RESPONSE && pdutype != SNMP_PDU_TRAP1 && pdutype != SNMP_PDU_TRAP2) return 1; /* * Request header or v1 trap */ if (pdutype == SNMP_PDU_TRAP1) { struct snmp_v1_trap trap; unsigned char ret = snmp_trap_decode(&ctx, &trap, map, check); /* Discard trap allocations regardless */ kfree(trap.id); kfree((unsigned long *)trap.ip_address); if (!ret) return ret; } else { struct snmp_request req; if (!snmp_request_decode(&ctx, &req)) return 0; if (debug > 1) printk(KERN_DEBUG "bsalg: request: id=0x%lx error_status=%u " "error_index=%u\n", req.id, req.error_status, req.error_index); } /* * Loop through objects, look for IP addresses to mangle. */ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &tag)) return 0; if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ) return 0; obj = kmalloc(sizeof(struct snmp_object), GFP_ATOMIC); if (obj == NULL) { if (net_ratelimit()) printk(KERN_WARNING "OOM in bsalg(%d)\n", __LINE__); return 0; } while (!asn1_eoc_decode(&ctx, eoc)) { unsigned int i; if (!snmp_object_decode(&ctx, obj)) { if (*obj) { if ((*obj)->id) kfree((*obj)->id); kfree(*obj); } kfree(obj); return 0; } if (debug > 1) { printk(KERN_DEBUG "bsalg: object: "); for (i = 0; i < (*obj)->id_len; i++) { if (i > 0) printk("."); printk("%lu", (*obj)->id[i]); } printk(": type=%u\n", (*obj)->type); } if ((*obj)->type == SNMP_IPADDR) mangle_address(ctx.begin, ctx.pointer - 4 , map, check); kfree((*obj)->id); kfree(*obj); } kfree(obj); if (!asn1_eoc_decode(&ctx, eoc)) return 0; return 1; } /***************************************************************************** * * NAT routines. * *****************************************************************************/ /* * SNMP translation routine. */ static int snmp_translate(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, struct sk_buff **pskb) { struct iphdr *iph = (*pskb)->nh.iph; struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph + iph->ihl); u_int16_t udplen = ntohs(udph->len); u_int16_t paylen = udplen - sizeof(struct udphdr); int dir = CTINFO2DIR(ctinfo); struct oct1_map map; /* * Determine mappping for application layer addresses based * on NAT manipulations for the packet. */ if (dir == IP_CT_DIR_ORIGINAL) { /* SNAT traps */ map.from = NOCT1(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip); map.to = NOCT1(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip); } else { /* DNAT replies */ map.from = NOCT1(ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip); map.to = NOCT1(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip); } if (map.from == map.to) return NF_ACCEPT; if (!snmp_parse_mangle((unsigned char *)udph + sizeof(struct udphdr), paylen, &map, &udph->check)) { if (net_ratelimit()) printk(KERN_WARNING "bsalg: parser failed\n"); return NF_DROP; } return NF_ACCEPT; } /* We don't actually set up expectations, just adjust internal IP * addresses if this is being NATted */ static int help(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { int dir = CTINFO2DIR(ctinfo); unsigned int ret; struct iphdr *iph = (*pskb)->nh.iph; struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph + iph->ihl); /* SNMP replies and originating SNMP traps get mangled */ if (udph->source == ntohs(SNMP_PORT) && dir != IP_CT_DIR_REPLY) return NF_ACCEPT; if (udph->dest == ntohs(SNMP_TRAP_PORT) && dir != IP_CT_DIR_ORIGINAL) return NF_ACCEPT; /* No NAT? */ if (!(ct->status & IPS_NAT_MASK)) return NF_ACCEPT; /* * Make sure the packet length is ok. So far, we were only guaranteed * to have a valid length IP header plus 8 bytes, which means we have * enough room for a UDP header. Just verify the UDP length field so we * can mess around with the payload. */ if (ntohs(udph->len) != (*pskb)->len - (iph->ihl << 2)) { if (net_ratelimit()) printk(KERN_WARNING "SNMP: dropping malformed packet " "src=%u.%u.%u.%u dst=%u.%u.%u.%u\n", NIPQUAD(iph->saddr), NIPQUAD(iph->daddr)); return NF_DROP; } if (!skb_ip_make_writable(pskb, (*pskb)->len)) return NF_DROP; spin_lock_bh(&snmp_lock); ret = snmp_translate(ct, ctinfo, pskb); spin_unlock_bh(&snmp_lock); return ret; } static struct ip_conntrack_helper snmp_helper = { .max_expected = 0, .timeout = 180, .me = THIS_MODULE, .help = help, .name = "snmp", .tuple = { .src = { .u = { __constant_htons(SNMP_PORT) } }, .dst = { .protonum = IPPROTO_UDP }, }, .mask = { .src = { .u = { 0xFFFF } }, .dst = { .protonum = 0xFF }, }, }; static struct ip_conntrack_helper snmp_trap_helper = { .max_expected = 0, .timeout = 180, .me = THIS_MODULE, .help = help, .name = "snmp_trap", .tuple = { .src = { .u = { __constant_htons(SNMP_TRAP_PORT) } }, .dst = { .protonum = IPPROTO_UDP }, }, .mask = { .src = { .u = { 0xFFFF } }, .dst = { .protonum = 0xFF }, }, }; /***************************************************************************** * * Module stuff. * *****************************************************************************/ static int __init init(void) { int ret = 0; ret = ip_conntrack_helper_register(&snmp_helper); if (ret < 0) return ret; ret = ip_conntrack_helper_register(&snmp_trap_helper); if (ret < 0) { ip_conntrack_helper_unregister(&snmp_helper); return ret; } return ret; } static void __exit fini(void) { ip_conntrack_helper_unregister(&snmp_helper); ip_conntrack_helper_unregister(&snmp_trap_helper); } module_init(init); module_exit(fini); module_param(debug, bool, 0600);