/*
* ftpportmapper.{cc,hh} -- IPMapper for FTP PORT commands
* Eddie Kohler
*
* Copyright (c) 2000 Massachusetts Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include "ftpportmapper.hh"
#include <clicknet/ip.h>
#include <clicknet/tcp.h>
#include <click/router.hh>
#include <click/elemfilter.hh>
#include <click/confparse.hh>
#include <click/error.hh>
CLICK_DECLS
FTPPortMapper::FTPPortMapper()
: _pattern(0)
{
}
FTPPortMapper::~FTPPortMapper()
{
}
int
FTPPortMapper::configure(Vector<String> &conf, ErrorHandler *errh)
{
if (conf.size() != 3)
return errh->error("wrong number of arguments; expected `FTPPortMapper(element, pattern)'");
// get control packet rewriter
Element *e = cp_element(conf[0], this, errh);
if (!e)
return -1;
_control_rewriter = (TCPRewriter *)e->cast("TCPRewriter");
if (!_control_rewriter)
return errh->error("first argument must be a TCPRewriter-like element");
// get data packet rewriter
e = cp_element(conf[1], this, errh);
if (!e)
return -1;
_data_rewriter = (IPRw *)e->cast("IPRw");
if (!_data_rewriter)
return errh->error("second argument must be an IPRewriter-like element");
_pattern = 0;
if (IPRw::Pattern::parse_with_ports(conf[2], &_pattern, &_forward_port,
&_reverse_port, this, errh) < 0)
return -1;
_pattern->use();
if (_data_rewriter->notify_pattern(_pattern, errh) < 0)
return -1;
if (_forward_port >= _data_rewriter->noutputs()
|| _reverse_port >= _data_rewriter->noutputs())
return errh->error("port out of range for `%s'", _data_rewriter->declaration().c_str());
else
return 0;
}
int
FTPPortMapper::initialize(ErrorHandler *errh)
{
// make sure that _control_rewriter is downstream
CastElementFilter filter("TCPRewriter");
Vector<Element *> elts;
router()->downstream_elements(this, 0, &filter, elts);
filter.filter(elts);
for (int i = 0; i < elts.size(); i++)
if (elts[i] == _control_rewriter)
goto found_control_rewriter;
errh->warning("control packet rewriter `%s' is not downstream", _control_rewriter->declaration().c_str());
found_control_rewriter:
return 0;
}
void
FTPPortMapper::cleanup(CleanupStage)
{
if (_pattern)
_pattern->unuse();
}
Packet *
FTPPortMapper::simple_action(Packet *p)
{
const click_ip *iph = p->ip_header();
assert(iph->ip_p == IP_PROTO_TCP);
const click_tcp *tcph = p->tcp_header();
unsigned data_offset = p->transport_header_offset() + (tcph->th_off<<2);
const unsigned char *data = p->data() + data_offset;
unsigned len = p->length() - data_offset;
if (len < 4
|| (data[0] != 'P' && data[0] != 'p')
|| (data[1] != 'O' && data[1] != 'o')
|| (data[2] != 'R' && data[2] != 'r')
|| (data[3] != 'T' && data[3] != 't')
|| data[4] != ' ')
return p;
// parse the PORT command
unsigned pos = 5;
while (pos < len && data[pos] == ' ')
pos++;
unsigned port_arg_offset = pos;
// followed by 6 decimal numbers separated by commas
unsigned nums[6];
nums[0] = nums[1] = nums[2] = nums[3] = nums[4] = nums[5] = 0;
int which_num = 0;
while (pos < len && which_num < 6) {
if (data[pos] >= '0' && data[pos] <= '9')
nums[which_num] = (nums[which_num] * 10) + data[pos] - '0';
else if (data[pos] == ',')
which_num++;
else
break;
pos++;
}
// check that the command was complete and the numbers are ok
if (which_num != 5 || pos >= len || (data[pos] != '\r' && data[pos] != '\n'))
return p;
for (int i = 0; i < 6; i++)
if (nums[i] >= 256)
return p;
// OK; create the IP address and port number
IPAddress src_data_addr(htonl((nums[0]<<24) | (nums[1]<<16) | (nums[2]<<8) | nums[3]));
unsigned src_data_port = htons((nums[4]<<8) | nums[5]);
// add mapping from specified port to destination
unsigned dst_data_port = htons(ntohs(tcph->th_dport) - 1);
IPFlowID flow(src_data_addr, src_data_port,
IPAddress(iph->ip_dst), dst_data_port);
// check for existing mapping
IPRw::Mapping *forward = _data_rewriter->get_mapping(IP_PROTO_TCP, flow);
if (!forward) {
// create new mapping
forward = _data_rewriter->apply_pattern(_pattern, IP_PROTO_TCP, flow,
_forward_port, _reverse_port);
if (!forward)
return p;
}
// rewrite PORT command to reflect mapping
IPFlowID new_flow = forward->flow_id();
unsigned new_saddr = ntohl(new_flow.saddr().addr());
unsigned new_sport = ntohs(new_flow.sport());
char buf[30];
unsigned buflen;
buflen = sprintf(buf, "%d,%d,%d,%d,%d,%d", (new_saddr>>24)&255, (new_saddr>>16)&255,
(new_saddr>>8)&255, new_saddr&255, (new_sport>>8)&255, new_sport&255);
//click_chatter("%s", buf);
WritablePacket *wp;
unsigned port_arg_len = pos - port_arg_offset;
if (port_arg_len < buflen) {
wp = p->put(buflen - port_arg_len);
} else {
wp = p->uniqueify();
wp->take(port_arg_len - buflen);
}
memmove(wp->data() + data_offset + port_arg_offset + buflen,
wp->data() + data_offset + port_arg_offset + port_arg_len,
len - pos);
memcpy(wp->data() + data_offset + port_arg_offset,
buf,
buflen);
// set IP length field, incrementally update IP checksum according to RFC1624
// new_sum = ~(~old_sum + ~old_halfword + new_halfword)
click_ip *wp_iph = wp->ip_header();
unsigned short old_ip_hw = ((unsigned short *)wp_iph)[1];
wp_iph->ip_len = htons(wp->length() - wp->ip_header_offset());
unsigned short new_ip_hw = ((unsigned short *)wp_iph)[1];
unsigned ip_sum =
(~wp_iph->ip_sum & 0xFFFF) + (~old_ip_hw & 0xFFFF) + new_ip_hw;
while (ip_sum >> 16) // XXX necessary?
ip_sum = (ip_sum & 0xFFFF) + (ip_sum >> 16);
wp_iph->ip_sum = ~ip_sum;
// set TCP checksum
// XXX should check old TCP checksum first!!!
click_tcp *wp_tcph = wp->tcp_header();
// update sequence numbers in old mapping
IPFlowID p_flow(p);
if (TCPRewriter::TCPMapping *p_mapping = _control_rewriter->get_mapping(IP_PROTO_TCP, p_flow)) {
tcp_seq_t interesting_seqno = ntohl(wp_tcph->th_seq) + len;
p_mapping->update_seqno_delta(interesting_seqno, buflen - port_arg_len);
} else
click_chatter("%{element}: control packet with no mapping", this);
wp_tcph->th_sum = 0;
unsigned wp_tcp_len = wp->length() - wp->transport_header_offset();
unsigned csum = click_in_cksum((unsigned char *)wp_tcph, wp_tcp_len);
wp_tcph->th_sum = click_in_cksum_pseudohdr(csum, wp_iph, wp_tcp_len);
return wp;
}
CLICK_ENDDECLS
ELEMENT_REQUIRES(TCPRewriter)
EXPORT_ELEMENT(FTPPortMapper)
syntax highlighted by Code2HTML, v. 0.9.1