Exemplo n.º 1
0
/* So, this packet has hit the connection tracking matching code.
   Mangle it, and change the expectation to match the new version. */
static unsigned int ip_nat_ftp(struct sk_buff **pskb,
			       enum ip_conntrack_info ctinfo,
			       enum ip_ct_ftp_type type,
			       unsigned int matchoff,
			       unsigned int matchlen,
			       struct ip_conntrack_expect *exp,
			       u32 *seq)
{
	u_int32_t newip;
	u_int16_t port;
	int dir = CTINFO2DIR(ctinfo);
	struct ip_conntrack *ct = exp->master;

	DEBUGP("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen);

	/* Connection will come from wherever this packet goes, hence !dir */
	newip = ct->tuplehash[!dir].tuple.dst.ip;
	exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port;
	exp->dir = !dir;

	/* When you see the packet, we need to NAT it the same as the
	 * this one. */
	exp->expectfn = ip_nat_follow_master;

	/* Try to get same port: if not, try to change it. */
	for (port = ntohs(exp->saved_proto.tcp.port); port != 0; port++) {
		exp->tuple.dst.u.tcp.port = htons(port);
		if (ip_conntrack_expect_related(exp) == 0)
			break;
	}

	if (port == 0) {
		ip_conntrack_expect_free(exp);
		return NF_DROP;
	}

	if (!mangle[type](pskb, newip, port, matchoff, matchlen, ct, ctinfo,
			  seq)) {
		ip_conntrack_unexpect_related(exp);
		return NF_DROP;
	}
	return NF_ACCEPT;
}
Exemplo n.º 2
0
static unsigned int help(struct sk_buff **pskb,
			 enum ip_conntrack_info ctinfo,
			 unsigned int matchoff,
			 unsigned int matchlen,
			 struct ip_conntrack_expect *exp)
{
	char buffer[sizeof("65535")];
	u_int16_t port;
	unsigned int ret;

	/* Connection comes from client. */
	exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port;
	exp->dir = IP_CT_DIR_ORIGINAL;

	/* When you see the packet, we need to NAT it the same as the
	 * this one (ie. same IP: it will be TCP and master is UDP). */
	exp->expectfn = ip_nat_follow_master;

	/* Try to get same port: if not, try to change it. */
	for (port = ntohs(exp->saved_proto.tcp.port); port != 0; port++) {
		exp->tuple.dst.u.tcp.port = htons(port);
		if (ip_conntrack_expect_related(exp) == 0)
			break;
	}

	if (port == 0) {
		ip_conntrack_expect_free(exp);
		return NF_DROP;
	}

	sprintf(buffer, "%u", port);
	ret = ip_nat_mangle_udp_packet(pskb, exp->master, ctinfo,
				       matchoff, matchlen,
				       buffer, strlen(buffer));
	if (ret != NF_ACCEPT)
		ip_conntrack_unexpect_related(exp);
	return ret;
}
Exemplo n.º 3
0
static int quake3_help(struct sk_buff **pskb,
	struct ip_conntrack *ct,
	enum ip_conntrack_info ctinfo)
{
	struct udphdr _udph, *uh;
	struct ip_conntrack_expect *exp;
	void *data, *qb_ptr;
	int dir = CTINFO2DIR(ctinfo);
	int i, dataoff;
	int ret = NF_ACCEPT;

	
	/* Until there's been traffic both ways, don't look in packets. note:
	 * it's UDP ! */
	if (ctinfo != IP_CT_ESTABLISHED
	    && ctinfo != IP_CT_IS_REPLY) {
	        DEBUGP("ip_conntrack_quake3: not ok ! Conntrackinfo = %u\n",
			ctinfo);
	        return NF_ACCEPT;
	} else { 
		DEBUGP("ip_conntrack_quake3: it's ok ! Conntrackinfo = %u\n",
			ctinfo);
	}

	/* Valid UDP header? */
	uh = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*4,
				sizeof(_udph), &_udph);
	if (!uh)
		return NF_ACCEPT;

	/* Any data? */
	dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
	if (dataoff >= (*pskb)->len)
		return NF_ACCEPT;

	LOCK_BH(&quake3_buffer_lock);
	qb_ptr = skb_header_pointer(*pskb, dataoff,
				    (*pskb)->len - dataoff, quake3_buffer);
	BUG_ON(qb_ptr == NULL);
	data = qb_ptr;

	
	if (strnicmp(data + 4, quake3s_conntrack.pattern, 
		     quake3s_conntrack.plen) == 0) {
		for(i=23;    /* 4 bytes filler, 18 bytes "getserversResponse", 
				1 byte "\" */
		    i+6 < ntohs(uh->len);
		    i+=7) {
			u_int32_t *ip = data+i;
			u_int16_t *port = data+i+4;
#if 0
			DEBUGP("ip_conntrack_quake3: adding server at offset "
			       "%u/%u %u.%u.%u.%u:%u\n", i, ntohs(uh->len),
			       NIPQUAD(*ip), ntohs(*port));
#endif

			exp = ip_conntrack_expect_alloc();
			if (!exp) { 
				ret = NF_DROP;
				goto out;
			}

			memset(exp, 0, sizeof(*exp));

			exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
			exp->tuple.dst.ip = *ip;
			exp->tuple.dst.u.udp.port = *port;
			exp->tuple.dst.protonum = IPPROTO_UDP;

			exp->mask.src.ip = 0xffffffff;
			exp->mask.dst.ip = 0xffffffff;
			exp->mask.dst.u.udp.port = 0xffff;
			exp->mask.dst.protonum = 0xff;

			if (ip_nat_quake3_hook) 
				ret = ip_nat_quake3_hook(exp);
			else if (ip_conntrack_expect_related(exp) != 0) {
				ip_conntrack_expect_free(exp);
				ret = NF_DROP;
			}
			goto out;
		}
	}
	
out:
	return ret;
}
Exemplo n.º 4
0
static int
pptp_exp_gre(struct ip_conntrack_expect *expect_orig,
	     struct ip_conntrack_expect *expect_reply)
{
	struct ip_ct_pptp_master *ct_pptp_info = 
				&expect_orig->master->help.ct_pptp_info;
	struct ip_nat_pptp *nat_pptp_info = 
				&expect_orig->master->nat.help.nat_pptp_info;

	struct ip_conntrack *ct = expect_orig->master;

	struct ip_conntrack_tuple inv_t;
	struct ip_conntrack_tuple *orig_t, *reply_t;

	/* save original PAC call ID in nat_info */
	nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id;

	/* alter expectation */
	orig_t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
	reply_t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;

	/* alter expectation for PNS->PAC direction */
	invert_tuplepr(&inv_t, &expect_orig->tuple);
	expect_orig->saved_proto.gre.key = htons(ct_pptp_info->pns_call_id);
	expect_orig->tuple.src.u.gre.key = htons(nat_pptp_info->pns_call_id);
	expect_orig->tuple.dst.u.gre.key = htons(ct_pptp_info->pac_call_id);
	inv_t.src.ip = reply_t->src.ip;
	inv_t.dst.ip = reply_t->dst.ip;
	inv_t.src.u.gre.key = htons(nat_pptp_info->pac_call_id);
	inv_t.dst.u.gre.key = htons(ct_pptp_info->pns_call_id);

	if (!ip_conntrack_expect_related(expect_orig)) {
		DEBUGP("successfully registered expect\n");
	} else {
		DEBUGP("can't expect_related(expect_orig)\n");
		ip_conntrack_expect_free(expect_orig);
		return 1;
	}

	/* alter expectation for PAC->PNS direction */
	invert_tuplepr(&inv_t, &expect_reply->tuple);
	expect_reply->saved_proto.gre.key = htons(nat_pptp_info->pns_call_id);
	expect_reply->tuple.src.u.gre.key = htons(nat_pptp_info->pac_call_id);
	expect_reply->tuple.dst.u.gre.key = htons(ct_pptp_info->pns_call_id);
	inv_t.src.ip = orig_t->src.ip;
	inv_t.dst.ip = orig_t->dst.ip;
	inv_t.src.u.gre.key = htons(nat_pptp_info->pns_call_id);
	inv_t.dst.u.gre.key = htons(ct_pptp_info->pac_call_id);

	if (!ip_conntrack_expect_related(expect_reply)) {
		DEBUGP("successfully registered expect\n");
	} else {
		DEBUGP("can't expect_related(expect_reply)\n");
		ip_conntrack_unexpect_related(expect_orig);
		ip_conntrack_expect_free(expect_reply);
		return 1;
	}

	if (ip_ct_gre_keymap_add(ct, &expect_reply->tuple, 0) < 0) {
		DEBUGP("can't register original keymap\n");
		ip_conntrack_unexpect_related(expect_orig);
		ip_conntrack_unexpect_related(expect_reply);
		return 1;
	}

	if (ip_ct_gre_keymap_add(ct, &inv_t, 1) < 0) {
		DEBUGP("can't register reply keymap\n");
		ip_conntrack_unexpect_related(expect_orig);
		ip_conntrack_unexpect_related(expect_reply);
		ip_ct_gre_keymap_destroy(ct);
		return 1;
	}

	return 0;
}
Exemplo n.º 5
0
/* outbound packet: client->server */
static inline int
help_out(struct sk_buff **pskb, unsigned char *rb_ptr, unsigned int datalen,
         struct ip_conntrack* ct, enum ip_conntrack_info ctinfo)
{
    struct ip_ct_rtsp_expect expinfo;
    int dir = CTINFO2DIR(ctinfo);   /* = IP_CT_DIR_ORIGINAL */
    //struct  tcphdr* tcph = (void*)iph + iph->ihl * 4;
    //uint    tcplen = pktlen - iph->ihl * 4;
    char*   pdata = rb_ptr;
    //uint    datalen = tcplen - tcph->doff * 4;
    uint    dataoff = 0;
    int ret = NF_ACCEPT;

    struct ip_conntrack_expect *exp;

    memset(&expinfo, 0, sizeof(expinfo));

    while (dataoff < datalen)
    {
        uint    cmdoff = dataoff;
        uint    hdrsoff = 0;
        uint    hdrslen = 0;
        uint    cseqoff = 0;
        uint    cseqlen = 0;
        uint    lineoff = 0;
        uint    linelen = 0;
        uint    off;

        if (!rtsp_parse_message(pdata, datalen, &dataoff,
                                &hdrsoff, &hdrslen,
                                &cseqoff, &cseqlen))
        {
            break;      /* not a valid message */
        }

        if (strncmp(pdata+cmdoff, "SETUP ", 6) != 0)
        {
            continue;   /* not a SETUP message */
        }
        DEBUGP("found a setup message\n");

        off = 0;
        while (nf_mime_nextline(pdata+hdrsoff, hdrslen, &off,
                                &lineoff, &linelen))
        {
            if (linelen == 0)
            {
                break;
            }
            if (off > hdrsoff+hdrslen)
            {
                INFOP("!! overrun !!");
                break;
            }

            if (nf_strncasecmp(pdata+hdrsoff+lineoff, "Transport:", 10) == 0)
            {
                rtsp_parse_transport(pdata+hdrsoff+lineoff, linelen, &expinfo);
            }
        }

        if (expinfo.loport == 0)
        {
            DEBUGP("no udp transports found\n");
            continue;   /* no udp transports found */
        }

        DEBUGP("udp transport found, ports=(%d,%hu,%hu)\n",
               (int)expinfo.pbtype,
               expinfo.loport,
               expinfo.hiport);

        exp = ip_conntrack_expect_alloc();
        if (!exp) {
            ret = NF_DROP;
            goto out;
        }

        //exp->seq = ntohl(tcph->seq) + hdrsoff; /* mark all the headers */
        exp->master = ct;
        //exp.help.exp_rtsp_info.len = hdrslen;

        exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
        exp->mask.src.ip  = 0xffffffff;
        exp->tuple.dst.ip = ct->tuplehash[dir].tuple.src.ip;
        exp->mask.dst.ip  = 0xffffffff;
        exp->tuple.dst.u.udp.port = expinfo.loport;
        exp->mask.dst.u.udp.port  =
            (expinfo.pbtype == pb_range) ? 0xfffe : 0xffff;
        exp->tuple.dst.protonum = IPPROTO_UDP;
        exp->mask.dst.protonum  = 0xff;

        DEBUGP("expect_related %u.%u.%u.%u:%u-%u.%u.%u.%u:%u\n",
               NIPQUAD(exp->tuple.src.ip),
               ntohs(exp->tuple.src.u.tcp.port),
               NIPQUAD(exp->tuple.dst.ip),
               ntohs(exp->tuple.dst.u.tcp.port));

        if (ip_nat_rtsp_hook)
            /* pass the request off to the nat helper */
            ret = ip_nat_rtsp_hook(pskb, ctinfo, &expinfo, exp);
        else if (ip_conntrack_expect_related(exp) != 0) {
            INFOP("ip_conntrack_expect_related failed\n");
            ip_conntrack_expect_free(exp);
            ret  = NF_DROP;
        }
        goto out;
    }
out:

    return ret;
}
Exemplo n.º 6
0
static unsigned int mms_data_fixup(struct sk_buff **pskb,
                                   enum ip_conntrack_info ctinfo,
                                   const struct ip_ct_mms_expect *ct_mms_info,
                                   struct ip_conntrack_expect *expect)
{
    u_int32_t newip;
    struct ip_conntrack *ct = expect->master;
    struct iphdr *iph = (*pskb)->nh.iph;
    struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
    char *data = (char *)tcph + tcph->doff * 4;
    int i, j, k, port;
    u_int16_t mms_proto;

    u_int32_t *mms_chunkLenLV    = (u_int32_t *)(data + MMS_SRV_CHUNKLENLV_OFFSET);
    u_int32_t *mms_chunkLenLM    = (u_int32_t *)(data + MMS_SRV_CHUNKLENLM_OFFSET);
    u_int32_t *mms_messageLength = (u_int32_t *)(data + MMS_SRV_MESSAGELENGTH_OFFSET);

    int zero_padding;

    char buffer[28];         /* "\\255.255.255.255\UDP\65635" * 2
				    (for unicode) */
    char unicode_buffer[75]; /* 27*2 (unicode) + 20 + 1 */
    char proto_string[6];

    /* what was the protocol again ? */
    mms_proto = expect->tuple.dst.protonum;
    sprintf(proto_string, "%u", mms_proto);

    DEBUGP("ip_nat_mms: mms_data_fixup: info (seq %u + %u) "
           "in %u, proto %s\n",
           expect->seq, ct_mms_info->len, ntohl(tcph->seq),
           mms_proto == IPPROTO_UDP ? "UDP"
           : mms_proto == IPPROTO_TCP ? "TCP":proto_string);

    newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
    expect->saved_proto.tcp.port = expect->tuple.dst.u.tcp.port;
    expect->expectfn = ip_nat_follow_master;

    /* Alter conntrack's expectations. */
    for (port = ct_mms_info->port; port != 0; port++) {
        expect->tuple.dst.u.tcp.port = htons(port);
        if (ip_conntrack_expect_related(expect) == 0) {
            DEBUGP("ip_nat_mms: mms_data_fixup: using port %d\n",
                   port);
            break;
        }
    }

    if (port == 0) {
        ip_conntrack_expect_free(expect);
        return NF_DROP;
    }

    sprintf(buffer, "\\\\%u.%u.%u.%u\\%s\\%u",
            NIPQUAD(newip),
            expect->tuple.dst.protonum == IPPROTO_UDP ? "UDP"
            : expect->tuple.dst.protonum == IPPROTO_TCP ? "TCP":proto_string,
            port);
    DEBUGP("ip_nat_mms: new unicode string=%s\n", buffer);

    memset(unicode_buffer, 0, sizeof(char)*75);

    for (i=0; i<strlen(buffer); ++i)
        *(unicode_buffer+i*2)=*(buffer+i);

    DEBUGP("ip_nat_mms: mms_data_fixup: padding: %u len: %u\n",
           ct_mms_info->padding, ct_mms_info->len);
    DEBUGP("ip_nat_mms: mms_data_fixup: offset: %u\n",
           MMS_SRV_UNICODE_STRING_OFFSET+ct_mms_info->len);
    DUMP_BYTES(data+MMS_SRV_UNICODE_STRING_OFFSET, 60);

    /* add end of packet to it */
    for (j=0; j<ct_mms_info->padding; ++j) {
        DEBUGP("ip_nat_mms: mms_data_fixup: i=%u j=%u byte=%u\n",
               i, j, (u8)*(data+MMS_SRV_UNICODE_STRING_OFFSET+ct_mms_info->len+j));
        *(unicode_buffer+i*2+j) = *(data+MMS_SRV_UNICODE_STRING_OFFSET+ct_mms_info->len+j);
    }

    /* pad with zeroes at the end ? see explanation of weird math below */
    zero_padding = (8-(strlen(buffer)*2 + ct_mms_info->padding + 4)%8)%8;
    for (k=0; k<zero_padding; ++k)
        *(unicode_buffer+i*2+j+k)= (char)0;

    DEBUGP("ip_nat_mms: mms_data_fixup: zero_padding = %u\n", zero_padding);
    DEBUGP("ip_nat_mms: original=> chunkLenLV=%u chunkLenLM=%u "
           "messageLength=%u\n", *mms_chunkLenLV, *mms_chunkLenLM,
           *mms_messageLength);

    /* explanation, before I forget what I did:
       strlen(buffer)*2 + ct_mms_info->padding + 4 must be divisable by 8;
       divide by 8 and add 3 to compute the mms_chunkLenLM field,
       but note that things may have to be padded with zeroes to align by 8
       bytes, hence we add 7 and divide by 8 to get the correct length */
    *mms_chunkLenLM    = (u_int32_t) (3+(strlen(buffer)*2+ct_mms_info->padding+11)/8);
    *mms_chunkLenLV    = *mms_chunkLenLM+2;
    *mms_messageLength = *mms_chunkLenLV*8;

    DEBUGP("ip_nat_mms: modified=> chunkLenLV=%u chunkLenLM=%u"
           " messageLength=%u\n", *mms_chunkLenLV, *mms_chunkLenLM,
           *mms_messageLength);

    ip_nat_mangle_tcp_packet(pskb, ct, ctinfo,
                             ct_mms_info->offset,
                             ct_mms_info->len + ct_mms_info->padding,
                             unicode_buffer, strlen(buffer)*2 +
                             ct_mms_info->padding + zero_padding);
    DUMP_BYTES(unicode_buffer, 60);

    return NF_ACCEPT;
}
Exemplo n.º 7
0
static int check_rpc_packet(const u_int32_t *data,
			int dir, struct ip_conntrack *ct,
			struct list_head request_p_list)
{
        int ret = NF_ACCEPT;
	u_int32_t xid;
	struct request_p *req_p;
	struct ip_conntrack_expect *exp;

	/* Translstion's buffer for XDR */
	u_int16_t port_buf;


	/* Get XID */
	xid = *data;

 	/* This does sanity checking on RPC payloads,
	 * and permits only the RPC "get port" (3)
	 * in authorised procedures in client
	 * communications with the portmapper.
	 */

	/* perform direction dependant RPC work */
	if (dir == IP_CT_DIR_ORIGINAL) {

		data += 5;

		/* Get RPC requestor */
		if (IXDR_GET_INT32(data) != 3) {
			DEBUGP("RPC packet contains an invalid (non \"get\") requestor. [skip]\n");
			return NF_ACCEPT;
		}
		DEBUGP("RPC packet contains a \"get\" requestor. [cont]\n");

		data++;

		/* Jump Credentials and Verfifier */
		data = data + IXDR_GET_INT32(data) + 2;
		data = data + IXDR_GET_INT32(data) + 2;

		/* Get RPC procedure */
		DEBUGP("RPC packet contains procedure request [%u]. [cont]\n",
			(unsigned int)IXDR_GET_INT32(data));

		/* Get RPC protocol and store against client parameters */
		data = data + 2;
		alloc_request_p(xid, IXDR_GET_INT32(data), ct->tuplehash[dir].tuple.src.ip,
				ct->tuplehash[dir].tuple.src.u.all);

		DEBUGP("allocated RPC req_p for xid=%u proto=%u %u.%u.%u.%u:%u\n",
			xid, IXDR_GET_INT32(data),
			NIPQUAD(ct->tuplehash[dir].tuple.src.ip),
			ntohs(ct->tuplehash[dir].tuple.src.u.all));

		DEBUGP("allocated RPC request for protocol %u. [done]\n",
			(unsigned int)IXDR_GET_INT32(data));

	} else {

		/* Check for returning packet's stored counterpart */
		req_p = LIST_FIND(&request_p_list_udp, request_p_cmp,
				  struct request_p *, xid,
				  ct->tuplehash[!dir].tuple.src.ip,
				  ct->tuplehash[!dir].tuple.src.u.all);

		/* Drop unexpected packets */
		if (!req_p) {
			DEBUGP("packet is not expected. [skip]\n");
			return NF_ACCEPT;
		}

		/* Verifies if packet is really an RPC reply packet */
		data++;
		if (IXDR_GET_INT32(data) != 1) {
			DEBUGP("packet is not a valid RPC reply. [skip]\n");
			return NF_ACCEPT;
		}

		/* Is status accept? */
		data++;
		if (IXDR_GET_INT32(data)) {
			DEBUGP("packet is not an RPC accept. [skip]\n");
			return NF_ACCEPT;
		}

		/* Get Verifier length. Jump verifier */
		data++;
		data = data + IXDR_GET_INT32(data) + 2;

		/* Is accpet status "success"? */
		if (IXDR_GET_INT32(data)) {
			DEBUGP("packet is not an RPC accept status of success. [skip]\n");
			return NF_ACCEPT;
		}

		/* Get server port number */	  
		data++;
		port_buf = (u_int16_t) IXDR_GET_INT32(data);

		/* If a packet has made it this far then it deserves an
		 * expectation ...  if port == 0, then this service is 
		 * not going to be registered.
		 */
		if (port_buf) {
			DEBUGP("port found: %u\n", port_buf);

                        exp = ip_conntrack_expect_alloc();
                        if (!exp) {
                          ret = NF_DROP;
                          goto out;
                        }

			/* Watch out, Radioactive-Man! */
			exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
			exp->tuple.dst.ip = ct->tuplehash[!dir].tuple.dst.ip;
			exp->mask.src.ip = 0xffffffff;
			exp->mask.dst.ip = 0xffffffff;

			switch (req_p->proto) {
				case IPPROTO_UDP:
					exp->tuple.src.u.udp.port = 0;
					exp->tuple.dst.u.udp.port = htons(port_buf);
					exp->tuple.dst.protonum = IPPROTO_UDP;
					exp->mask.src.u.udp.port = 0;
					exp->mask.dst.u.udp.port = htons(0xffff);
					exp->mask.dst.protonum = 0xff;
					break;

				case IPPROTO_TCP:
					exp->tuple.src.u.tcp.port = 0;
					exp->tuple.dst.u.tcp.port = htons(port_buf);
					exp->tuple.dst.protonum = IPPROTO_TCP;
					exp->mask.src.u.tcp.port = 0;
					exp->mask.dst.u.tcp.port = htons(0xffff);
					exp->mask.dst.protonum = 0xff;
					break;
			}
			exp->expectfn = NULL;
			exp->master = ct;

			DEBUGP("expect related ip   %u.%u.%u.%u:0-%u.%u.%u.%u:%u proto=%u\n",
				NIPQUAD(exp->tuple.src.ip),
				NIPQUAD(exp->tuple.dst.ip),
				port_buf, req_p->proto);

			DEBUGP("expect related mask %u.%u.%u.%u:0-%u.%u.%u.%u:65535 proto=%u\n",
				NIPQUAD(exp->mask.src.ip),
				NIPQUAD(exp->mask.dst.ip),
				exp->mask.dst.protonum);

			if (ip_conntrack_expect_related(exp) != 0) {
		                ip_conntrack_expect_free(exp);
        		        ret = NF_DROP;
        		}
		}

out:
		req_cl(req_p);

		DEBUGP("packet evaluated. [expect]\n");
	}

	return ret;
}