/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_add_transaction
 *===========================================================================
 * Description: Add a DNS transaction to the list of active transactions.
 * Parameters:  type    - DNS query type
 *              dns_hdr - pointer to the DNS protocol header.
 *              param   - pointer to NAT proxy parameters.
 * Returns:     pointer to the transaction or IP_NULL if failed to add.
 */
IP_STATIC Ipnet_nat_dns_transaction *
ipnet_nat_proxy_dns_add_transaction(int type,
                                    Ipnet_nat_dns_hdr *dns_hdr,
                                    Ipnet_nat_proxy_param *param,
                                    Ip_u8 *ptrname)
{
    Ipnet_nat_dns_transaction *trans;

    /* Check that there is room for another transaction */
    if(ipnet_nat_proxy_dns_list.size >= IPNET_NAT_DNS_TRANS_MAX_ENTRIES)
        return IP_NULL;
    trans = ipcom_malloc(sizeof(*trans));
    if (trans == IP_NULL)
        return IP_NULL;

    ipcom_memset(trans, 0, sizeof(*trans));
    trans->id      = (Ip_u16)IP_GET_NTOHS(&dns_hdr->id);
    trans->srcport = param->tuple.private_port;
    trans->dstaddr = param->tuple.public_addr;
    trans->type    = type;
    if (ptrname != IP_NULL)
        ipcom_strncpy((char *)trans->ptrname, (char *)ptrname, sizeof(trans->ptrname)-1);
    ipcom_list_insert_last(&ipnet_nat_proxy_dns_list, &trans->list);
    /* Add the timeout */
    if (ipnet_nat_proxy_timeout_schedule(IPNET_NAT_DNS_TRANS_TIMEOUT,
                                         ipnet_nat_proxy_dns_transaction_timeout,
                                         trans,
                                         &trans->tmo) < 0)
    {
        ipcom_list_remove(&trans->list);
        ipcom_free(trans);
        return IP_NULL;
    }
    return trans;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_decode_name
 *===========================================================================
 * Description: Decode a DNS protocol encoded name to a domain name.
 * Parameters:  buf     - pointer to buffer with the encoded name.
 *              buflen  - size of buffer.
 *              offset  - the offset in buffer where the encoded name begins.
 *              name    - pointer to buffer for the decoded result.
 *              namelen - length of result buffer
 * Returns:     The number of parsed bytes or -1 if no name could be found.
 */
IP_STATIC int
ipnet_nat_proxy_dns_decode_name(Ip_u8 *buf, int buflen, int offset, Ip_u8 *name, int namelen)
{
    int i, j, len, prev;
    int parsed   = 0;
    int count    = 0;
    int ptr      = 0;

    i = offset;
    if (buflen - i < 1)
        return -1;                  /* Too little data */
    while (buf[i] != 0)
    {
        len = buf[i];
        if ((len & 0xc0) == 0xc0)
        {
            if (buflen - i < 2)
                return -1;          /* Too little data */
            prev = i;
            i = IP_GET_NTOHS(buf+i) & 0x3fff;
            if (i >= prev)
                return -1;          /* Do not allow forward pointers (avoid loops) */
            if (buflen - i < 1)
                return -1;          /* Too little data */
            if (!ptr)
                parsed += 2;
            ptr = 1;
            continue;
        }
        i++;
        if (buflen - i < len)
            return -1;              /* Too little data */
        for (j=0; j<len; j++)
        {
            if (count < namelen)
                name[count++] = buf[i];
            i++;
        }
        if (count < namelen)
            name[count++] = '.';
        if (buflen - i < 1)
            return -1;              /* Too little data */
        if (!ptr)
            parsed += len + 1;
    }

    if (!ptr)
        parsed++;                   /* Skip null terminator */
    if (count)
        name[count-1] = '\0';
    else
        name[0] = 0;
    return parsed;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_find_transaction
 *===========================================================================
 * Description: Find a transaction in the list of transactions.
 * Parameters:  type    - DNS query type.
 *              dns_hdr - pointer to the DNS protocol header.
 *              param   - pointer to NAT proxy parameters.
 * Returns:     A pointer to transaction if found or else IP_NULL.
 */
IP_STATIC Ipnet_nat_dns_transaction *
ipnet_nat_proxy_dns_find_transaction(int type,
                                     Ipnet_nat_dns_hdr *dns_hdr,
                                     Ipnet_nat_proxy_param *param)
{
    Ipnet_nat_dns_transaction *trans;
    Ip_u16 id;

    id = (Ip_u16)IP_GET_NTOHS(&dns_hdr->id);
    for (trans = IPCOM_LIST_FIRST(&ipnet_nat_proxy_dns_list);
         trans != IP_NULL;
         trans = IPCOM_LIST_NEXT(&trans->list))
    {
        if (id == trans->id
            && param->tuple.private_port == trans->srcport
            && param->tuple.public_addr == trans->dstaddr
            && type == trans->type)
        {
            return trans;
        }
    }

    return IP_NULL;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_h225_msg
 *===========================================================================
 * Description: Track H.225 packets.
 * This function handles the H.225/Q.931 call signalling packets.  NAT calls
 * this function after translating the address and port number in the IP and
 * TCP headers, and when the source or destination port is the H.323 port
 * registered to NAT during the H.323 ALG registration (the standard port for
 * H.323 protocol is 1720).  The H.225 call signalling protocol is used in H.323
 * to establish connection between two end-points.
 *
 * This function must look at both an outbound as well as an inbound packet.
 * H.323 connection can be attempted from local to global endpoint or vice
 * versa.
 *
 * NOTE 1:
 * The fields in the H.225 message are ASN.1 encoded. However, due to schedule,
 * constraint, rather than employing an ASN.1 decoder, this function uses a simple
 * strategy to look for the ip+port address in the H.225 payload by taking advantage
 * of the fact that the port number always follows immediately after the ip address.
 * Since the ALG can get the expected ip address from NAT, it can search byte by
 * byte for this ip address to locate where it is in the H.225 payload..
 *
 * Local host (L) <---------------> NAT <----------------> Global host (G)
 *
 * The tuple of IP address and TCP/UDP port number is sometimes called transport
 * address in some publications, and the same terminology will be used here.
 *
 * Case 1:  Local (L) endpoint to global (G) endpoint connection
 *
 * L starts a TCP connection to G and the H.225 call signaling session starts.
 * The establishment of this TCP connection is all handled by NAT, so by now,
 * NAT should have the bind entry for this connection.  During the H.225 session,
 * L will embed its sourceCallSignalAddress (ip/port) and G's transport address
 * in the H.225 payload to G.  Thus, this function must parse for each H.225
 * outbound packet, look for L's transport address, and substitute it with the
 * translated address obtained from NAT.
 *
 * In addition, this function must also observe the H.225 inbound packets from G
 * to look for the H.245 transport address (ip/port) in the connect message from
 * G.  The port number provided by G here will serve as the H.245 port number.
 * L will use this port number to open the H.245 TCP connection with G.  So upon
 * obtaining the H.245 port number, this function must also register the H.245
 * ALG agent to NAT .
 *
 * Case 2:  Global (G) endpoint to local (L) endpoint connection
 *
 * G starts a TCP connection to L via the NAT's H.323 static entry and the H.225
 * call signaling session starts.  As in case 1, the establishment of this TCP
 * connection is all handled by NAT, so by now, NAT should have the bind entry
 * for this connection.  During the H.225 session, G will embed its sourceCall-
 * SignalAddress and L's global transport address in the H.225 payload.  So, this
 * function must examine all inbound H.225 packets and substitute L's global
 * transport address with its real transport address.
 *
 * In addition, this function must also observe the H.225 outbound packets
 * to look for L's H.245 transport address which is embedded in the connect
 * message from L to G. The port number embedded in this message will serve as
 * the H.245 port number.
 *
 * G will use this port number to open the H.245 TCP connection with L.  So, this
 * function must also register the H.245 ALG agent to NAT upon obtaining the
 * H.245 port number.  Furthermore, since the H.245 TCP connection will be
 * started from G to L, this function must create a new TCP bind entry for the
 * impending H.245 port connection.
 *
 * NOTE 2:
 * TCP sequence adjustment is not required since it is a binary substitution of
 * IP address and port number in the payload.  However, the checksum in the TCP
 * header must be adjusted when the TCP payload is modified.
 *
 * Parameters:  appdata   - pointer to application data.
 *              applen    - pointer to length of application data.
 *              param     - pointer to proxy parameters.
 * Returns:     1 = Packet modified.
 *              0 = Packet untouched.
 *             -1 = Drop packet.
 */
IP_STATIC int
ipnet_nat_proxy_h225_msg(Ip_u8 *appdata,
                         int    applen,
                         Ipnet_nat_proxy_param *param)
{
    Ipnet_nat_proxy_tuple proxy_tuple;
    Ip_u8   *data, *data_start;
    Ip_u16  local_port, port;
    Ip_u32  remote_address, local_address, ip_addr;
    int     data_length, tmp;
    Ip_bool mod = IP_FALSE;

    data_length = applen;       /* length of TCP payload */
    if (data_length <= 0)
    {
        /* no payload to look at */
        return 0;
    }

    /* go to start of TCP payload */
    data = appdata;
    data_start = data;
    local_address  = param->tuple.private_addr;
    local_port     = param->tuple.private_port;
    remote_address = param->tuple.public_addr;

    if (param->incoming == IP_FALSE)    /* outbound packet */
    {
        /* get the H.225 TCP bind descriptor using L's translated source transport
           address from the source address in the TCP/IP header.  From the bind
           descriptor, obtain L's real transport address and the session flow (i.e.
           who starts the connection first) of this connection.
        */
        if (param->inbound == IP_FALSE)     /* session started by L */
        {
            IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_h225_msg() :: outbound packet, outbound session. "
                              "Look for sourceCallSignalAddress from local host.");

            while ((data + 6) <= (data_start + data_length))
            {
                ip_addr = IP_GET_NTOHL(data);

                /* look for L's sourceCallSignalAddress match */
                if (ip_addr == local_address)
                {
                    port = IP_GET_NTOHS(data + 4);

                    if (port == local_port)
                    {
                        /* replace with L's translated sourceCallSignalAdress */
                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                          "Replace local address and port (0x%08x:%d) in H225 payload.",
                                           ip_addr, port);

                        IP_SET_HTONL(data, param->nat_addr);
                        IP_SET_HTONS(data + 4, param->nat_port);
                        data += 6;
                        mod = IP_TRUE;

                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                          "Translated local address and port are (0x%08x:%d)",
                                           param->nat_addr, param->nat_port);
                    }
                    else    /* port != bind_info.local_transport */
                    {
                        data++;

                        IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_h225_msg() :: matched local address found, "
                                          "but not the port number.");
                    }
                }
                else    /* ip_addr != bind_info.local_addr */
                {
                    data++;
                }
            }
        }
        else    /* session started by G */
        {
            IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_h225_msg() :: outbound packet, inbound session. "
                              "Look for H245Address from local host.");

            while ((data + 6) <= (data_start + data_length))
            {
                ip_addr = IP_GET_NTOHL(data);

                /* look for L's H245Address port */
                if (ip_addr == local_address)
                {
                    port = IP_GET_NTOHS(data + 4);

                    IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                      "Found H245Address (0x%08x:%d) in H225 payload.",
                                       ip_addr, port);

                    /* register H.245 ALG to NAT */
                    /* if NAPT, create a H.245 TCP bind entry to prepare for H.245
                       connection request from G */

                    ipcom_memset(&proxy_tuple, 0, sizeof(proxy_tuple));
                    proxy_tuple.protocol     = param->tuple.protocol;
                    proxy_tuple.private_addr = local_address;
                    proxy_tuple.private_port = port;
                    proxy_tuple.public_addr  = remote_address;

                    tmp = ipnet_nat_proxy_add_mapping(&proxy_tuple,
                                                      0,
                                                      param->mapping,
                                                      IP_TRUE,          /* Use port translation */
                                                      IP_TRUE,          /* Inbound session */
                                                      ipnet_nat_proxy_h245,
                                                      IP_NULL);
                    if (tmp < 0)
                    {
                        IPCOM_LOG2(ERR, "ipnet_nat_proxy_h225_msg() :: Failed to add mapping for address = 0x%08x, port = %d",
                                         local_address, port);
                    }
                    else
                    {
                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: Added mapping for address = 0x%08x, port = %d",
                                           local_address, port);
                    }

                    IP_SET_HTONL(data, param->nat_addr);
                    IP_SET_HTONS(data + 4, (Ip_u16)tmp);
                    data += 6;
                    mod = IP_TRUE;

                    IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                      "Translated local address and port are (0x%08x:%d)",
                                       param->nat_addr, tmp);

                }
                else    /* ip_addr != bind_info.local_addr */
                {
                    data++;
                }
            }/* while */
        }   /* else: session started by G */
    }
    else    /* inbound packet */
    {
        if (param->inbound == IP_FALSE)     /* session started by L */
        {

            IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_h225_msg() :: inbound packet, outbound session. "
                              "Look for H245Address from remote host.");

            while ((data + 6) <= (data_start + data_length))
            {
                ip_addr = IP_GET_NTOHL(data);

                /* look for G's H245Address match */
                if (ip_addr == remote_address)
                {
                    port = IP_GET_NTOHS(data + 4);

                    IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                      "H245Address from remote host found (0x%08x:%d).",
                                       ip_addr, port);

                    /* register H.245 ALG to NAT */
                    ipcom_memset(&proxy_tuple, 0, sizeof(proxy_tuple));
                    proxy_tuple.protocol     = param->tuple.protocol;
                    proxy_tuple.public_addr  = ip_addr;
                    proxy_tuple.public_port  = port;
                    proxy_tuple.private_addr = param->tuple.private_addr;

                    if (ipnet_nat_proxy_add_mapping(&proxy_tuple,
                                                    0,
                                                    param->mapping,
                                                    IP_TRUE,          /* Use port translation */
                                                    IP_FALSE,         /* Outbound session */
                                                    ipnet_nat_proxy_h245,
                                                    IP_NULL) < 0)
                    {
                        IPCOM_LOG2(ERR, "ipnet_nat_proxy_h225_msg() :: Failed to add mapping for address = 0x%08x, port = %d",
                                         remote_address, port);
                    }
                    else
                    {
                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: Added mapping for address = 0x%08x, port = %d",
                                           remote_address, port);
                    }
                }
                data++;
            }
        }
        else    /* session started by G */
        {
            /* search for L's transport address in the H225 payload.
               If found, translate it to its real transport address. */
            IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_h225_msg() :: inbound packet, inbound session."
                              "Translate global transport to its local transport.");

            while ((data + 6) <= (data_start + data_length))
            {
                ip_addr = IP_GET_NTOHL(data);

                /* look for L's translated address match */
                if (ip_addr == param->nat_addr)
                {
                    port = IP_GET_NTOHS(data + 4);

                    IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                      "global ip match found in H.225 payload (0x%08x:%d).",
                                       ip_addr, port);

                    if (port == param->nat_port)
                    {
                        IP_SET_HTONL(data, local_address);
                        IP_SET_HTONS(data + 4, local_port);
                        data += 6;
                        mod = IP_TRUE;

                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h225_msg() :: "
                                          "After translation local_address and port are (0x%08x:%d).",
                                           local_address, local_port);
                    }
                    else    /* port != bind_info.global_transport */
                    {
                        data++;
                    }
                }
                else    /* ip_addr != bind_info.global_addr */
                {
                    data++;
                }
            }   /* while */
        }   /* else (session started by G) */
    }

    return mod == IP_TRUE ? 1 : 0;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_h245_msg
 *===========================================================================
 * Description: Track H.245 packets.
 * This function handles the H.245 call control packets.  NAT calls this function
 * after translating the address and port number in the IP and TCP headers, and
 * when the source or destination port is the H.245 port. The H.245 port was
 * negotiated during the H.225 call signaling session and registered to NAT.
 * The H.245 call control protocol is used in H.323 to negotiate call parameters
 * between two end-points.  For example, it negotiates the UDP connections for
 * the open logical channels for RTP and RTCP streams between the two endpoints.
 * In addition, it also negotiates the TCP connection for the T1.120 session.
 *
 * NOTE 1:
 * The H.245 message is ASN.1 encoded. However, due to resource and schedule
 * constraint, rather than employing an ASN.1 decoder, this function uses a simple
 * work-around alternative which appears to work just as well.  This work-around
 * strategy looks for the ip+port address in the H.245 payload by taking advantage
 * of the fact that the port number always follows immediately after the ip address.
 * Since the ALG can get the expected ip address from NAT, it can search byte by
 * byte for this ip address to locate where it is in the H.245 payload.
 *
 * Local host (L) <---------------> NAT <----------------> Global host (G)
 *
 * The tuple of IP address and TCP/UDP port number is sometimes called transport
 * address in some publications, and the same terminology will be used here.
 *
 * For an outbound packet, L may be sending its transport addresses to G to let
 * G make TCP/UDP connections to it.  Similarly, for an inbound packet, G may be
 * sending its transport addresses to L to let L make TCP/UDP connections to it.
 * Since only the L's transport addresses in the payload need to be translated,
 * it is sufficient to examine only the outbound packets.
 *
 * NOTE 2:
 * TCP sequence adjustment is not required since it is a binary substitution of
 * IP address and port number in the payload.  However, the checksum in the TCP
 * header must be adjusted when the TCP payload is modified.
 *
 * Parameters:  appdata   - pointer to application data.
 *              applen    - pointer to length of application data.
 *              param     - pointer to proxy parameters.
 * Returns:     1 = Packet modified.
 *              0 = Packet untouched.
 *             -1 = Drop packet.
 */
IP_STATIC int
ipnet_nat_proxy_h245_msg(Ip_u8 *appdata,
                         int    applen,
                         Ipnet_nat_proxy_param *param)
{
    Ipnet_nat_proxy_tuple proxy_tuple;
    Ip_u8   *data, *data_start;
    Ip_u16  port;
    Ip_u32  local_address, ip_addr;
    int     data_length;
    Ip_bool mod = IP_FALSE;

    if (param->incoming == IP_TRUE)
        return 0;
    data_length = applen;       /* length of TCP payload */
    if (data_length <= 0)
    {
        /* no payload to look at */
        return 0;
    }

    /* go to start of TCP payload */
    data = appdata;
    data_start = data;
    local_address = param->tuple.private_addr;    /* L's local ip */

    /* search for L's ip address in the H.245 payload for all possible
      TCP/UDP connections that may be negotiated.  For each one found, create
      create a static TCP/UDP control block to get the translated transport address.
      We must do this for all L's ip address found in the H.245 payload
      since we are not decoding the ASN.1 message thus don't really know
      which ones will actually be used.  A check is made to prevent creation
      of duplicate bind entries.  The ID of each created static entry is recorded
      so the ALG can delete them when the Netmeeting session is terminated */

    while ((data + 6) <= (data_start + data_length))
    {
        ip_addr = IP_GET_NTOHL(data);

        /* look for L's global address match */
        if (ip_addr == local_address)
        {
            port = IP_GET_NTOHS(data + 4);
            if (port > IPNET_NAT_H323_LOWER_EPHEMERAL_PORT_VALUE)
            {
                IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h245_msg() :: Local host address found in H245 payload. "
                                  "ip = 0x%08x, port = %d", ip_addr, port);

                /* Do not add a mapping for the TCP T1.120 port here. Should be taken care of by NAT rules */
                if (port != IPNET_NAT_T1_120_PORT)
                {
                    /* create a bind entry for this transport address if it hasn't been created yet */
                    ipcom_memset(&proxy_tuple, 0, sizeof(proxy_tuple));
                    proxy_tuple.protocol     = IP_IPPROTO_UDP;
                    proxy_tuple.private_addr = local_address;
                    proxy_tuple.private_port = port;
                    proxy_tuple.public_addr  = param->tuple.public_addr;
                    if (ipnet_nat_proxy_add_mapping(&proxy_tuple,
                                                    IPNET_NAT_H323_MEDIA_TIMEOUT,
                                                    param->mapping,
                                                    IP_FALSE,         /* NAT port = local source port */
                                                    IP_TRUE,          /* Inbound session */
                                                    IP_NULL,
                                                    IP_NULL) < 0)
                    {
                        IPCOM_LOG2(ERR, "ipnet_nat_proxy_h245_msg() :: Failed to add mapping for address = 0x%08x, port = %d",
                                         local_address, port);
                    }
                    else
                    {
                        IPCOM_LOG2(DEBUG, "ipnet_nat_proxy_h245_msg() :: Added mapping for address = 0x%08x, port = %d",
                                           local_address, port);
                    }
                }

                /* Replace L's local address with its global address in the payload */
                IP_SET_HTONL(data, param->nat_addr);
                data += 6;
                mod = IP_TRUE;

                IPCOM_LOG1(DEBUG, "ipnet_nat_proxy_h245_msg() :: "
                                  "Translated local address is (0x%08x)",
                                  param->nat_addr);
            }
            else    /* detected port number is not a valid ephemeral port value */
            {
                IPCOM_LOG1(INFO, "ipnet_nat_proxy_h245_msg() :: detected port invalid, ignore! port = %d", port);
                data++;
            }
        }
        else    /* ip_addr != local_address */
        {
            data++;
        }
    }   /* end while loop */

    return mod == IP_TRUE ? 1 : 0;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_request
 *===========================================================================
 * Description: Parse and modify DNS request.
 * Parameters:  appdata   - pointer to application data.
 *              applen    - pointer to length of application data.
 *              growspace - space available to extend application data.
 *              param     - pointer to proxy parameters.
 *              newdata   - pointer to pointer to new application data.
 * Returns:     1 = Packet modified.
 *              0 = Packet untouched.
 */
IP_STATIC int
ipnet_nat_proxy_dns_request(Ip_u8 *appdata,
                            int   *applen,
                            int    growspace,
                            Ipnet_nat_proxy_param *param,
                            Ip_u8 **newdata)
{
    Ipnet_nat_dns_hdr *dns_hdr;
    Ip_u16 flags;
    int offset, diff, qlen, remainder;
    int newbuflen = sizeof(dnsbuf);
    int newlen    = 0;

    /* Check that at least a header is included */
    if (*applen < (int)sizeof(*dns_hdr))
    {
        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_request() :: message too short for dns header");
        return 0;
    }
    dns_hdr = (Ipnet_nat_dns_hdr *)appdata;
    flags = IP_GET_NTOHS(&dns_hdr->flags);
    if((flags & IPNET_NAT_DNS_QR_FLAG)     != 0 ||  /* Not a request */
       (flags & IPNET_NAT_DNS_OPCODE_FLAG) != 0 ||  /* Not a standard query */
       (flags & IPNET_NAT_DNS_TC_FLAG)     != 0)    /* Truncated */
    {
        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_request() :: invalid header");
        return 0;
    }

    /* Copy the header */
    if (newbuflen - newlen < (int)sizeof(*dns_hdr))
    {
        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_request() :: no space left in modified buffer");
        return 0;
    }
    ipcom_memcpy(&dnsbuf[newlen], dns_hdr, sizeof(*dns_hdr));
    newlen += sizeof(*dns_hdr);

    /* Get the questions */
    offset = sizeof(*dns_hdr);
    qlen   = ipnet_nat_proxy_dns_parse_questions(appdata, *applen, offset, &newlen, dns_hdr, param);
    if (qlen < 0)
        return 0;
    offset += qlen;

    /* Copy the rest of the message */
    remainder = *applen - offset;
    if (remainder)
    {
        if (newbuflen - newlen < remainder)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_request() :: no space left in modified buffer");
            return 0;
        }
        ipcom_memcpy(&dnsbuf[newlen], appdata+offset, remainder);
        newlen += remainder;
        offset += remainder;
    }

    /* Update application data with the modified buffer */
    diff = newlen - *applen;
    if (diff > growspace)
    {
        /* Must allocate a new buffer */
        *newdata = ipcom_malloc(*applen + diff);
        if (*newdata == IP_NULL)
        {
            IPCOM_LOG1(ERR, "ipnet_nat_proxy_dns_request() :: ipcom_malloc(%d) failed",
                            *applen + diff);
            return -1;
        }
        ipcom_memcpy(*newdata, dnsbuf, newlen);
    }
    else
    {
        /* Let the current buffer grow */
        ipcom_memcpy(appdata, dnsbuf, newlen);
    }
    *applen = newlen;
    return 1;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_parse_answers
 *===========================================================================
 * Description: Parses and modifies DNS answers in a DNS packet.
 * Parameters:  buf     - pointer to buffer with the DNS questions.
 *              buflen  - length of buffer with DNS questions.
 *              offset  - offset in the buffer where the DNS questions begin.
 *              newlen  - pointer to the length of the message if modified.
 *              dns_hdr - pointer to the DNS protocol header.
 *              param   - pointer to NAT proxy parameters.
 * Returns:     The number of bytes parsed or -1 if failed.
 */
IP_STATIC Ip_s32
ipnet_nat_proxy_dns_parse_answers(Ip_u8 *buf,
                              int buflen,
                              int offset,
                              int *newlen,
                              Ipnet_nat_dns_hdr *dns_hdr,
                              Ipnet_nat_proxy_param *param)
{
    int i, count, origoffset, numa;
    Ip_u16 type, cla, rlen;
    int newbuflen = sizeof(dnsbuf);
    Ipnet_nat_dns_transaction *trans;

    numa = IP_GET_NTOHS(&dns_hdr->no_answ);
    origoffset = offset;
    for (i=0; i<numa; i++)
    {
        /* Get the name */
        count = ipnet_nat_proxy_dns_decode_name(buf, buflen, offset, dnsname, sizeof(dnsname));
        if (count < 0)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: could not decode dns name");
            return -1;
        }

        /* Copy the name */
        if (newbuflen - *newlen < count)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        ipcom_memcpy(&dnsbuf[*newlen], buf+offset, count);
        *newlen += count;
        offset  += count;

        /* Check space for type, class, ttl and record length */
        if (buflen - offset < 10)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: message too short to parse answer");
            return -1;
        }

        /* Get the type */
        type = (Ip_u16)(IP_GET_NTOHS(buf+offset));
        switch(type)
        {
            case IPNET_NAT_DNS_QTYPE_A:
                type = IPNET_NAT_DNS_QTYPE_AAAA;
                break;
            case IPNET_NAT_DNS_QTYPE_PTR:
                trans = ipnet_nat_proxy_dns_find_transaction(IPNET_NAT_DNS_QTYPE_PTR, dns_hdr, param);
                if (trans != IP_NULL)
                {
                    IPCOM_LOG4(DEBUG, "ipnet_nat_proxy_dns_parse_answers() :: found transaction:"
                                      "id=%d port=%d addr=0x%08x type=%d",
                                       trans->id, trans->srcport, trans->dstaddr, trans->type);
                    *newlen -= count;   /* Move index back to before the name */
                    count = ipnet_nat_proxy_dns_encode_name(dnsbuf, newbuflen, *newlen, trans->ptrname);
                    if (count < 0)
                    {
                        ipnet_nat_proxy_dns_remove_transaction(trans);
                        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: could not encode dns name");
                        return -1;
                    }
                    *newlen += count;
                    if (i+1 == numa)
                    {
                        /* Remove transaction for last answer */
                        ipnet_nat_proxy_dns_remove_transaction(trans);
                    }
                }
                break;
            default:
                break;
        }
        /* Copy the type */
        if (newbuflen - *newlen < 2)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        IP_SET_HTONS(&dnsbuf[*newlen], type);
        *newlen += 2;
        offset  += 2;

        /* Get the class */
        cla = (Ip_u16)(IP_GET_NTOHS(buf+offset));
        if (cla != IPNET_NAT_DNS_QCLASS_INET)
        {
            IPCOM_LOG1(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: unhandled class: %d", cla);
            return -1;
        }
        /* Copy the class */
        if (newbuflen - *newlen < 2)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        IP_SET_HTONS(&dnsbuf[*newlen], cla);
        *newlen += 2;
        offset  += 2;

        /* Copy the ttl */
        if (newbuflen - *newlen < 4)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        ipcom_memcpy(&dnsbuf[*newlen], buf+offset, 4);
        *newlen += 4;
        offset  += 4;

        /* Get the record length */
        rlen = (Ip_u16)(IP_GET_NTOHS(buf+offset));
        if (buflen - offset < rlen)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: message too short to parse answer");
            return -1;
        }

        /* Copy the record length */
        if (newbuflen - *newlen < 2)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        if (type == IPNET_NAT_DNS_QTYPE_AAAA && rlen == 4)
        {
            /* Modify record length and make space for the AAAA record */
            IP_SET_HTONS(&dnsbuf[*newlen], 16);
            *newlen += 2;
            /* Insert AAAA record data  */
            if (newbuflen - *newlen < 12)
            {
                IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
                return -1;
            }
            ipcom_memcpy(&dnsbuf[*newlen], param->prefix, 12);
            *newlen += 12;
        }
        else
        {
            IP_SET_HTONS(&dnsbuf[*newlen], rlen);
            *newlen += 2;
        }
        offset += 2;

        /* Copy the record */
        if (newbuflen - *newlen < rlen)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_answers() :: no space left in modified buffer");
            return -1;
        }
        ipcom_memcpy(&dnsbuf[*newlen], buf+offset, rlen);
        *newlen += rlen;
        offset  += rlen;
    }

    return offset - origoffset;
}
/*
 *===========================================================================
 *                    ipnet_nat_proxy_dns_parse_questions
 *===========================================================================
 * Description: Parses and modifies DNS questions in a DNS packet.
 * Parameters:  buf     - pointer to buffer with the DNS questions.
 *              buflen  - length of buffer with DNS questions.
 *              offset  - offset in the buffer where the DNS questions begin.
 *              newlen  - pointer to the length of the message if modified.
 *              dns_hdr - pointer to the DNS protocol header.
 *              param   - pointer to NAT proxy parameters.
 * Returns:     The number of bytes parsed or -1 if failed.
 */
IP_STATIC Ip_s32
ipnet_nat_proxy_dns_parse_questions(Ip_u8 *buf,
                                int buflen,
                                int offset,
                                int *newlen,
                                Ipnet_nat_dns_hdr *dns_hdr,
                                Ipnet_nat_proxy_param *param)
{
    int i, j, k, count, origoffset, numq, numa, request;
    Ip_u16 type, cla, flags;
    Ip_u8 addr[4];
    Ipnet_nat_dns_transaction *trans;
    int newbuflen = sizeof(dnsbuf);
    Ip_u8 c, *zone;

    numq = IP_GET_NTOHS(&dns_hdr->no_ques);
    if (numq != 1)
    {
        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: supports only one question per message");
        return -1;
    }
    numa = IP_GET_NTOHS(&dns_hdr->no_answ);
    flags = IP_GET_NTOHS(&dns_hdr->flags);
    request = (flags & IPNET_NAT_DNS_QR_FLAG) != 0 ? 0 : 1;
    origoffset = offset;
    for (i=0; i<numq; i++)
    {
        /* Get the name */
        count = ipnet_nat_proxy_dns_decode_name(buf, buflen, offset, dnsname, sizeof(dnsname));
        if (count < 0)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: could not decode dns name");
            return -1;
        }

        /* Copy the name */
        if (newbuflen - *newlen < count)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: no space left in modified buffer");
            return -1;
        }
        ipcom_memcpy(&dnsbuf[*newlen], buf+offset, count);
        *newlen += count;
        offset += count;

        /* Check space for type and class */
        if (buflen - offset < 4)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: message too short to parse question");
            return -1;
        }

        /* Get the type */
        type = (Ip_u16)(IP_GET_NTOHS(buf+offset));
        switch(type)
        {
            case IPNET_NAT_DNS_QTYPE_AAAA:
                if (request)
                {
                    trans = ipnet_nat_proxy_dns_add_transaction(IPNET_NAT_DNS_QTYPE_A, dns_hdr, param, IP_NULL);
                    if (trans == IP_NULL)
                    {
                        IPCOM_LOG0(ERR, "ipnet_nat_proxy_dns_parse_questions() :: could not add transaction to list");
                        return -1;
                    }
                    else
                    {
                        IPCOM_LOG4(DEBUG, "ipnet_nat_proxy_dns_parse_questions() :: added transaction:"
                                          "id=%d port=%d addr=0x%08x type=%d",
                                           trans->id, trans->srcport, trans->dstaddr, trans->type);

                    }
                    type = IPNET_NAT_DNS_QTYPE_A;      /* Change type to A */
                }
                break;
            case IPNET_NAT_DNS_QTYPE_A:
                if (!request)
                {
                    trans = ipnet_nat_proxy_dns_find_transaction(IPNET_NAT_DNS_QTYPE_A, dns_hdr, param);
                    if (trans != IP_NULL)
                    {
                        IPCOM_LOG4(DEBUG, "ipnet_nat_proxy_dns_parse_questions() :: found transaction:"
                                          "id=%d port=%d addr=0x%08x type=%d",
                                           trans->id, trans->srcport, trans->dstaddr, trans->type);
                        ipnet_nat_proxy_dns_remove_transaction(trans);
                    }
                    else
                    {
                        return -1;
                    }
                    type = IPNET_NAT_DNS_QTYPE_AAAA;   /* Change type back to AAAA */
                }
                break;
            case IPNET_NAT_DNS_QTYPE_PTR:
                if (request)
                {
                    zone = (Ip_u8 *)ipcom_strstr((char *)dnsname, "ip6.int");
                    if (zone == IP_NULL)
                        zone = (Ip_u8 *)ipcom_strstr((char *)dnsname, "ip6.arpa");
                    if (zone == IP_NULL)
                    {
                        IPCOM_LOG0(DEBUG, "ipnet_nat_proxy_dns_parse_questions() :: unhandled zone in PTR request");
                        return -1;
                    }
                    /* Extract IPv4 part */
                    if (ipcom_strlen((char *)dnsname) != 64 + ipcom_strlen((char *)zone))
                    {
                        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: invalid name in PTR request");
                        return -1;
                    }
                    k=0;
                    for (j=3; j>=0; j--)
                    {
                        c = ipcom_tolower(dnsname[k]);
                        c = c > '9' ? c - 'a' + 10 : c - '0';
                        addr[j] = c;
                        k += 2;
                        c = ipcom_tolower(dnsname[k]);
                        c = c > '9' ? c - 'a' + 10 : c - '0';
                        c <<= 4;
                        addr[j] += c;
                        k += 2;
                    }

                    trans = ipnet_nat_proxy_dns_add_transaction(IPNET_NAT_DNS_QTYPE_PTR, dns_hdr, param, dnsname);
                    if (trans == IP_NULL)
                    {
                        IPCOM_LOG0(ERR, "ipnet_nat_proxy_dns_parse_questions() :: could not add transaction to list");
                        return -1;
                    }
                    else
                    {
                        IPCOM_LOG4(DEBUG, "ipnet_nat_proxy_dns_parse_questions() :: added transaction:"
                                          "id=%d port=%d addr=0x%08x type=%d",
                                           trans->id, trans->srcport, trans->dstaddr, trans->type);
                    }

                    /* Convert address to PTR name */
                    if (ipnet_nat_proxy_dns_ptr_name(dnsname, sizeof(dnsname), addr, IP_AF_INET, (Ip_u8 *)"in-addr.arpa") < 0)
                    {
                        ipnet_nat_proxy_dns_remove_transaction(trans);
                        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: could not encode PTR name");
                        return -1;
                    }
                    *newlen -= count;   /* Move index back to before the name */
                    count = ipnet_nat_proxy_dns_encode_name(dnsbuf, newbuflen, *newlen, dnsname);
                    if (count < 0)
                    {
                        ipnet_nat_proxy_dns_remove_transaction(trans);
                        IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: could not encode dns name");
                        return -1;
                    }
                    *newlen += count;
                }
                else
                {
                    trans = ipnet_nat_proxy_dns_find_transaction(IPNET_NAT_DNS_QTYPE_PTR, dns_hdr, param);
                    if (trans != IP_NULL)
                    {
                        IPCOM_LOG4(DEBUG, "ipnet_nat_proxy_dns_parse_questions() :: found transaction:"
                                          "id=%d port=%d addr=0x%08x type=%d",
                                           trans->id, trans->srcport, trans->dstaddr, trans->type);
                        *newlen -= count;   /* Move index back to before the name */
                        count = ipnet_nat_proxy_dns_encode_name(dnsbuf, newbuflen, *newlen, trans->ptrname);
                        if (count < 0)
                        {
                            ipnet_nat_proxy_dns_remove_transaction(trans);
                            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: could not encode dns name");
                            return -1;
                        }
                        *newlen += count;
                        if (numa == 0)
                        {
                            /* Remove the transaction if there are no answers */
                            ipnet_nat_proxy_dns_remove_transaction(trans);
                        }
                    }
                    else
                    {
                        return -1;
                    }
                }
                break;
            default:
                return -1;
        }
        /* Copy the type */
        if (newbuflen - *newlen < 2)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: no space left in modified buffer");
            return -1;
        }
        IP_SET_HTONS(&dnsbuf[*newlen], type);
        *newlen += 2;
        offset += 2;

        /* Get the class */
        cla = (Ip_u16)(IP_GET_NTOHS(buf+offset));
        if (cla != IPNET_NAT_DNS_QCLASS_INET)
        {
            IPCOM_LOG1(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: unhandled class: %d", cla);
            return -1;
        }
        /* Copy the class */
        if (newbuflen - *newlen < 2)
        {
            IPCOM_LOG0(WARNING, "ipnet_nat_proxy_dns_parse_questions() :: no space left in modified buffer");
            return -1;
        }
        IP_SET_HTONS(&dnsbuf[*newlen], cla);
        *newlen += 2;
        offset += 2;
    }

    return offset - origoffset;
}