Index: head/sbin/ipfw/ipfw.8 =================================================================== --- head/sbin/ipfw/ipfw.8 (revision 37408) +++ head/sbin/ipfw/ipfw.8 (revision 37409) @@ -1,530 +1,554 @@ .Dd July 20, 1996 .Dt IPFW 8 SMM .Os FreeBSD .Sh NAME .Nm ipfw .Nd controlling utility for IP firewall .Sh SYNOPSIS .Nm .Ar file .Nm ipfw .Oo .Fl f | .Fl q .Oc flush .Nm ipfw .Oo .Fl q .Oc zero .Op Ar number ... .Nm ipfw delete .Ar number ... .Nm ipfw .Op Fl aftN list .Op Ar number ... .Nm ipfw .Oo .Fl ftN .Oc show .Op Ar number ... .Nm ipfw .Oo .Fl q .Oc add .Op Ar number .Ar action .Op log .Ar proto from .Ar src to .Ar dst .Op via Ar name | ipno .Op Ar options .Sh DESCRIPTION If used as shown in the first synopsis line, the .Ar file will be read line by line and applied as arguments to the .Nm command. .Pp The .Nm code works by going through the rule-list for each packet, until a match is found. All rules have two associated counters, a packet count and a byte count. These counters are updated when a packet matches the rule. .Pp The rules are ordered by a ``line-number'' from 1 to 65534 that is used to order and delete rules. Rules are tried in increasing order, and the first rule that matches a packet applies. Multiple rules may share the same number and apply in the order in which they were added. .Pp If a rule is added without a number, it is numbered 100 higher than the previous rule. If the highest defined rule number is greater than 65534, new rules are appended to the last rule. .Pp The delete operation deletes the first rule with number .Ar number , if any. .Pp The list command prints out the current rule set. .Pp The show command is equivalent to `ipfw -a list'. .Pp The zero operation zeroes the counters associated with rule number .Ar number . .Pp The flush operation removes all rules. .Pp Any command beginning with a '#', or being all blank, is ignored. .Pp One rule is always present: .Bd -literal -offset center 65535 deny all from any to any .Ed .Pp This rule is the default policy, i.e., don't allow anything at all. Your job in setting up rules is to modify this policy to match your needs. .Pp However, if the kernel option .Dq IPFIREWALL_DEFAULT_TO_ACCEPT is active, the rule is instead: .Bd -literal -offset center 65535 allow all from any to any .Ed .Pp This variation lets everything pass through. This option should only be activated in particular circumstances, such as if you use the firewall system as an on-demand denial-of-service filter that is normally wide open. .Pp The following options are available: .Bl -tag -width flag .It Fl a While listing, show counter values. See also ``show'' command. .It Fl f Don't ask for confirmation for commands that can cause problems if misused (ie; flush). .Ar Note , if there is no tty associated with the process, this is implied. .It Fl q While adding, zeroing or flushing, be quiet about actions (implies '-f'). This is useful for adjusting rules by executing multiple ipfw commands in a script (e.g. sh /etc/rc.firewall), or by processing a file of many ipfw rules, across a remote login session. If a flush is performed in normal (verbose) mode (with the default kernel configuration), it prints a message. Because all rules are flushed, the message cannot be delivered to the login session, the login session is closed and the remainder of the ruleset is not processed. Access to the console is required to recover. .It Fl t While listing, show last match timestamp. .It Fl N Try to resolve addresses and service names in output. .El .Pp .Ar action : .Bl -hang -offset flag -width 1234567890123456 .It Ar allow Allow packets that match rule. The search terminates. Aliases are .Ar pass , .Ar permit , and .Ar accept . .It Ar deny Discard packets that match this rule. The search terminates. .Ar Drop is an alias for .Ar deny . .It Ar reject (Deprecated.) Discard packets that match this rule, and try to send an ICMP host unreachable notice. The search terminates. .It Ar unreach code Discard packets that match this rule, and try to send an ICMP unreachable notice with code .Ar code , where .Ar code is a number from zero to 255, or one of these aliases: .Ar net , .Ar host , .Ar protocol , .Ar port , .Ar needfrag , .Ar srcfail , .Ar net-unknown , .Ar host-unknown , .Ar isolated , .Ar net-prohib , .Ar host-prohib , .Ar tosnet , .Ar toshost , .Ar filter-prohib , .Ar host-precedence , or .Ar precedence-cutoff . The search terminates. .It Ar reset TCP packets only. Discard packets that match this rule, and try to send a TCP reset (RST) notice. The search terminates. .It Ar count Update counters for all packets that match rule. The search continues with the next rule. .It Ar divert port Divert packets that match this rule to the .Xr divert 4 socket bound to port .Ar port . The search terminates. .It Ar tee port Send a copy of packets matching this rule to the .Xr divert 4 socket bound to port .Ar port . -The search continues with the next rule. +The search continues with the next rule. This feature is not yet implemeted. +.It Ar fwd ipaddr Op ,port +Change the next-hop on matching packets to +.Ar ipaddr , +which can be an IP address in dotted quad or a host name. +If +.Ar ipaddr +is not a directly-reachable address, the route +as found in the local routing table for that IP is used +instead. +If +.Ar ipaddr +is a local address, then on a packet entering the system from a remote +host it will be diverted to +.Ar port +on the local machine, keeping the local address of the socket set +to the original IP address the packet was destined for. This is intended +for use with transparent proxy servers. If the IP is not +a local address then the port number (if specified) is ignored and +the rule only applies to packets leaving the system. This will +also map addresses to local ports when packets are generated locally. +The search terminates if this rule matches. If the port number is not +given then the port number in the packet is used, so that a packet for +an external machine port Y would be forwarded to local port Y. The kernel +must have been compiled with optiions IPFIREWALL_FORWARD. .It Ar skipto number Skip all subsequent rules numbered less than .Ar number . The search continues with the first rule numbered .Ar number or higher. .El .Pp If a packet matches more than one .Ar divert and/or .Ar tee rule, all but the last are ignored. .Pp If the kernel was compiled with .Dv IPFIREWALL_VERBOSE , then when a packet matches a rule with the ``log'' keyword a message will be printed on the console. If the kernel was compiled with the .Dv IPFIREWALL_VERBOSE_LIMIT option, then logging will cease after the number of packets specified by the option are received for that particular chain entry. Logging may then be re-enabled by clearing the packet counter for that entry. .Pp Console logging and the log limit are adjustable dynamically through the .Xr sysctl 8 interface. .Pp .Ar proto : .Bl -hang -offset flag -width 1234567890123456 .It Ar ip All packets match. The alias .Ar all has the same effect. .It Ar tcp Only TCP packets match. .It Ar udp Only UDP packets match. .It Ar icmp Only ICMP packets match. .It Ar Only packets for the specified protocol matches (see .Pa /etc/protocols for a complete list). .El .Pp .Ar src and .Ar dst : .Bl -hang -offset flag .It Ar
.Op Ar ports .El .Pp The .Em
may be specified as: .Bl -hang -offset flag -width 1234567890123456 .It Ar ipno An ipnumber of the form 1.2.3.4. Only this exact ip number match the rule. .It Ar ipno/bits An ipnumber with a mask width of the form 1.2.3.4/24. In this case all ip numbers from 1.2.3.0 to 1.2.3.255 will match. .It Ar ipno:mask An ipnumber with a mask width of the form 1.2.3.4:255.255.240.0. In this case all ip numbers from 1.2.0.0 to 1.2.15.255 will match. .El .Pp The sense of the match can be inverted by preceding an address with the ``not'' modifier, causing all other addresses to be matched instead. This does not affect the selection of port numbers. .Pp With the TCP and UDP protocols, optional .Em ports may be specified as: .Pp .Bl -hang -offset flag .It Ns {port|port-port} Ns Op ,port Ns Op ,... .El .Pp Service names (from .Pa /etc/services ) may be used instead of numeric port values. A range may only be specified as the first value, and the length of the port list is limited to .Dv IP_FW_MAX_PORTS (as defined in .Pa /usr/src/sys/netinet/ip_fw.h ) ports. .Pp Fragmented packets which have a non-zero offset (i.e. not the first fragment) will never match a rule which has one or more port specifications. See the .Ar frag option for details on matching fragmented packets. .Pp Rules can apply to packets when they are incoming, or outgoing, or both. The .Ar in keyword indicates the rule should only match incoming packets. The .Ar out keyword indicates the rule should only match outgoing packets. .Pp To match packets going through a certain interface, specify the interface using .Ar via : .Bl -hang -offset flag -width 1234567890123456 .It Ar via ifX Packet must be going through interface .Ar ifX. .It Ar via if* Packet must be going through interface .Ar ifX , where X is any unit number. .It Ar via any Packet must be going through .Em some interface. .It Ar via ipno Packet must be going through the interface having IP address .Ar ipno . .El .Pp The .Ar via keyword causes the interface to always be checked. If .Ar recv or .Ar xmit is used instead of .Ar via , then the only receive or transmit interface (respectively) is checked. By specifying both, it is possible to match packets based on both receive and transmit interface, e.g.: .Pp .Dl "ipfw add 100 deny ip from any to any out recv ed0 xmit ed1" .Pp The .Ar recv interface can be tested on either incoming or outgoing packets, while the .Ar xmit interface can only be tested on outgoing packets. So .Ar out is required (and .Ar in invalid) whenver .Ar xmit is used. Specifying .Ar via together with .Ar xmit or .Ar recv is invalid. .Pp A packet may not have a receive or transmit interface: packets originating from the local host have no receive interface. while packets destined for the local host have no transmit interface. .Pp Additional .Ar options : .Bl -hang -offset flag -width 1234567890123456 .It frag Matches if the packet is a fragment and this is not the first fragment of the datagram. .Ar frag may not be used in conjunction with either .Ar tcpflags or TCP/UDP port specifications. .It in Matches if this packet was on the way in. .It out Matches if this packet was on the way out. .It ipoptions Ar spec Matches if the IP header contains the comma separated list of options specified in .Ar spec . The supported IP options are: .Ar ssrr (strict source route), .Ar lsrr (loose source route), .Ar rr (record packet route), and .Ar ts (timestamp). The absence of a particular option may be denoted with a ``!''. .It established Matches packets that have the RST or ACK bits set. TCP packets only. .It setup Matches packets that have the SYN bit set but no ACK bit. TCP packets only. .It tcpflags Ar spec Matches if the TCP header contains the comma separated list of flags specified in .Ar spec . The supported TCP flags are: .Ar fin , .Ar syn , .Ar rst , .Ar psh , .Ar ack , and .Ar urg . The absence of a particular flag may be denoted with a ``!''. A rule which contains a .Ar tcpflags specification can never match a fragmented packet which has a non-zero offset. See the .Ar frag option for details on matching fragmented packets. .It icmptypes Ar types Matches if the ICMP type is in the list .Ar types . The list may be specified as any combination of ranges or individual types separated by commas. .El .Sh CHECKLIST Here are some important points to consider when designing your rules: .Bl -bullet -hang -offset flag .It Remember that you filter both packets going in and out. Most connections need packets going in both directions. .It Remember to test very carefully. It is a good idea to be near the console when doing this. .It Don't forget the loopback interface. .El .Sh FINE POINTS There is one kind of packet that the firewall will always discard, that is an IP fragment with a fragment offset of one. This is a valid packet, but it only has one use, to try to circumvent firewalls. .Pp If you are logged in over a network, loading the LKM version of .Nm is probably not as straightforward as you would think. I recommend this command line: .Bd -literal -offset center modload /lkm/ipfw_mod.o && \e ipfw add 32000 allow all from any to any .Ed .Pp Along the same lines, doing an .Bd -literal -offset center ipfw flush .Ed .Pp in similar surroundings is also a bad idea. .Sh PACKET DIVERSION A divert socket bound to the specified port will receive all packets diverted to that port; see .Xr divert 4 . If no socket is bound to the destination port, or if the kernel wasn't compiled with divert socket support, diverted packets are dropped. .Sh EXAMPLES This command adds an entry which denies all tcp packets from .Em cracker.evil.org to the telnet port of .Em wolf.tambov.su from being forwarded by the host: .Pp .Dl ipfw add deny tcp from cracker.evil.org to wolf.tambov.su 23 .Pp This one disallows any connection from the entire crackers network to my host: .Pp .Dl ipfw addf deny all from 123.45.67.0/24 to my.host.org .Pp Here is a good usage of the list command to see accounting records and timestamp information: .Pp .Dl ipfw -at l .Pp or in short form without timestamps: .Pp .Dl ipfw -a l .Pp This rule diverts all incoming packets from 192.168.2.0/24 to divert port 5000: .Pp .Dl ipfw divert 5000 all from 192.168.2.0/24 to any in .Sh SEE ALSO .Xr divert 4 , .Xr ip 4 , .Xr ipfirewall 4 , .Xr protocols 5 , .Xr services 5 , .Xr reboot 8 , .Xr sysctl 8 , .Xr syslogd 8 .Sh BUGS .Pp .Em WARNING!!WARNING!!WARNING!!WARNING!!WARNING!!WARNING!!WARNING!! .Pp This program can put your computer in rather unusable state. When using it for the first time, work on the console of the computer, and do .Em NOT do anything you don't understand. .Pp When manipulating/adding chain entries, service and protocol names are not accepted. .Pp Incoming packet fragments diverted by .Ar divert are reassembled before delivery to the socket, whereas fragments diverted via .Ar tee are not. .Pp Port aliases containing dashes cannot be first in a list. .Sh AUTHORS .An Ugen J. S. Antsilevich , .An Poul-Henning Kamp , .An Alex Nash , .An Archie Cobbs . API based upon code written by .An Daniel Boulet for BSDI. .Sh HISTORY .Nm first appeared in .Fx 2.0 . Index: head/sbin/ipfw/ipfw.c =================================================================== --- head/sbin/ipfw/ipfw.c (revision 37408) +++ head/sbin/ipfw/ipfw.c (revision 37409) @@ -1,1304 +1,1333 @@ /* * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp * Copyright (c) 1994 Ugen J.S.Antsilevich * * Idea and grammar partially left from: * Copyright (c) 1993 Daniel Boulet * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * * NEW command line interface for IP firewall facility * - * $Id: ipfw.c,v 1.56 1998/04/22 06:20:20 phk Exp $ + * $Id: ipfw.c,v 1.57 1998/05/15 12:38:07 danny Exp $ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int lineno = -1; int s; /* main RAW socket */ int do_resolv=0; /* Would try to resolv all */ int do_acct=0; /* Show packet/byte count */ int do_time=0; /* Show time stamps */ int do_quiet=0; /* Be quiet in add and flush */ int do_force=0; /* Don't ask for confirmation */ struct icmpcode { int code; char *str; }; static struct icmpcode icmpcodes[] = { { ICMP_UNREACH_NET, "net" }, { ICMP_UNREACH_HOST, "host" }, { ICMP_UNREACH_PROTOCOL, "protocol" }, { ICMP_UNREACH_PORT, "port" }, { ICMP_UNREACH_NEEDFRAG, "needfrag" }, { ICMP_UNREACH_SRCFAIL, "srcfail" }, { ICMP_UNREACH_NET_UNKNOWN, "net-unknown" }, { ICMP_UNREACH_HOST_UNKNOWN, "host-unknown" }, { ICMP_UNREACH_ISOLATED, "isolated" }, { ICMP_UNREACH_NET_PROHIB, "net-prohib" }, { ICMP_UNREACH_HOST_PROHIB, "host-prohib" }, { ICMP_UNREACH_TOSNET, "tosnet" }, { ICMP_UNREACH_TOSHOST, "toshost" }, { ICMP_UNREACH_FILTER_PROHIB, "filter-prohib" }, { ICMP_UNREACH_HOST_PRECEDENCE, "host-precedence" }, { ICMP_UNREACH_PRECEDENCE_CUTOFF, "precedence-cutoff" }, { 0, NULL } }; static void show_usage(const char *fmt, ...); static int mask_bits(struct in_addr m_ad) { int h_fnd=0,h_num=0,i; u_long mask; mask=ntohl(m_ad.s_addr); for (i=0;i>1; } return h_num; } void print_port(prot, port, comma) u_char prot; u_short port; const char *comma; { struct servent *se; struct protoent *pe; const char *protocol; int printed = 0; if (do_resolv) { pe = getprotobynumber(prot); if (pe) protocol = pe->p_name; else protocol = NULL; se = getservbyport(htons(port), protocol); if (se) { printf("%s%s", comma, se->s_name); printed = 1; } } if (!printed) printf("%s%d",comma,port); } static void print_iface(char *key, union ip_fw_if *un, int byname) { char ifnb[FW_IFNLEN+1]; if (byname) { strncpy(ifnb, un->fu_via_if.name, FW_IFNLEN); ifnb[FW_IFNLEN]='\0'; if (un->fu_via_if.unit == -1) printf(" %s %s*", key, ifnb); else printf(" %s %s%d", key, ifnb, un->fu_via_if.unit); } else if (un->fu_via_ip.s_addr != 0) { printf(" %s %s", key, inet_ntoa(un->fu_via_ip)); } else printf(" %s any", key); } static void print_reject_code(int code) { struct icmpcode *ic; for (ic = icmpcodes; ic->str; ic++) if (ic->code == code) { printf("%s", ic->str); return; } printf("%u", code); } static void show_ipfw(struct ip_fw *chain, int pcwidth, int bcwidth) { char *comma; u_long adrt; struct hostent *he; struct protoent *pe; int i, mb; int nsp = IP_FW_GETNSRCP(chain); int ndp = IP_FW_GETNDSTP(chain); if (do_resolv) setservent(1/*stayopen*/); printf("%05u ", chain->fw_number); if (do_acct) printf("%*qu %*qu ",pcwidth,chain->fw_pcnt,bcwidth,chain->fw_bcnt); if (do_time) { if (chain->timestamp) { char timestr[30]; strcpy(timestr, ctime((time_t *)&chain->timestamp)); *strchr(timestr, '\n') = '\0'; printf("%s ", timestr); } else printf(" "); } switch (chain->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_ACCEPT: printf("allow"); break; case IP_FW_F_DENY: printf("deny"); break; case IP_FW_F_COUNT: printf("count"); break; case IP_FW_F_DIVERT: printf("divert %u", chain->fw_divert_port); break; case IP_FW_F_TEE: printf("tee %u", chain->fw_divert_port); break; case IP_FW_F_SKIPTO: printf("skipto %u", chain->fw_skipto_rule); break; case IP_FW_F_REJECT: if (chain->fw_reject_code == IP_FW_REJECT_RST) printf("reset"); else { printf("unreach "); print_reject_code(chain->fw_reject_code); } break; + case IP_FW_F_FWD: + printf("fwd %s", inet_ntoa(chain->fw_fwd_ip.sin_addr)); + if(chain->fw_fwd_ip.sin_port) + printf(",%d", chain->fw_fwd_ip.sin_port); + break; default: errx(EX_OSERR, "impossible"); } if (chain->fw_flg & IP_FW_F_PRN) printf(" log"); pe = getprotobynumber(chain->fw_prot); if (pe) printf(" %s", pe->p_name); else printf(" %u", chain->fw_prot); printf(" from %s", chain->fw_flg & IP_FW_F_INVSRC ? "not " : ""); adrt=ntohl(chain->fw_smsk.s_addr); if (adrt==ULONG_MAX && do_resolv) { adrt=(chain->fw_src.s_addr); he=gethostbyaddr((char *)&adrt,sizeof(u_long),AF_INET); if (he==NULL) { printf(inet_ntoa(chain->fw_src)); } else printf("%s",he->h_name); } else { if (adrt!=ULONG_MAX) { mb=mask_bits(chain->fw_smsk); if (mb == 0) { printf("any"); } else { if (mb > 0) { printf(inet_ntoa(chain->fw_src)); printf("/%d",mb); } else { printf(inet_ntoa(chain->fw_src)); printf(":"); printf(inet_ntoa(chain->fw_smsk)); } } } else printf(inet_ntoa(chain->fw_src)); } if (chain->fw_prot == IPPROTO_TCP || chain->fw_prot == IPPROTO_UDP) { comma = " "; for (i = 0; i < nsp; i++) { print_port(chain->fw_prot, chain->fw_uar.fw_pts[i], comma); if (i==0 && (chain->fw_flg & IP_FW_F_SRNG)) comma = "-"; else comma = ","; } } printf(" to %s", chain->fw_flg & IP_FW_F_INVDST ? "not " : ""); adrt=ntohl(chain->fw_dmsk.s_addr); if (adrt==ULONG_MAX && do_resolv) { adrt=(chain->fw_dst.s_addr); he=gethostbyaddr((char *)&adrt,sizeof(u_long),AF_INET); if (he==NULL) { printf(inet_ntoa(chain->fw_dst)); } else printf("%s",he->h_name); } else { if (adrt!=ULONG_MAX) { mb=mask_bits(chain->fw_dmsk); if (mb == 0) { printf("any"); } else { if (mb > 0) { printf(inet_ntoa(chain->fw_dst)); printf("/%d",mb); } else { printf(inet_ntoa(chain->fw_dst)); printf(":"); printf(inet_ntoa(chain->fw_dmsk)); } } } else printf(inet_ntoa(chain->fw_dst)); } if (chain->fw_prot == IPPROTO_TCP || chain->fw_prot == IPPROTO_UDP) { comma = " "; for (i = 0; i < ndp; i++) { print_port(chain->fw_prot, chain->fw_uar.fw_pts[nsp+i], comma); if (i==0 && (chain->fw_flg & IP_FW_F_DRNG)) comma = "-"; else comma = ","; } } /* Direction */ if ((chain->fw_flg & IP_FW_F_IN) && !(chain->fw_flg & IP_FW_F_OUT)) printf(" in"); if (!(chain->fw_flg & IP_FW_F_IN) && (chain->fw_flg & IP_FW_F_OUT)) printf(" out"); /* Handle hack for "via" backwards compatibility */ if ((chain->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { print_iface("via", &chain->fw_in_if, chain->fw_flg & IP_FW_F_IIFNAME); } else { /* Receive interface specified */ if (chain->fw_flg & IP_FW_F_IIFACE) print_iface("recv", &chain->fw_in_if, chain->fw_flg & IP_FW_F_IIFNAME); /* Transmit interface specified */ if (chain->fw_flg & IP_FW_F_OIFACE) print_iface("xmit", &chain->fw_out_if, chain->fw_flg & IP_FW_F_OIFNAME); } if (chain->fw_flg & IP_FW_F_FRAG) printf(" frag"); if (chain->fw_ipopt || chain->fw_ipnopt) { int _opt_printed = 0; #define PRINTOPT(x) {if (_opt_printed) printf(",");\ printf(x); _opt_printed = 1;} printf(" ipopt "); if (chain->fw_ipopt & IP_FW_IPOPT_SSRR) PRINTOPT("ssrr"); if (chain->fw_ipnopt & IP_FW_IPOPT_SSRR) PRINTOPT("!ssrr"); if (chain->fw_ipopt & IP_FW_IPOPT_LSRR) PRINTOPT("lsrr"); if (chain->fw_ipnopt & IP_FW_IPOPT_LSRR) PRINTOPT("!lsrr"); if (chain->fw_ipopt & IP_FW_IPOPT_RR) PRINTOPT("rr"); if (chain->fw_ipnopt & IP_FW_IPOPT_RR) PRINTOPT("!rr"); if (chain->fw_ipopt & IP_FW_IPOPT_TS) PRINTOPT("ts"); if (chain->fw_ipnopt & IP_FW_IPOPT_TS) PRINTOPT("!ts"); } if (chain->fw_tcpf & IP_FW_TCPF_ESTAB) printf(" established"); else if (chain->fw_tcpf == IP_FW_TCPF_SYN && chain->fw_tcpnf == IP_FW_TCPF_ACK) printf(" setup"); else if (chain->fw_tcpf || chain->fw_tcpnf) { int _flg_printed = 0; #define PRINTFLG(x) {if (_flg_printed) printf(",");\ printf(x); _flg_printed = 1;} printf(" tcpflg "); if (chain->fw_tcpf & IP_FW_TCPF_FIN) PRINTFLG("fin"); if (chain->fw_tcpnf & IP_FW_TCPF_FIN) PRINTFLG("!fin"); if (chain->fw_tcpf & IP_FW_TCPF_SYN) PRINTFLG("syn"); if (chain->fw_tcpnf & IP_FW_TCPF_SYN) PRINTFLG("!syn"); if (chain->fw_tcpf & IP_FW_TCPF_RST) PRINTFLG("rst"); if (chain->fw_tcpnf & IP_FW_TCPF_RST) PRINTFLG("!rst"); if (chain->fw_tcpf & IP_FW_TCPF_PSH) PRINTFLG("psh"); if (chain->fw_tcpnf & IP_FW_TCPF_PSH) PRINTFLG("!psh"); if (chain->fw_tcpf & IP_FW_TCPF_ACK) PRINTFLG("ack"); if (chain->fw_tcpnf & IP_FW_TCPF_ACK) PRINTFLG("!ack"); if (chain->fw_tcpf & IP_FW_TCPF_URG) PRINTFLG("urg"); if (chain->fw_tcpnf & IP_FW_TCPF_URG) PRINTFLG("!urg"); } if (chain->fw_flg & IP_FW_F_ICMPBIT) { int type_index; int first = 1; printf(" icmptype"); for (type_index = 0; type_index < IP_FW_ICMPTYPES_DIM * sizeof(unsigned) * 8; ++type_index) if (chain->fw_uar.fw_icmptypes[type_index / (sizeof(unsigned) * 8)] & (1U << (type_index % (sizeof(unsigned) * 8)))) { printf("%c%d", first == 1 ? ' ' : ',', type_index); first = 0; } } printf("\n"); if (do_resolv) endservent(); } void list(ac, av) int ac; char **av; { struct ip_fw *r; struct ip_fw rules[1024]; int l,i,bytes; unsigned long rulenum; int pcwidth = 0; int bcwidth = 0; /* extract rules from kernel */ memset(rules,0,sizeof rules); bytes = sizeof rules; i = getsockopt(s, IPPROTO_IP, IP_FW_GET, rules, &bytes); if (i < 0) err(2,"getsockopt(IP_FW_GET)"); if (do_acct) /* find the maximum packet/byte counter widths */ for (r=rules, l = bytes; l >= sizeof rules[0]; r++, l-=sizeof rules[0]) { char temp[32]; int width; /* packet counter */ width = sprintf(temp, "%qu", r->fw_pcnt); if (width > pcwidth) pcwidth = width; /* byte counter */ width = sprintf(temp, "%qu", r->fw_bcnt); if (width > bcwidth) bcwidth = width; } if (!ac) { /* display all rules */ for (r = rules, l = bytes; l >= sizeof rules[0]; r++, l-=sizeof rules[0]) show_ipfw(r, pcwidth, bcwidth); } else { /* display specific rules requested on command line */ int exitval = EX_OK; while (ac--) { char *endptr; int seen; /* convert command line rule # */ rulenum = strtoul(*av++, &endptr, 10); if (*endptr) { exitval = EX_USAGE; warn("invalid rule number: %s", *(av - 1)); continue; } seen = 0; for (r = rules, l = bytes; l >= sizeof rules[0] && r->fw_number <= rulenum; r++, l-=sizeof rules[0]) if (rulenum == r->fw_number) { show_ipfw(r, pcwidth, bcwidth); seen = 1; } if (!seen) { /* give precedence to other error(s) */ if (exitval == EX_OK) exitval = EX_UNAVAILABLE; warnx("rule %lu does not exist", rulenum); } } if (exitval != EX_OK) exit(exitval); } } static void show_usage(const char *fmt, ...) { if (fmt) { char buf[100]; va_list args; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); warnx("error: %s", buf); } fprintf(stderr, "usage: ipfw [options]\n" " flush\n" " add [number] rule\n" " delete number ...\n" " list [number ...]\n" " show [number ...]\n" " zero [number ...]\n" " rule: action proto src dst extras...\n" " action:\n" " {allow|permit|accept|pass|deny|drop|reject|unreach code|\n" -" reset|count|skipto num|divert port|tee port} [log]\n" +" reset|count|skipto num|divert port|tee port|fwd ip} [log]\n" " proto: {ip|tcp|udp|icmp|}\n" " src: from [not] {any|ip[{/bits|:mask}]} [{port|port-port},[port],...]\n" " dst: to [not] {any|ip[{/bits|:mask}]} [{port|port-port},[port],...]\n" " extras:\n" " fragment (may not be used with ports or tcpflags)\n" " in\n" " out\n" " {xmit|recv|via} {iface|ip|any}\n" " {established|setup}\n" " tcpflags [!]{syn|fin|rst|ack|psh|urg},...\n" " ipoptions [!]{ssrr|lsrr|rr|ts},...\n" " icmptypes {type[,type]}...\n"); exit(EX_USAGE); } static int lookup_host (host, ipaddr) char *host; struct in_addr *ipaddr; { struct hostent *he = gethostbyname(host); if (!he) return(-1); *ipaddr = *(struct in_addr *)he->h_addr_list[0]; return(0); } void fill_ip(ipno, mask, acp, avp) struct in_addr *ipno, *mask; int *acp; char ***avp; { int ac = *acp; char **av = *avp; char *p = 0, md = 0; if (ac && !strncmp(*av,"any",strlen(*av))) { ipno->s_addr = mask->s_addr = 0; av++; ac--; } else { p = strchr(*av, '/'); if (!p) p = strchr(*av, ':'); if (p) { md = *p; *p++ = '\0'; } if (lookup_host(*av, ipno) != 0) show_usage("hostname ``%s'' unknown", *av); switch (md) { case ':': if (!inet_aton(p,mask)) show_usage("bad netmask ``%s''", p); break; case '/': if (atoi(p) == 0) { mask->s_addr = 0; } else if (atoi(p) > 32) { show_usage("bad width ``%s''", p); } else { mask->s_addr = htonl(~0 << (32 - atoi(p))); } break; default: mask->s_addr = htonl(~0); break; } ipno->s_addr &= mask->s_addr; av++; ac--; } *acp = ac; *avp = av; } static void fill_reject_code(u_short *codep, char *str) { struct icmpcode *ic; u_long val; char *s; val = strtoul(str, &s, 0); if (s != str && *s == '\0' && val < 0x100) { *codep = val; return; } for (ic = icmpcodes; ic->str; ic++) if (!strcasecmp(str, ic->str)) { *codep = ic->code; return; } show_usage("unknown ICMP unreachable code ``%s''", str); } static void add_port(cnt, ptr, off, port) u_short *cnt, *ptr, off, port; { if (off + *cnt >= IP_FW_MAX_PORTS) errx(EX_USAGE, "too many ports (max is %d)", IP_FW_MAX_PORTS); ptr[off+*cnt] = port; (*cnt)++; } static int lookup_port(const char *arg, int test, int nodash) { int val; char *earg, buf[32]; struct servent *s; snprintf(buf, sizeof(buf), "%s", arg); buf[strcspn(arg, nodash ? "-," : ",")] = 0; val = (int) strtoul(buf, &earg, 0); if (!*buf || *earg) { setservent(1); if ((s = getservbyname(buf, NULL))) { val = htons(s->s_port); } else { if (!test) { errx(EX_DATAERR, "unknown port ``%s''", arg); } val = -1; } } else { if (val < 0 || val > 0xffff) { if (!test) { errx(EX_DATAERR, "port ``%s'' out of range", arg); } val = -1; } } return(val); } int fill_port(cnt, ptr, off, arg) u_short *cnt, *ptr, off; char *arg; { char *s; int initial_range = 0; s = arg + strcspn(arg, "-,"); /* first port name can't have a dash */ if (*s == '-') { *s++ = '\0'; if (strchr(arg, ',')) errx(EX_USAGE, "port range must be first in list"); add_port(cnt, ptr, off, *arg ? lookup_port(arg, 0, 0) : 0x0000); arg = s; s = strchr(arg,','); if (s) *s++ = '\0'; add_port(cnt, ptr, off, *arg ? lookup_port(arg, 0, 0) : 0xffff); arg = s; initial_range = 1; } while (arg != NULL) { s = strchr(arg,','); if (s) *s++ = '\0'; add_port(cnt, ptr, off, lookup_port(arg, 0, 0)); arg = s; } return initial_range; } void fill_tcpflag(set, reset, vp) u_char *set, *reset; char **vp; { char *p = *vp,*q; u_char *d; while (p && *p) { struct tpcflags { char * name; u_char value; } flags[] = { { "syn", IP_FW_TCPF_SYN }, { "fin", IP_FW_TCPF_FIN }, { "ack", IP_FW_TCPF_ACK }, { "psh", IP_FW_TCPF_PSH }, { "rst", IP_FW_TCPF_RST }, { "urg", IP_FW_TCPF_URG } }; int i; if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) if (!strncmp(p, flags[i].name, strlen(p))) { *d |= flags[i].value; break; } if (i == sizeof(flags) / sizeof(flags[0])) show_usage("invalid tcp flag ``%s''", p); p = q; } } static void fill_ipopt(u_char *set, u_char *reset, char **vp) { char *p = *vp,*q; u_char *d; while (p && *p) { if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; if (!strncmp(p,"ssrr",strlen(p))) *d |= IP_FW_IPOPT_SSRR; if (!strncmp(p,"lsrr",strlen(p))) *d |= IP_FW_IPOPT_LSRR; if (!strncmp(p,"rr",strlen(p))) *d |= IP_FW_IPOPT_RR; if (!strncmp(p,"ts",strlen(p))) *d |= IP_FW_IPOPT_TS; p = q; } } void fill_icmptypes(types, vp, fw_flg) u_long *types; char **vp; u_short *fw_flg; { char *c = *vp; while (*c) { unsigned long icmptype; if ( *c == ',' ) ++c; icmptype = strtoul(c, &c, 0); if ( *c != ',' && *c != '\0' ) show_usage("invalid ICMP type"); if (icmptype >= IP_FW_ICMPTYPES_DIM * sizeof(unsigned) * 8) show_usage("ICMP type out of range"); types[icmptype / (sizeof(unsigned) * 8)] |= 1 << (icmptype % (sizeof(unsigned) * 8)); *fw_flg |= IP_FW_F_ICMPBIT; } } void delete(ac,av) int ac; char **av; { struct ip_fw rule; int i; int exitval = EX_OK; memset(&rule, 0, sizeof rule); av++; ac--; /* Rule number */ while (ac && isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rule, sizeof rule); if (i) { exitval = EX_UNAVAILABLE; warn("rule %u: setsockopt(%s)", rule.fw_number, "IP_FW_DEL"); } } if (exitval != EX_OK) exit(exitval); } static void verify_interface(union ip_fw_if *ifu) { struct ifreq ifr; /* * If a unit was specified, check for that exact interface. * If a wildcard was specified, check for unit 0. */ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", ifu->fu_via_if.name, ifu->fu_via_if.unit == -1 ? 0 : ifu->fu_via_if.unit); if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) warnx("warning: interface ``%s'' does not exist", ifr.ifr_name); } static void fill_iface(char *which, union ip_fw_if *ifu, int *byname, int ac, char *arg) { if (!ac) show_usage("missing argument for ``%s''", which); /* Parse the interface or address */ if (!strcmp(arg, "any")) { ifu->fu_via_ip.s_addr = 0; *byname = 0; } else if (!isdigit(*arg)) { char *q; *byname = 1; strncpy(ifu->fu_via_if.name, arg, sizeof(ifu->fu_via_if.name)); ifu->fu_via_if.name[sizeof(ifu->fu_via_if.name) - 1] = '\0'; for (q = ifu->fu_via_if.name; *q && !isdigit(*q) && *q != '*'; q++) continue; ifu->fu_via_if.unit = (*q == '*') ? -1 : atoi(q); *q = '\0'; verify_interface(ifu); } else if (!inet_aton(arg, &ifu->fu_via_ip)) { show_usage("bad ip address ``%s''", arg); } else *byname = 0; } static void add(ac,av) int ac; char **av; { struct ip_fw rule; int i; u_char proto; struct protoent *pe; int saw_xmrc = 0, saw_via = 0; memset(&rule, 0, sizeof rule); av++; ac--; /* Rule number */ if (ac && isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; } /* Action */ if (ac == 0) show_usage("missing action"); if (!strncmp(*av,"accept",strlen(*av)) || !strncmp(*av,"pass",strlen(*av)) || !strncmp(*av,"allow",strlen(*av)) || !strncmp(*av,"permit",strlen(*av))) { rule.fw_flg |= IP_FW_F_ACCEPT; av++; ac--; } else if (!strncmp(*av,"count",strlen(*av))) { rule.fw_flg |= IP_FW_F_COUNT; av++; ac--; } else if (!strncmp(*av,"divert",strlen(*av))) { rule.fw_flg |= IP_FW_F_DIVERT; av++; ac--; if (!ac) show_usage("missing divert port"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; if (rule.fw_divert_port == 0) { struct servent *s; setservent(1); s = getservbyname(av[-1], "divert"); if (s != NULL) rule.fw_divert_port = ntohs(s->s_port); else show_usage("illegal divert port"); } } else if (!strncmp(*av,"tee",strlen(*av))) { rule.fw_flg |= IP_FW_F_TEE; av++; ac--; if (!ac) show_usage("missing divert port"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; if (rule.fw_divert_port == 0) { struct servent *s; setservent(1); s = getservbyname(av[-1], "divert"); if (s != NULL) rule.fw_divert_port = ntohs(s->s_port); else show_usage("illegal divert port"); } + } else if (!strncmp(*av,"fwd",strlen(*av)) || + !strncmp(*av,"forward",strlen(*av))) { + struct in_addr dummyip; + char *pp; + rule.fw_flg |= IP_FW_F_FWD; av++; ac--; + if (!ac) + show_usage("missing forwarding IP address"); + rule.fw_fwd_ip.sin_len = sizeof(struct sockaddr_in); + rule.fw_fwd_ip.sin_family = AF_INET; + rule.fw_fwd_ip.sin_port = 0; + pp = strchr(*av, ':'); + if(pp == NULL) + pp = strchr(*av, ','); + if(pp != NULL) + { + *(pp++) = '\0'; + rule.fw_fwd_ip.sin_port = lookup_port(pp, 1, 1); + if(rule.fw_fwd_ip.sin_port == (unsigned int)-1) + show_usage("illegal forwarding port"); + } + fill_ip(&(rule.fw_fwd_ip.sin_addr), &dummyip, &ac, &av); + if (rule.fw_fwd_ip.sin_addr.s_addr == 0) + show_usage("illegal forwarding IP address"); + } else if (!strncmp(*av,"skipto",strlen(*av))) { rule.fw_flg |= IP_FW_F_SKIPTO; av++; ac--; if (!ac) show_usage("missing skipto rule number"); rule.fw_skipto_rule = strtoul(*av, NULL, 0); av++; ac--; } else if ((!strncmp(*av,"deny",strlen(*av)) || !strncmp(*av,"drop",strlen(*av)))) { rule.fw_flg |= IP_FW_F_DENY; av++; ac--; } else if (!strncmp(*av,"reject",strlen(*av))) { rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; rule.fw_reject_code = ICMP_UNREACH_HOST; } else if (!strncmp(*av,"reset",strlen(*av))) { rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; rule.fw_reject_code = IP_FW_REJECT_RST; /* check TCP later */ } else if (!strncmp(*av,"unreach",strlen(*av))) { rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; fill_reject_code(&rule.fw_reject_code, *av); av++; ac--; } else { show_usage("invalid action ``%s''", *av); } /* [log] */ if (ac && !strncmp(*av,"log",strlen(*av))) { rule.fw_flg |= IP_FW_F_PRN; av++; ac--; } /* protocol */ if (ac == 0) show_usage("missing protocol"); if ((proto = atoi(*av)) > 0) { rule.fw_prot = proto; av++; ac--; } else if (!strncmp(*av,"all",strlen(*av))) { rule.fw_prot = IPPROTO_IP; av++; ac--; } else if ((pe = getprotobyname(*av)) != NULL) { rule.fw_prot = pe->p_proto; av++; ac--; } else { show_usage("invalid protocol ``%s''", *av); } if (rule.fw_prot != IPPROTO_TCP && (rule.fw_flg & IP_FW_F_COMMAND) == IP_FW_F_REJECT && rule.fw_reject_code == IP_FW_REJECT_RST) show_usage("``reset'' is only valid for tcp packets"); /* from */ if (ac && !strncmp(*av,"from",strlen(*av))) { av++; ac--; } else show_usage("missing ``from''"); if (ac && !strncmp(*av,"not",strlen(*av))) { rule.fw_flg |= IP_FW_F_INVSRC; av++; ac--; } if (!ac) show_usage("missing arguments"); fill_ip(&rule.fw_src, &rule.fw_smsk, &ac, &av); if (ac && (isdigit(**av) || lookup_port(*av, 1, 1) >= 0)) { u_short nports = 0; if (fill_port(&nports, rule.fw_uar.fw_pts, 0, *av)) rule.fw_flg |= IP_FW_F_SRNG; IP_FW_SETNSRCP(&rule, nports); av++; ac--; } /* to */ if (ac && !strncmp(*av,"to",strlen(*av))) { av++; ac--; } else show_usage("missing ``to''"); if (ac && !strncmp(*av,"not",strlen(*av))) { rule.fw_flg |= IP_FW_F_INVDST; av++; ac--; } if (!ac) show_usage("missing arguments"); fill_ip(&rule.fw_dst, &rule.fw_dmsk, &ac, &av); if (ac && (isdigit(**av) || lookup_port(*av, 1, 1) >= 0)) { u_short nports = 0; if (fill_port(&nports, rule.fw_uar.fw_pts, IP_FW_GETNSRCP(&rule), *av)) rule.fw_flg |= IP_FW_F_DRNG; IP_FW_SETNDSTP(&rule, nports); av++; ac--; } if ((rule.fw_prot != IPPROTO_TCP) && (rule.fw_prot != IPPROTO_UDP) && (IP_FW_GETNSRCP(&rule) || IP_FW_GETNDSTP(&rule))) { show_usage("only TCP and UDP protocols are valid" " with port specifications"); } while (ac) { if (!strncmp(*av,"in",strlen(*av))) { rule.fw_flg |= IP_FW_F_IN; av++; ac--; continue; } if (!strncmp(*av,"out",strlen(*av))) { rule.fw_flg |= IP_FW_F_OUT; av++; ac--; continue; } if (ac && !strncmp(*av,"xmit",strlen(*av))) { union ip_fw_if ifu; int byname; if (saw_via) { badviacombo: show_usage("``via'' is incompatible" " with ``xmit'' and ``recv''"); } saw_xmrc = 1; av++; ac--; fill_iface("xmit", &ifu, &byname, ac, *av); rule.fw_out_if = ifu; rule.fw_flg |= IP_FW_F_OIFACE; if (byname) rule.fw_flg |= IP_FW_F_OIFNAME; av++; ac--; continue; } if (ac && !strncmp(*av,"recv",strlen(*av))) { union ip_fw_if ifu; int byname; if (saw_via) goto badviacombo; saw_xmrc = 1; av++; ac--; fill_iface("recv", &ifu, &byname, ac, *av); rule.fw_in_if = ifu; rule.fw_flg |= IP_FW_F_IIFACE; if (byname) rule.fw_flg |= IP_FW_F_IIFNAME; av++; ac--; continue; } if (ac && !strncmp(*av,"via",strlen(*av))) { union ip_fw_if ifu; int byname = 0; if (saw_xmrc) goto badviacombo; saw_via = 1; av++; ac--; fill_iface("via", &ifu, &byname, ac, *av); rule.fw_out_if = rule.fw_in_if = ifu; if (byname) rule.fw_flg |= (IP_FW_F_IIFNAME | IP_FW_F_OIFNAME); av++; ac--; continue; } if (!strncmp(*av,"fragment",strlen(*av))) { rule.fw_flg |= IP_FW_F_FRAG; av++; ac--; continue; } if (!strncmp(*av,"ipoptions",strlen(*av))) { av++; ac--; if (!ac) show_usage("missing argument" " for ``ipoptions''"); fill_ipopt(&rule.fw_ipopt, &rule.fw_ipnopt, av); av++; ac--; continue; } if (rule.fw_prot == IPPROTO_TCP) { if (!strncmp(*av,"established",strlen(*av))) { rule.fw_tcpf |= IP_FW_TCPF_ESTAB; av++; ac--; continue; } if (!strncmp(*av,"setup",strlen(*av))) { rule.fw_tcpf |= IP_FW_TCPF_SYN; rule.fw_tcpnf |= IP_FW_TCPF_ACK; av++; ac--; continue; } if (!strncmp(*av,"tcpflags",strlen(*av))) { av++; ac--; if (!ac) show_usage("missing argument" " for ``tcpflags''"); fill_tcpflag(&rule.fw_tcpf, &rule.fw_tcpnf, av); av++; ac--; continue; } } if (rule.fw_prot == IPPROTO_ICMP) { if (!strncmp(*av,"icmptypes",strlen(*av))) { av++; ac--; if (!ac) show_usage("missing argument" " for ``icmptypes''"); fill_icmptypes(rule.fw_uar.fw_icmptypes, av, &rule.fw_flg); av++; ac--; continue; } } show_usage("unknown argument ``%s''", *av); } /* No direction specified -> do both directions */ if (!(rule.fw_flg & (IP_FW_F_OUT|IP_FW_F_IN))) rule.fw_flg |= (IP_FW_F_OUT|IP_FW_F_IN); /* Sanity check interface check, but handle "via" case separately */ if (saw_via) { if (rule.fw_flg & IP_FW_F_IN) rule.fw_flg |= IP_FW_F_IIFACE; if (rule.fw_flg & IP_FW_F_OUT) rule.fw_flg |= IP_FW_F_OIFACE; } else if ((rule.fw_flg & IP_FW_F_OIFACE) && (rule.fw_flg & IP_FW_F_IN)) show_usage("can't check xmit interface of incoming packets"); /* frag may not be used in conjunction with ports or TCP flags */ if (rule.fw_flg & IP_FW_F_FRAG) { if (rule.fw_tcpf || rule.fw_tcpnf) show_usage("can't mix 'frag' and tcpflags"); if (rule.fw_nports) show_usage("can't mix 'frag' and port specifications"); } if (!do_quiet) show_ipfw(&rule, 10, 10); i = setsockopt(s, IPPROTO_IP, IP_FW_ADD, &rule, sizeof rule); if (i) err(EX_UNAVAILABLE, "setsockopt(%s)", "IP_FW_ADD"); } static void zero (ac, av) int ac; char **av; { av++; ac--; if (!ac) { /* clear all entries */ if (setsockopt(s,IPPROTO_IP,IP_FW_ZERO,NULL,0)<0) err(EX_UNAVAILABLE, "setsockopt(%s)", "IP_FW_ZERO"); if (!do_quiet) printf("Accounting cleared.\n"); } else { struct ip_fw rule; int failed = EX_OK; memset(&rule, 0, sizeof rule); while (ac) { /* Rule number */ if (isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO, &rule, sizeof rule)) { warn("rule %u: setsockopt(%s)", rule.fw_number, "IP_FW_ZERO"); failed = EX_UNAVAILABLE; } else if (!do_quiet) printf("Entry %d cleared\n", rule.fw_number); } else show_usage("invalid rule number ``%s''", *av); } if (failed != EX_OK) exit(failed); } } int ipfw_main(ac,av) int ac; char **av; { int ch; extern int optind; if ( ac == 1 ) { show_usage(NULL); } /* Set the force flag for non-interactive processes */ do_force = !isatty(STDIN_FILENO); optind = 1; while ((ch = getopt(ac, av, "afqtN")) != -1) switch(ch) { case 'a': do_acct=1; break; case 'f': do_force=1; break; case 'q': do_quiet=1; break; case 't': do_time=1; break; case 'N': do_resolv=1; break; default: show_usage(NULL); } ac -= optind; if (*(av+=optind)==NULL) { show_usage("Bad arguments"); } if (!strncmp(*av, "add", strlen(*av))) { add(ac,av); } else if (!strncmp(*av, "delete", strlen(*av))) { delete(ac,av); } else if (!strncmp(*av, "flush", strlen(*av))) { int do_flush = 0; if ( do_force || do_quiet ) do_flush = 1; else { int c; /* Ask the user */ printf("Are you sure? [yn] "); do { fflush(stdout); c = toupper(getc(stdin)); while (c != '\n' && getc(stdin) != '\n') if (feof(stdin)) return (0); } while (c != 'Y' && c != 'N'); printf("\n"); if (c == 'Y') do_flush = 1; } if ( do_flush ) { if (setsockopt(s,IPPROTO_IP,IP_FW_FLUSH,NULL,0) < 0) err(EX_UNAVAILABLE, "setsockopt(%s)", "IP_FW_FLUSH"); if (!do_quiet) printf("Flushed all rules.\n"); } } else if (!strncmp(*av, "zero", strlen(*av))) { zero(ac,av); } else if (!strncmp(*av, "print", strlen(*av))) { list(--ac,++av); } else if (!strncmp(*av, "list", strlen(*av))) { list(--ac,++av); } else if (!strncmp(*av, "show", strlen(*av))) { do_acct++; list(--ac,++av); } else { show_usage("Bad arguments"); } return 0; } int main(ac, av) int ac; char **av; { #define MAX_ARGS 32 #define WHITESP " \t\f\v\n\r" char buf[BUFSIZ]; char *a, *args[MAX_ARGS]; char linename[10]; int i; FILE *f; s = socket( AF_INET, SOCK_RAW, IPPROTO_RAW ); if ( s < 0 ) err(EX_UNAVAILABLE, "socket"); setbuf(stdout,0); if (av[1] && !access(av[1], R_OK)) { lineno = 0; if ((f = fopen(av[1], "r")) == NULL) err(EX_UNAVAILABLE, "fopen: %s", av[1]); while (fgets(buf, BUFSIZ, f)) { char *p; lineno++; sprintf(linename, "Line %d", lineno); args[0] = linename; if (*buf == '#') continue; if ((p = strchr(buf, '#')) != NULL) *p = '\0'; for (i = 1, a = strtok(buf, WHITESP); a && i < MAX_ARGS; a = strtok(NULL, WHITESP), i++) args[i] = a; if (i == 1) continue; if (i == MAX_ARGS) errx(EX_USAGE, "%s: too many arguments", linename); args[i] = NULL; ipfw_main(i, args); } fclose(f); } else ipfw_main(ac,av); return EX_OK; } Index: head/sys/netinet/in.h =================================================================== --- head/sys/netinet/in.h (revision 37408) +++ head/sys/netinet/in.h (revision 37409) @@ -1,449 +1,449 @@ /* * Copyright (c) 1982, 1986, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)in.h 8.3 (Berkeley) 1/3/94 - * $Id: in.h,v 1.34 1998/06/06 19:39:08 julian Exp $ + * $Id: in.h,v 1.35 1998/06/06 20:45:25 julian Exp $ */ #ifndef _NETINET_IN_H_ #define _NETINET_IN_H_ /* * Constants and structures defined by the internet system, * Per RFC 790, September 1981, and numerous additions. */ /* * Protocols (RFC 1700) */ #define IPPROTO_IP 0 /* dummy for IP */ #define IPPROTO_ICMP 1 /* control message protocol */ #define IPPROTO_IGMP 2 /* group mgmt protocol */ #define IPPROTO_GGP 3 /* gateway^2 (deprecated) */ #define IPPROTO_IPIP 4 /* IP encapsulation in IP */ #define IPPROTO_TCP 6 /* tcp */ #define IPPROTO_ST 7 /* Stream protocol II */ #define IPPROTO_EGP 8 /* exterior gateway protocol */ #define IPPROTO_PIGP 9 /* private interior gateway */ #define IPPROTO_RCCMON 10 /* BBN RCC Monitoring */ #define IPPROTO_NVPII 11 /* network voice protocol*/ #define IPPROTO_PUP 12 /* pup */ #define IPPROTO_ARGUS 13 /* Argus */ #define IPPROTO_EMCON 14 /* EMCON */ #define IPPROTO_XNET 15 /* Cross Net Debugger */ #define IPPROTO_CHAOS 16 /* Chaos*/ #define IPPROTO_UDP 17 /* user datagram protocol */ #define IPPROTO_MUX 18 /* Multiplexing */ #define IPPROTO_MEAS 19 /* DCN Measurement Subsystems */ #define IPPROTO_HMP 20 /* Host Monitoring */ #define IPPROTO_PRM 21 /* Packet Radio Measurement */ #define IPPROTO_IDP 22 /* xns idp */ #define IPPROTO_TRUNK1 23 /* Trunk-1 */ #define IPPROTO_TRUNK2 24 /* Trunk-2 */ #define IPPROTO_LEAF1 25 /* Leaf-1 */ #define IPPROTO_LEAF2 26 /* Leaf-2 */ #define IPPROTO_RDP 27 /* Reliable Data */ #define IPPROTO_IRTP 28 /* Reliable Transaction */ #define IPPROTO_TP 29 /* tp-4 w/ class negotiation */ #define IPPROTO_BLT 30 /* Bulk Data Transfer */ #define IPPROTO_NSP 31 /* Network Services */ #define IPPROTO_INP 32 /* Merit Internodal */ #define IPPROTO_SEP 33 /* Sequential Exchange */ #define IPPROTO_3PC 34 /* Third Party Connect */ #define IPPROTO_IDPR 35 /* InterDomain Policy Routing */ #define IPPROTO_XTP 36 /* XTP */ #define IPPROTO_DDP 37 /* Datagram Delivery */ #define IPPROTO_CMTP 38 /* Control Message Transport */ #define IPPROTO_TPXX 39 /* TP++ Transport */ #define IPPROTO_IL 40 /* IL transport protocol */ #define IPPROTO_SIP 41 /* Simple Internet Protocol */ #define IPPROTO_SDRP 42 /* Source Demand Routing */ #define IPPROTO_SIPSR 43 /* SIP Source Route */ #define IPPROTO_SIPFRAG 44 /* SIP Fragment */ #define IPPROTO_IDRP 45 /* InterDomain Routing*/ #define IPPROTO_RSVP 46 /* resource reservation */ #define IPPROTO_GRE 47 /* General Routing Encap. */ #define IPPROTO_MHRP 48 /* Mobile Host Routing */ #define IPPROTO_BHA 49 /* BHA */ #define IPPROTO_ESP 50 /* SIPP Encap Sec. Payload */ #define IPPROTO_AH 51 /* SIPP Auth Header */ #define IPPROTO_INLSP 52 /* Integ. Net Layer Security */ #define IPPROTO_SWIPE 53 /* IP with encryption */ #define IPPROTO_NHRP 54 /* Next Hop Resolution */ /* 55-60: Unassigned */ #define IPPROTO_AHIP 61 /* any host internal protocol */ #define IPPROTO_CFTP 62 /* CFTP */ #define IPPROTO_HELLO 63 /* "hello" routing protocol */ #define IPPROTO_SATEXPAK 64 /* SATNET/Backroom EXPAK */ #define IPPROTO_KRYPTOLAN 65 /* Kryptolan */ #define IPPROTO_RVD 66 /* Remote Virtual Disk */ #define IPPROTO_IPPC 67 /* Pluribus Packet Core */ #define IPPROTO_ADFS 68 /* Any distributed FS */ #define IPPROTO_SATMON 69 /* Satnet Monitoring */ #define IPPROTO_VISA 70 /* VISA Protocol */ #define IPPROTO_IPCV 71 /* Packet Core Utility */ #define IPPROTO_CPNX 72 /* Comp. Prot. Net. Executive */ #define IPPROTO_CPHB 73 /* Comp. Prot. HeartBeat */ #define IPPROTO_WSN 74 /* Wang Span Network */ #define IPPROTO_PVP 75 /* Packet Video Protocol */ #define IPPROTO_BRSATMON 76 /* BackRoom SATNET Monitoring */ #define IPPROTO_ND 77 /* Sun net disk proto (temp.) */ #define IPPROTO_WBMON 78 /* WIDEBAND Monitoring */ #define IPPROTO_WBEXPAK 79 /* WIDEBAND EXPAK */ #define IPPROTO_EON 80 /* ISO cnlp */ #define IPPROTO_VMTP 81 /* VMTP */ #define IPPROTO_SVMTP 82 /* Secure VMTP */ #define IPPROTO_VINES 83 /* Banyon VINES */ #define IPPROTO_TTP 84 /* TTP */ #define IPPROTO_IGP 85 /* NSFNET-IGP */ #define IPPROTO_DGP 86 /* dissimilar gateway prot. */ #define IPPROTO_TCF 87 /* TCF */ #define IPPROTO_IGRP 88 /* Cisco/GXS IGRP */ #define IPPROTO_OSPFIGP 89 /* OSPFIGP */ #define IPPROTO_SRPC 90 /* Strite RPC protocol */ #define IPPROTO_LARP 91 /* Locus Address Resoloution */ #define IPPROTO_MTP 92 /* Multicast Transport */ #define IPPROTO_AX25 93 /* AX.25 Frames */ #define IPPROTO_IPEIP 94 /* IP encapsulated in IP */ #define IPPROTO_MICP 95 /* Mobile Int.ing control */ #define IPPROTO_SCCSP 96 /* Semaphore Comm. security */ #define IPPROTO_ETHERIP 97 /* Ethernet IP encapsulation */ #define IPPROTO_ENCAP 98 /* encapsulation header */ #define IPPROTO_APES 99 /* any private encr. scheme */ #define IPPROTO_GMTP 100 /* GMTP*/ /* 101-254: Unassigned */ /* 255: Reserved */ /* BSD Private, local use, namespace incursion */ #define IPPROTO_DIVERT 254 /* divert pseudo-protocol */ #define IPPROTO_RAW 255 /* raw IP packet */ #define IPPROTO_MAX 256 /* * Local port number conventions: * * When a user does a bind(2) or connect(2) with a port number of zero, * a non-conflicting local port address is chosen. * The default range is IPPORT_RESERVED through * IPPORT_USERRESERVED, although that is settable by sysctl. * * A user may set the IPPROTO_IP option IP_PORTRANGE to change this * default assignment range. * * The value IP_PORTRANGE_DEFAULT causes the default behavior. * * The value IP_PORTRANGE_HIGH changes the range of candidate port numbers * into the "high" range. These are reserved for client outbound connections * which do not want to be filtered by any firewalls. * * The value IP_PORTRANGE_LOW changes the range to the "low" are * that is (by convention) restricted to privileged processes. This * convention is based on "vouchsafe" principles only. It is only secure * if you trust the remote host to restrict these ports. * * The default range of ports and the high range can be changed by * sysctl(3). (net.inet.ip.port{hi,low}{first,last}_auto) * * Changing those values has bad security implications if you are * using a a stateless firewall that is allowing packets outside of that * range in order to allow transparent outgoing connections. * * Such a firewall configuration will generally depend on the use of these * default values. If you change them, you may find your Security * Administrator looking for you with a heavy object. * * For a slightly more orthodox text view on this: * * ftp://ftp.isi.edu/in-notes/iana/assignments/port-numbers * * port numbers are divided into three ranges: * * 0 - 1023 Well Known Ports * 1024 - 49151 Registered Ports * 49152 - 65535 Dynamic and/or Private Ports * */ /* * Ports < IPPORT_RESERVED are reserved for * privileged processes (e.g. root). (IP_PORTRANGE_LOW) * Ports > IPPORT_USERRESERVED are reserved * for servers, not necessarily privileged. (IP_PORTRANGE_DEFAULT) */ #define IPPORT_RESERVED 1024 #define IPPORT_USERRESERVED 5000 /* * Default local port range to use by setting IP_PORTRANGE_HIGH */ #define IPPORT_HIFIRSTAUTO 49152 #define IPPORT_HILASTAUTO 65535 /* * Scanning for a free reserved port return a value below IPPORT_RESERVED, * but higher than IPPORT_RESERVEDSTART. Traditionally the start value was * 512, but that conflicts with some well-known-services that firewalls may * have a fit if we use. */ #define IPPORT_RESERVEDSTART 600 /* * Internet address (a structure for historical reasons) */ struct in_addr { u_int32_t s_addr; }; /* * Definitions of bits in internet address integers. * On subnets, the decomposition of addresses to host and net parts * is done according to subnet mask, not the masks here. */ #define IN_CLASSA(i) (((u_int32_t)(i) & 0x80000000) == 0) #define IN_CLASSA_NET 0xff000000 #define IN_CLASSA_NSHIFT 24 #define IN_CLASSA_HOST 0x00ffffff #define IN_CLASSA_MAX 128 #define IN_CLASSB(i) (((u_int32_t)(i) & 0xc0000000) == 0x80000000) #define IN_CLASSB_NET 0xffff0000 #define IN_CLASSB_NSHIFT 16 #define IN_CLASSB_HOST 0x0000ffff #define IN_CLASSB_MAX 65536 #define IN_CLASSC(i) (((u_int32_t)(i) & 0xe0000000) == 0xc0000000) #define IN_CLASSC_NET 0xffffff00 #define IN_CLASSC_NSHIFT 8 #define IN_CLASSC_HOST 0x000000ff #define IN_CLASSD(i) (((u_int32_t)(i) & 0xf0000000) == 0xe0000000) #define IN_CLASSD_NET 0xf0000000 /* These ones aren't really */ #define IN_CLASSD_NSHIFT 28 /* net and host fields, but */ #define IN_CLASSD_HOST 0x0fffffff /* routing needn't know. */ #define IN_MULTICAST(i) IN_CLASSD(i) #define IN_EXPERIMENTAL(i) (((u_int32_t)(i) & 0xf0000000) == 0xf0000000) #define IN_BADCLASS(i) (((u_int32_t)(i) & 0xf0000000) == 0xf0000000) #define INADDR_ANY (u_int32_t)0x00000000 #define INADDR_LOOPBACK (u_int32_t)0x7f000001 #define INADDR_BROADCAST (u_int32_t)0xffffffff /* must be masked */ #ifndef KERNEL #define INADDR_NONE 0xffffffff /* -1 return */ #endif #define INADDR_UNSPEC_GROUP (u_int32_t)0xe0000000 /* 224.0.0.0 */ #define INADDR_ALLHOSTS_GROUP (u_int32_t)0xe0000001 /* 224.0.0.1 */ #define INADDR_ALLRTRS_GROUP (u_int32_t)0xe0000002 /* 224.0.0.2 */ #define INADDR_MAX_LOCAL_GROUP (u_int32_t)0xe00000ff /* 224.0.0.255 */ #define IN_LOOPBACKNET 127 /* official! */ /* * Socket address, internet style. */ struct sockaddr_in { u_char sin_len; u_char sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; /* * Structure used to describe IP options. * Used to store options internally, to pass them to a process, * or to restore options retrieved earlier. * The ip_dst is used for the first-hop gateway when using a source route * (this gets put into the header proper). */ struct ip_opts { struct in_addr ip_dst; /* first hop, 0 w/o src rt */ char ip_opts[40]; /* actually variable in size */ }; /* * Options for use with [gs]etsockopt at the IP level. * First word of comment is data type; bool is stored in int. */ #define IP_OPTIONS 1 /* buf/ip_opts; set/get IP options */ #define IP_HDRINCL 2 /* int; header is included with data */ #define IP_TOS 3 /* int; IP type of service and preced. */ #define IP_TTL 4 /* int; IP time to live */ #define IP_RECVOPTS 5 /* bool; receive all IP opts w/dgram */ #define IP_RECVRETOPTS 6 /* bool; receive IP opts for response */ #define IP_RECVDSTADDR 7 /* bool; receive IP dst addr w/dgram */ #define IP_RETOPTS 8 /* ip_opts; set/get IP options */ #define IP_MULTICAST_IF 9 /* u_char; set/get IP multicast i/f */ #define IP_MULTICAST_TTL 10 /* u_char; set/get IP multicast ttl */ #define IP_MULTICAST_LOOP 11 /* u_char; set/get IP multicast loopback */ #define IP_ADD_MEMBERSHIP 12 /* ip_mreq; add an IP group membership */ #define IP_DROP_MEMBERSHIP 13 /* ip_mreq; drop an IP group membership */ #define IP_MULTICAST_VIF 14 /* set/get IP mcast virt. iface */ #define IP_RSVP_ON 15 /* enable RSVP in kernel */ #define IP_RSVP_OFF 16 /* disable RSVP in kernel */ #define IP_RSVP_VIF_ON 17 /* set RSVP per-vif socket */ #define IP_RSVP_VIF_OFF 18 /* unset RSVP per-vif socket */ #define IP_PORTRANGE 19 /* int; range to choose for unspec port */ #define IP_RECVIF 20 /* bool; receive reception if w/dgram */ #define IP_FW_ADD 50 /* add a firewall rule to chain */ #define IP_FW_DEL 51 /* delete a firewall rule from chain */ #define IP_FW_FLUSH 52 /* flush firewall rule chain */ #define IP_FW_ZERO 53 /* clear single/all firewall counter(s) */ #define IP_FW_GET 54 /* get entire firewall rule chain */ #define IP_NAT 55 /* set/get NAT opts */ /* * Defaults and limits for options */ #define IP_DEFAULT_MULTICAST_TTL 1 /* normally limit m'casts to 1 hop */ #define IP_DEFAULT_MULTICAST_LOOP 1 /* normally hear sends if a member */ #define IP_MAX_MEMBERSHIPS 20 /* per socket */ /* * Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP. */ struct ip_mreq { struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */ }; /* * Argument for IP_PORTRANGE: * - which range to search when port is unspecified at bind() or connect() */ #define IP_PORTRANGE_DEFAULT 0 /* default range */ #define IP_PORTRANGE_HIGH 1 /* "high" - request firewall bypass */ #define IP_PORTRANGE_LOW 2 /* "low" - vouchsafe security */ /* * Definitions for inet sysctl operations. * * Third level is protocol number. * Fourth level is desired variable within that protocol. */ #define IPPROTO_MAXID (IPPROTO_IDP + 1) /* don't list to IPPROTO_MAX */ #define CTL_IPPROTO_NAMES { \ { "ip", CTLTYPE_NODE }, \ { "icmp", CTLTYPE_NODE }, \ { "igmp", CTLTYPE_NODE }, \ { "ggp", CTLTYPE_NODE }, \ { 0, 0 }, \ { 0, 0 }, \ { "tcp", CTLTYPE_NODE }, \ { 0, 0 }, \ { "egp", CTLTYPE_NODE }, \ { 0, 0 }, \ { 0, 0 }, \ { 0, 0 }, \ { "pup", CTLTYPE_NODE }, \ { 0, 0 }, \ { 0, 0 }, \ { 0, 0 }, \ { 0, 0 }, \ { "udp", CTLTYPE_NODE }, \ { 0, 0 }, \ { 0, 0 }, \ { 0, 0 }, \ { 0, 0 }, \ { "idp", CTLTYPE_NODE }, \ } /* * Names for IP sysctl objects */ #define IPCTL_FORWARDING 1 /* act as router */ #define IPCTL_SENDREDIRECTS 2 /* may send redirects when forwarding */ #define IPCTL_DEFTTL 3 /* default TTL */ #ifdef notyet #define IPCTL_DEFMTU 4 /* default MTU */ #endif #define IPCTL_RTEXPIRE 5 /* cloned route expiration time */ #define IPCTL_RTMINEXPIRE 6 /* min value for expiration time */ #define IPCTL_RTMAXCACHE 7 /* trigger level for dynamic expire */ #define IPCTL_SOURCEROUTE 8 /* may perform source routes */ #define IPCTL_DIRECTEDBROADCAST 9 /* may re-broadcast received packets */ #define IPCTL_INTRQMAXLEN 10 /* max length of netisr queue */ #define IPCTL_INTRQDROPS 11 /* number of netisr q drops */ #define IPCTL_STATS 12 /* ipstat structure */ #define IPCTL_ACCEPTSOURCEROUTE 13 /* may accept source routed packets */ #define IPCTL_FASTFORWARDING 14 /* use fast IP forwarding code */ #define IPCTL_MAXID 15 #define IPCTL_NAMES { \ { 0, 0 }, \ { "forwarding", CTLTYPE_INT }, \ { "redirect", CTLTYPE_INT }, \ { "ttl", CTLTYPE_INT }, \ { "mtu", CTLTYPE_INT }, \ { "rtexpire", CTLTYPE_INT }, \ { "rtminexpire", CTLTYPE_INT }, \ { "rtmaxcache", CTLTYPE_INT }, \ { "sourceroute", CTLTYPE_INT }, \ { "directed-broadcast", CTLTYPE_INT }, \ { "intr-queue-maxlen", CTLTYPE_INT }, \ { "intr-queue-drops", CTLTYPE_INT }, \ { "stats", CTLTYPE_STRUCT }, \ { "accept_sourceroute", CTLTYPE_INT }, \ { "fastforwarding", CTLTYPE_INT }, \ } #ifdef KERNEL struct ifnet; struct mbuf; /* forward declarations for Standard C */ int in_broadcast __P((struct in_addr, struct ifnet *)); int in_canforward __P((struct in_addr)); int in_cksum __P((struct mbuf *, int)); int in_localaddr __P((struct in_addr)); char *inet_ntoa __P((struct in_addr)); /* in libkern */ /* Firewall hooks */ struct ip; -typedef int ip_fw_chk_t __P((struct ip**, int, struct ifnet*, u_int16_t*, struct mbuf**)); +typedef int ip_fw_chk_t __P((struct ip**, int, struct ifnet*, u_int16_t*, struct mbuf**, struct sockaddr_in**)); typedef int ip_fw_ctl_t __P((int, struct mbuf**)); extern ip_fw_chk_t *ip_fw_chk_ptr; extern ip_fw_ctl_t *ip_fw_ctl_ptr; /* IP NAT hooks */ typedef int ip_nat_t __P((struct ip**, struct mbuf**, struct ifnet*, int)); typedef int ip_nat_ctl_t __P((int, struct mbuf**)); extern ip_nat_t *ip_nat_ptr; extern ip_nat_ctl_t *ip_nat_ctl_ptr; #define IP_NAT_IN 0x00000001 #define IP_NAT_OUT 0x00000002 #endif /* KERNEL */ #endif Index: head/sys/netinet/ip_fw.c =================================================================== --- head/sys/netinet/ip_fw.c (revision 37408) +++ head/sys/netinet/ip_fw.c (revision 37409) @@ -1,1158 +1,1193 @@ /* * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * Copyright (c) 1996 Alex Nash * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * - * $Id: ip_fw.c,v 1.90 1998/06/21 14:53:30 bde Exp $ + * $Id: ip_fw.c,v 1.91 1998/07/02 05:49:08 julian Exp $ */ /* * Implement IP packet firewall */ #ifndef IPFIREWALL_MODULE #include "opt_ipfw.h" #include "opt_ipdivert.h" #include "opt_inet.h" #ifndef INET #error IPFIREWALL requires INET. #endif /* INET */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int fw_debug = 1; #ifdef IPFIREWALL_VERBOSE static int fw_verbose = 1; #else static int fw_verbose = 0; #endif #ifdef IPFIREWALL_VERBOSE_LIMIT static int fw_verbose_limit = IPFIREWALL_VERBOSE_LIMIT; #else static int fw_verbose_limit = 0; #endif static LIST_HEAD (ip_fw_head, ip_fw_chain) ip_fw_chain; static MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's"); #ifdef SYSCTL_NODE SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, debug, CTLFLAG_RW, &fw_debug, 0, ""); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_RW, &fw_verbose, 0, ""); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_RW, &fw_verbose_limit, 0, ""); #endif #define dprintf(a) if (!fw_debug); else printf a #define print_ip(a) printf("%ld.%ld.%ld.%ld",(ntohl(a.s_addr)>>24)&0xFF,\ (ntohl(a.s_addr)>>16)&0xFF,\ (ntohl(a.s_addr)>>8)&0xFF,\ (ntohl(a.s_addr))&0xFF); #define dprint_ip(a) if (!fw_debug); else print_ip(a) static int add_entry __P((struct ip_fw_head *chainptr, struct ip_fw *frwl)); static int del_entry __P((struct ip_fw_head *chainptr, u_short number)); static int zero_entry __P((struct mbuf *m)); static struct ip_fw *check_ipfw_struct __P((struct ip_fw *m)); static struct ip_fw *check_ipfw_mbuf __P((struct mbuf *fw)); static __inline int iface_match __P((struct ifnet *ifp, union ip_fw_if *ifu, int byname)); static int ipopts_match __P((struct ip *ip, struct ip_fw *f)); static __inline int port_match __P((u_short *portptr, int nports, u_short port, int range_flag)); static int tcpflg_match __P((struct tcphdr *tcp, struct ip_fw *f)); static int icmptype_match __P((struct icmp * icmp, struct ip_fw * f)); static void ipfw_report __P((struct ip_fw *f, struct ip *ip, struct ifnet *rif, struct ifnet *oif)); #ifdef IPFIREWALL_MODULE static ip_fw_chk_t *old_chk_ptr; static ip_fw_ctl_t *old_ctl_ptr; #endif static int ip_fw_chk __P((struct ip **pip, int hlen, - struct ifnet *oif, u_int16_t *cookie, struct mbuf **m)); + struct ifnet *oif, u_int16_t *cookie, struct mbuf **m, + struct sockaddr_in **next_hop)); static int ip_fw_ctl __P((int stage, struct mbuf **mm)); static char err_prefix[] = "ip_fw_ctl:"; /* * Returns 1 if the port is matched by the vector, 0 otherwise */ static __inline int port_match(u_short *portptr, int nports, u_short port, int range_flag) { if (!nports) return 1; if (range_flag) { if (portptr[0] <= port && port <= portptr[1]) { return 1; } nports -= 2; portptr += 2; } while (nports-- > 0) { if (*portptr++ == port) { return 1; } } return 0; } static int tcpflg_match(struct tcphdr *tcp, struct ip_fw *f) { u_char flg_set, flg_clr; if ((f->fw_tcpf & IP_FW_TCPF_ESTAB) && (tcp->th_flags & (IP_FW_TCPF_RST | IP_FW_TCPF_ACK))) return 1; flg_set = tcp->th_flags & f->fw_tcpf; flg_clr = tcp->th_flags & f->fw_tcpnf; if (flg_set != f->fw_tcpf) return 0; if (flg_clr) return 0; return 1; } static int icmptype_match(struct icmp *icmp, struct ip_fw *f) { int type; if (!(f->fw_flg & IP_FW_F_ICMPBIT)) return(1); type = icmp->icmp_type; /* check for matching type in the bitmap */ if (type < IP_FW_ICMPTYPES_MAX && (f->fw_uar.fw_icmptypes[type / (sizeof(unsigned) * 8)] & (1U << (type % (8 * sizeof(unsigned)))))) return(1); return(0); /* no match */ } static int is_icmp_query(struct ip *ip) { const struct icmp *icmp; int icmp_type; icmp = (struct icmp *)((u_long *)ip + ip->ip_hl); icmp_type = icmp->icmp_type; if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ROUTERSOLICIT || icmp_type == ICMP_TSTAMP || icmp_type == ICMP_IREQ || icmp_type == ICMP_MASKREQ) return(1); return(0); } static int ipopts_match(struct ip *ip, struct ip_fw *f) { register u_char *cp; int opt, optlen, cnt; u_char opts, nopts, nopts_sve; cp = (u_char *)(ip + 1); cnt = (ip->ip_hl << 2) - sizeof (struct ip); opts = f->fw_ipopt; nopts = nopts_sve = f->fw_ipnopt; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > cnt) { return 0; /*XXX*/ } } switch (opt) { default: break; case IPOPT_LSRR: opts &= ~IP_FW_IPOPT_LSRR; nopts &= ~IP_FW_IPOPT_LSRR; break; case IPOPT_SSRR: opts &= ~IP_FW_IPOPT_SSRR; nopts &= ~IP_FW_IPOPT_SSRR; break; case IPOPT_RR: opts &= ~IP_FW_IPOPT_RR; nopts &= ~IP_FW_IPOPT_RR; break; case IPOPT_TS: opts &= ~IP_FW_IPOPT_TS; nopts &= ~IP_FW_IPOPT_TS; break; } if (opts == nopts) break; } if (opts == 0 && nopts == nopts_sve) return 1; else return 0; } static __inline int iface_match(struct ifnet *ifp, union ip_fw_if *ifu, int byname) { /* Check by name or by IP address */ if (byname) { /* Check unit number (-1 is wildcard) */ if (ifu->fu_via_if.unit != -1 && ifp->if_unit != ifu->fu_via_if.unit) return(0); /* Check name */ if (strncmp(ifp->if_name, ifu->fu_via_if.name, FW_IFNLEN)) return(0); return(1); } else if (ifu->fu_via_ip.s_addr != 0) { /* Zero == wildcard */ struct ifaddr *ia; for (ia = ifp->if_addrhead.tqh_first; ia != NULL; ia = ia->ifa_link.tqe_next) { if (ia->ifa_addr == NULL) continue; if (ia->ifa_addr->sa_family != AF_INET) continue; if (ifu->fu_via_ip.s_addr != ((struct sockaddr_in *) (ia->ifa_addr))->sin_addr.s_addr) continue; return(1); } return(0); } return(1); } static void ipfw_report(struct ip_fw *f, struct ip *ip, struct ifnet *rif, struct ifnet *oif) { static u_int64_t counter; struct tcphdr *const tcp = (struct tcphdr *) ((u_long *) ip+ ip->ip_hl); struct udphdr *const udp = (struct udphdr *) ((u_long *) ip+ ip->ip_hl); struct icmp *const icmp = (struct icmp *) ((u_long *) ip + ip->ip_hl); int count; count = f ? f->fw_pcnt : ++counter; if (fw_verbose_limit != 0 && count > fw_verbose_limit) return; /* Print command name */ printf("ipfw: %d ", f ? f->fw_number : -1); if (!f) printf("Refuse"); else switch (f->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_DENY: printf("Deny"); break; case IP_FW_F_REJECT: if (f->fw_reject_code == IP_FW_REJECT_RST) printf("Reset"); else printf("Unreach"); break; case IP_FW_F_ACCEPT: printf("Accept"); break; case IP_FW_F_COUNT: printf("Count"); break; case IP_FW_F_DIVERT: printf("Divert %d", f->fw_divert_port); break; case IP_FW_F_TEE: printf("Tee %d", f->fw_divert_port); break; case IP_FW_F_SKIPTO: printf("SkipTo %d", f->fw_skipto_rule); break; +#ifdef IPFIREWALL_FORWARD + case IP_FW_F_FWD: + printf("Forward to "); + print_ip(f->fw_fwd_ip.sin_addr); + if (f->fw_fwd_ip.sin_port) + printf(":%d", f->fw_fwd_ip.sin_port); + break; +#endif default: printf("UNKNOWN"); break; } printf(" "); switch (ip->ip_p) { case IPPROTO_TCP: printf("TCP "); print_ip(ip->ip_src); if ((ip->ip_off & IP_OFFMASK) == 0) printf(":%d ", ntohs(tcp->th_sport)); else printf(" "); print_ip(ip->ip_dst); if ((ip->ip_off & IP_OFFMASK) == 0) printf(":%d", ntohs(tcp->th_dport)); break; case IPPROTO_UDP: printf("UDP "); print_ip(ip->ip_src); if ((ip->ip_off & IP_OFFMASK) == 0) printf(":%d ", ntohs(udp->uh_sport)); else printf(" "); print_ip(ip->ip_dst); if ((ip->ip_off & IP_OFFMASK) == 0) printf(":%d", ntohs(udp->uh_dport)); break; case IPPROTO_ICMP: printf("ICMP:%u.%u ", icmp->icmp_type, icmp->icmp_code); print_ip(ip->ip_src); printf(" "); print_ip(ip->ip_dst); break; default: printf("P:%d ", ip->ip_p); print_ip(ip->ip_src); printf(" "); print_ip(ip->ip_dst); break; } if (oif) printf(" out via %s%d", oif->if_name, oif->if_unit); else if (rif) printf(" in via %s%d", rif->if_name, rif->if_unit); if ((ip->ip_off & IP_OFFMASK)) printf(" Fragment = %d",ip->ip_off & IP_OFFMASK); printf("\n"); if (fw_verbose_limit != 0 && count == fw_verbose_limit) printf("ipfw: limit reached on rule #%d\n", f ? f->fw_number : -1); } /* * Parameters: * * ip Pointer to packet header (struct ip *) * hlen Packet header length * oif Outgoing interface, or NULL if packet is incoming * *cookie Skip up to the first rule past this rule number; * *m The packet; we set to NULL when/if we nuke it. * * Return value: * * 0 The packet is to be accepted and routed normally OR * the packet was denied/rejected and has been dropped; * in the latter case, *m is equal to NULL upon return. * port Divert the packet to port. */ static int ip_fw_chk(struct ip **pip, int hlen, - struct ifnet *oif, u_int16_t *cookie, struct mbuf **m) + struct ifnet *oif, u_int16_t *cookie, struct mbuf **m, + struct sockaddr_in **next_hop) { struct ip_fw_chain *chain; struct ip_fw *rule = NULL; struct ip *ip = *pip; struct ifnet *const rif = (*m)->m_pkthdr.rcvif; u_short offset = (ip->ip_off & IP_OFFMASK); u_short src_port, dst_port; u_int16_t skipto = *cookie; *cookie = 0; /* * Go down the chain, looking for enlightment * If we've been asked to start at a given rule immediatly, do so. */ chain = LIST_FIRST(&ip_fw_chain); if ( skipto ) { if (skipto >= 65535) goto dropit; while (chain && (chain->rule->fw_number <= skipto)) { chain = LIST_NEXT(chain, chain); } if (! chain) goto dropit; } for (; chain; chain = LIST_NEXT(chain, chain)) { register struct ip_fw *const f = chain->rule; if (oif) { /* Check direction outbound */ if (!(f->fw_flg & IP_FW_F_OUT)) continue; } else { /* Check direction inbound */ if (!(f->fw_flg & IP_FW_F_IN)) continue; } /* Fragments */ if ((f->fw_flg & IP_FW_F_FRAG) && !(ip->ip_off & IP_OFFMASK)) continue; /* If src-addr doesn't match, not this rule. */ if (((f->fw_flg & IP_FW_F_INVSRC) != 0) ^ ((ip->ip_src.s_addr & f->fw_smsk.s_addr) != f->fw_src.s_addr)) continue; /* If dest-addr doesn't match, not this rule. */ if (((f->fw_flg & IP_FW_F_INVDST) != 0) ^ ((ip->ip_dst.s_addr & f->fw_dmsk.s_addr) != f->fw_dst.s_addr)) continue; /* Interface check */ if ((f->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { struct ifnet *const iface = oif ? oif : rif; /* Backwards compatibility hack for "via" */ if (!iface || !iface_match(iface, &f->fw_in_if, f->fw_flg & IP_FW_F_OIFNAME)) continue; } else { /* Check receive interface */ if ((f->fw_flg & IP_FW_F_IIFACE) && (!rif || !iface_match(rif, &f->fw_in_if, f->fw_flg & IP_FW_F_IIFNAME))) continue; /* Check outgoing interface */ if ((f->fw_flg & IP_FW_F_OIFACE) && (!oif || !iface_match(oif, &f->fw_out_if, f->fw_flg & IP_FW_F_OIFNAME))) continue; } /* Check IP options */ if (f->fw_ipopt != f->fw_ipnopt && !ipopts_match(ip, f)) continue; /* Check protocol; if wildcard, match */ if (f->fw_prot == IPPROTO_IP) goto got_match; /* If different, don't match */ if (ip->ip_p != f->fw_prot) continue; #define PULLUP_TO(len) do { \ if ((*m)->m_len < (len) \ && (*m = m_pullup(*m, (len))) == 0) { \ goto bogusfrag; \ } \ *pip = ip = mtod(*m, struct ip *); \ offset = (ip->ip_off & IP_OFFMASK); \ } while (0) /* Protocol specific checks */ switch (ip->ip_p) { case IPPROTO_TCP: { struct tcphdr *tcp; if (offset == 1) /* cf. RFC 1858 */ goto bogusfrag; if (offset != 0) { /* * TCP flags and ports aren't available in this * packet -- if this rule specified either one, * we consider the rule a non-match. */ if (f->fw_nports != 0 || f->fw_tcpf != f->fw_tcpnf) continue; break; } PULLUP_TO(hlen + 14); tcp = (struct tcphdr *) ((u_long *)ip + ip->ip_hl); if (f->fw_tcpf != f->fw_tcpnf && !tcpflg_match(tcp, f)) continue; src_port = ntohs(tcp->th_sport); dst_port = ntohs(tcp->th_dport); goto check_ports; } case IPPROTO_UDP: { struct udphdr *udp; if (offset != 0) { /* * Port specification is unavailable -- if this * rule specifies a port, we consider the rule * a non-match. */ if (f->fw_nports != 0) continue; break; } PULLUP_TO(hlen + 4); udp = (struct udphdr *) ((u_long *)ip + ip->ip_hl); src_port = ntohs(udp->uh_sport); dst_port = ntohs(udp->uh_dport); check_ports: if (!port_match(&f->fw_uar.fw_pts[0], IP_FW_GETNSRCP(f), src_port, f->fw_flg & IP_FW_F_SRNG)) continue; if (!port_match(&f->fw_uar.fw_pts[IP_FW_GETNSRCP(f)], IP_FW_GETNDSTP(f), dst_port, f->fw_flg & IP_FW_F_DRNG)) continue; break; } case IPPROTO_ICMP: { struct icmp *icmp; if (offset != 0) /* Type isn't valid */ break; PULLUP_TO(hlen + 2); icmp = (struct icmp *) ((u_long *)ip + ip->ip_hl); if (!icmptype_match(icmp, f)) continue; break; } #undef PULLUP_TO bogusfrag: if (fw_verbose) ipfw_report(NULL, ip, rif, oif); goto dropit; } got_match: /* Update statistics */ f->fw_pcnt += 1; f->fw_bcnt += ip->ip_len; f->timestamp = time_second; /* Log to console if desired */ if ((f->fw_flg & IP_FW_F_PRN) && fw_verbose) ipfw_report(f, ip, rif, oif); /* Take appropriate action */ switch (f->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_ACCEPT: return(0); case IP_FW_F_COUNT: continue; case IP_FW_F_DIVERT: *cookie = f->fw_number; return(f->fw_divert_port); case IP_FW_F_TEE: /* * XXX someday tee packet here, but beware that you * can't use m_copym() or m_copypacket() because * the divert input routine modifies the mbuf * (and these routines only increment reference * counts in the case of mbuf clusters), so need * to write custom routine. */ continue; case IP_FW_F_SKIPTO: #ifdef DIAGNOSTIC while (LIST_NEXT(chain, chain) && LIST_NEXT(chain, chain)->rule->fw_number < f->fw_skipto_rule) #else while (LIST_NEXT(chain, chain)->rule->fw_number < f->fw_skipto_rule) #endif chain = LIST_NEXT(chain, chain); continue; +#ifdef IPFIREWALL_FORWARD + case IP_FW_F_FWD: + /* Change the next-hop address for this packet. + * Initially we'll only worry about directly + * reachable next-hop's, but ultimately + * we will work out for next-hops that aren't + * direct the route we would take for it. We + * [cs]ould leave this latter problem to + * ip_output.c. We hope to high [name the abode of + * your favourite deity] that ip_output doesn't modify + * the new value of next_hop (which is dst there) + */ + if (next_hop != NULL) /* Make sure, first... */ + *next_hop = &(f->fw_fwd_ip); + return(0); /* Allow the packet */ +#endif } /* Deny/reject this packet using this rule */ rule = f; break; + } #ifdef DIAGNOSTIC /* Rule 65535 should always be there and should always match */ if (!chain) panic("ip_fw: chain"); #endif /* * At this point, we're going to drop the packet. * Send a reject notice if all of the following are true: * * - The packet matched a reject rule * - The packet is not an ICMP packet, or is an ICMP query packet * - The packet is not a multicast or broadcast packet */ if ((rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_REJECT && (ip->ip_p != IPPROTO_ICMP || is_icmp_query(ip)) && !((*m)->m_flags & (M_BCAST|M_MCAST)) && !IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { switch (rule->fw_reject_code) { case IP_FW_REJECT_RST: { struct tcphdr *const tcp = (struct tcphdr *) ((u_long *)ip + ip->ip_hl); struct tcpiphdr ti, *const tip = (struct tcpiphdr *) ip; if (offset != 0 || (tcp->th_flags & TH_RST)) break; ti.ti_i = *((struct ipovly *) ip); ti.ti_t = *tcp; bcopy(&ti, ip, sizeof(ti)); NTOHL(tip->ti_seq); NTOHL(tip->ti_ack); tip->ti_len = ip->ip_len - hlen - (tip->ti_off << 2); if (tcp->th_flags & TH_ACK) { tcp_respond(NULL, tip, *m, (tcp_seq)0, ntohl(tcp->th_ack), TH_RST); } else { if (tcp->th_flags & TH_SYN) tip->ti_len++; tcp_respond(NULL, tip, *m, tip->ti_seq + tip->ti_len, (tcp_seq)0, TH_RST|TH_ACK); } *m = NULL; break; } default: /* Send an ICMP unreachable using code */ icmp_error(*m, ICMP_UNREACH, rule->fw_reject_code, 0L, 0); *m = NULL; break; } } dropit: /* * Finally, drop the packet. */ *cookie = 0; if (*m) { m_freem(*m); *m = NULL; } return(0); } static int add_entry(struct ip_fw_head *chainptr, struct ip_fw *frwl) { struct ip_fw *ftmp = 0; struct ip_fw_chain *fwc = 0, *fcp, *fcpl = 0; u_short nbr = 0; int s; fwc = malloc(sizeof *fwc, M_IPFW, M_DONTWAIT); ftmp = malloc(sizeof *ftmp, M_IPFW, M_DONTWAIT); if (!fwc || !ftmp) { dprintf(("%s malloc said no\n", err_prefix)); if (fwc) free(fwc, M_IPFW); if (ftmp) free(ftmp, M_IPFW); return (ENOSPC); } bcopy(frwl, ftmp, sizeof(struct ip_fw)); ftmp->fw_in_if.fu_via_if.name[FW_IFNLEN - 1] = '\0'; ftmp->fw_pcnt = 0L; ftmp->fw_bcnt = 0L; fwc->rule = ftmp; s = splnet(); if (!LIST_FIRST(chainptr)) { LIST_INSERT_HEAD(chainptr, fwc, chain); splx(s); return(0); } else if (ftmp->fw_number == (u_short)-1) { if (fwc) free(fwc, M_IPFW); if (ftmp) free(ftmp, M_IPFW); splx(s); dprintf(("%s bad rule number\n", err_prefix)); return (EINVAL); } /* If entry number is 0, find highest numbered rule and add 100 */ if (ftmp->fw_number == 0) { for (fcp = LIST_FIRST(chainptr); fcp; fcp = LIST_NEXT(fcp, chain)) { if (fcp->rule->fw_number != (u_short)-1) nbr = fcp->rule->fw_number; else break; } if (nbr < (u_short)-1 - 100) nbr += 100; ftmp->fw_number = nbr; } /* Got a valid number; now insert it, keeping the list ordered */ for (fcp = LIST_FIRST(chainptr); fcp; fcp = LIST_NEXT(fcp, chain)) { if (fcp->rule->fw_number > ftmp->fw_number) { if (fcpl) { LIST_INSERT_AFTER(fcpl, fwc, chain); } else { LIST_INSERT_HEAD(chainptr, fwc, chain); } break; } else { fcpl = fcp; } } splx(s); return (0); } static int del_entry(struct ip_fw_head *chainptr, u_short number) { struct ip_fw_chain *fcp; fcp = LIST_FIRST(chainptr); if (number != (u_short)-1) { for (; fcp; fcp = LIST_NEXT(fcp, chain)) { if (fcp->rule->fw_number == number) { int s; /* prevent access to rules while removing them */ s = splnet(); while (fcp && fcp->rule->fw_number == number) { struct ip_fw_chain *next; next = LIST_NEXT(fcp, chain); LIST_REMOVE(fcp, chain); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); fcp = next; } splx(s); return 0; } } } return (EINVAL); } static int zero_entry(struct mbuf *m) { struct ip_fw *frwl; struct ip_fw_chain *fcp; int s; if (m) { if (m->m_len != sizeof(struct ip_fw)) return(EINVAL); frwl = mtod(m, struct ip_fw *); } else frwl = NULL; if (!frwl) { s = splnet(); for (fcp = LIST_FIRST(&ip_fw_chain); fcp; fcp = LIST_NEXT(fcp, chain)) { fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; fcp->rule->timestamp = 0; } splx(s); } else { int cleared = 0; /* * It's possible to insert multiple chain entries with the * same number, so we don't stop after finding the first * match if zeroing a specific entry. */ for (fcp = LIST_FIRST(&ip_fw_chain); fcp; fcp = LIST_NEXT(fcp, chain)) if (frwl->fw_number == fcp->rule->fw_number) { s = splnet(); while (fcp && frwl->fw_number == fcp->rule->fw_number) { fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; fcp->rule->timestamp = 0; fcp = LIST_NEXT(fcp, chain); } splx(s); cleared = 1; break; } if (!cleared) return(EINVAL); /* we didn't find any matching rules */ } if (fw_verbose) { if (frwl) printf("ipfw: Entry %d cleared.\n", frwl->fw_number); else printf("ipfw: Accounting cleared.\n"); } return(0); } static struct ip_fw * check_ipfw_mbuf(struct mbuf *m) { /* Check length */ if (m->m_len != sizeof(struct ip_fw)) { dprintf(("%s len=%d, want %d\n", err_prefix, m->m_len, sizeof(struct ip_fw))); return (NULL); } return(check_ipfw_struct(mtod(m, struct ip_fw *))); } static struct ip_fw * check_ipfw_struct(struct ip_fw *frwl) { /* Check for invalid flag bits */ if ((frwl->fw_flg & ~IP_FW_F_MASK) != 0) { dprintf(("%s undefined flag bits set (flags=%x)\n", err_prefix, frwl->fw_flg)); return (NULL); } /* Must apply to incoming or outgoing (or both) */ if (!(frwl->fw_flg & (IP_FW_F_IN | IP_FW_F_OUT))) { dprintf(("%s neither in nor out\n", err_prefix)); return (NULL); } /* Empty interface name is no good */ if (((frwl->fw_flg & IP_FW_F_IIFNAME) && !*frwl->fw_in_if.fu_via_if.name) || ((frwl->fw_flg & IP_FW_F_OIFNAME) && !*frwl->fw_out_if.fu_via_if.name)) { dprintf(("%s empty interface name\n", err_prefix)); return (NULL); } /* Sanity check interface matching */ if ((frwl->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { ; /* allow "via" backwards compatibility */ } else if ((frwl->fw_flg & IP_FW_F_IN) && (frwl->fw_flg & IP_FW_F_OIFACE)) { dprintf(("%s outgoing interface check on incoming\n", err_prefix)); return (NULL); } /* Sanity check port ranges */ if ((frwl->fw_flg & IP_FW_F_SRNG) && IP_FW_GETNSRCP(frwl) < 2) { dprintf(("%s src range set but n_src_p=%d\n", err_prefix, IP_FW_GETNSRCP(frwl))); return (NULL); } if ((frwl->fw_flg & IP_FW_F_DRNG) && IP_FW_GETNDSTP(frwl) < 2) { dprintf(("%s dst range set but n_dst_p=%d\n", err_prefix, IP_FW_GETNDSTP(frwl))); return (NULL); } if (IP_FW_GETNSRCP(frwl) + IP_FW_GETNDSTP(frwl) > IP_FW_MAX_PORTS) { dprintf(("%s too many ports (%d+%d)\n", err_prefix, IP_FW_GETNSRCP(frwl), IP_FW_GETNDSTP(frwl))); return (NULL); } /* * Protocols other than TCP/UDP don't use port range */ if ((frwl->fw_prot != IPPROTO_TCP) && (frwl->fw_prot != IPPROTO_UDP) && (IP_FW_GETNSRCP(frwl) || IP_FW_GETNDSTP(frwl))) { dprintf(("%s port(s) specified for non TCP/UDP rule\n", err_prefix)); return(NULL); } /* * Rather than modify the entry to make such entries work, * we reject this rule and require user level utilities * to enforce whatever policy they deem appropriate. */ if ((frwl->fw_src.s_addr & (~frwl->fw_smsk.s_addr)) || (frwl->fw_dst.s_addr & (~frwl->fw_dmsk.s_addr))) { dprintf(("%s rule never matches\n", err_prefix)); return(NULL); } if ((frwl->fw_flg & IP_FW_F_FRAG) && (frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) { if (frwl->fw_nports) { dprintf(("%s cannot mix 'frag' and ports\n", err_prefix)); return(NULL); } if (frwl->fw_prot == IPPROTO_TCP && frwl->fw_tcpf != frwl->fw_tcpnf) { dprintf(("%s cannot mix 'frag' and TCP flags\n", err_prefix)); return(NULL); } } /* Check command specific stuff */ switch (frwl->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_REJECT: if (frwl->fw_reject_code >= 0x100 && !(frwl->fw_prot == IPPROTO_TCP && frwl->fw_reject_code == IP_FW_REJECT_RST)) { dprintf(("%s unknown reject code\n", err_prefix)); return(NULL); } break; case IP_FW_F_DIVERT: /* Diverting to port zero is invalid */ case IP_FW_F_TEE: if (frwl->fw_divert_port == 0) { dprintf(("%s can't divert to port 0\n", err_prefix)); return (NULL); } break; case IP_FW_F_DENY: case IP_FW_F_ACCEPT: case IP_FW_F_COUNT: case IP_FW_F_SKIPTO: +#ifdef IPFIREWALL_FORWARD + case IP_FW_F_FWD: +#endif break; default: dprintf(("%s invalid command\n", err_prefix)); return(NULL); } return frwl; } static int ip_fw_ctl(int stage, struct mbuf **mm) { int error; /* * If we have any number of rules, then it's worth while * using clusters for this. The smaller case is rare. * Note that using clusters for setsockopt is only in 3.0 at this time. */ struct mbuf *m; if (stage == IP_FW_GET) { /* * If we have any number of rules, then it's worth while * using clusters for this. The smaller case is rare. * Note that using clusters for setsockopt is only in * 3.0 at this time. */ struct ip_fw_chain *fcp = LIST_FIRST(&ip_fw_chain); *mm = m = m_get(M_WAIT, MT_SOOPTS); if (m == NULL) return (ENOBUFS); MCLGET(m, M_WAIT); - if(!(m->m_flags & M_EXT)) { + if (!(m->m_flags & M_EXT)) { abort: m_freem(*mm); *mm = NULL; return (ENOBUFS); } m->m_len = 0; for (; fcp; fcp = LIST_NEXT(fcp, chain)) { /* Will we need a new cluster? */ - if((m->m_len + sizeof *(fcp->rule)) > MCLBYTES) { + if ((m->m_len + sizeof *(fcp->rule)) > MCLBYTES) { m = m->m_next = m_get(M_WAIT, MT_SOOPTS); if (m == NULL) { goto abort; } MCLGET(m, M_WAIT); if (!(m->m_flags & M_EXT)) { goto abort; } m->m_len = 0; } memcpy(m->m_data + m->m_len, fcp->rule, sizeof *(fcp->rule)); m->m_len += sizeof *(fcp->rule); } return (0); } m = *mm; /* only allow get calls if secure mode > 2 */ if (securelevel > 2) { if (m) (void)m_free(m); return(EPERM); } if (stage == IP_FW_FLUSH) { while (LIST_FIRST(&ip_fw_chain) != NULL && LIST_FIRST(&ip_fw_chain)->rule->fw_number != (u_short)-1) { struct ip_fw_chain *fcp = LIST_FIRST(&ip_fw_chain); int s = splnet(); LIST_REMOVE(LIST_FIRST(&ip_fw_chain), chain); splx(s); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); } if (m) (void)m_free(m); return (0); } if (stage == IP_FW_ZERO) { error = zero_entry(m); if (m) (void)m_free(m); return (error); } if (m == NULL) { printf("%s NULL mbuf ptr\n", err_prefix); return (EINVAL); } if (stage == IP_FW_ADD) { struct ip_fw *frwl = check_ipfw_mbuf(m); if (!frwl) error = EINVAL; else error = add_entry(&ip_fw_chain, frwl); if (m) (void)m_free(m); return error; } if (stage == IP_FW_DEL) { if (m->m_len != sizeof(struct ip_fw)) { dprintf(("%s len=%d, want %d\n", err_prefix, m->m_len, sizeof(struct ip_fw))); error = EINVAL; } else if (mtod(m, struct ip_fw *)->fw_number == (u_short)-1) { dprintf(("%s can't delete rule 65535\n", err_prefix)); error = EINVAL; } else error = del_entry(&ip_fw_chain, mtod(m, struct ip_fw *)->fw_number); if (m) (void)m_free(m); return error; } dprintf(("%s unknown request %d\n", err_prefix, stage)); if (m) (void)m_free(m); return (EINVAL); } void ip_fw_init(void) { struct ip_fw default_rule; ip_fw_chk_ptr = ip_fw_chk; ip_fw_ctl_ptr = ip_fw_ctl; LIST_INIT(&ip_fw_chain); bzero(&default_rule, sizeof default_rule); default_rule.fw_prot = IPPROTO_IP; default_rule.fw_number = (u_short)-1; #ifdef IPFIREWALL_DEFAULT_TO_ACCEPT default_rule.fw_flg |= IP_FW_F_ACCEPT; #else default_rule.fw_flg |= IP_FW_F_DENY; #endif default_rule.fw_flg |= IP_FW_F_IN | IP_FW_F_OUT; if (check_ipfw_struct(&default_rule) == NULL || add_entry(&ip_fw_chain, &default_rule)) panic("ip_fw_init"); printf("IP packet filtering initialized, " #ifdef IPDIVERT "divert enabled, "); #else "divert disabled, "); +#endif +#ifdef IPFIREWALL_FORWARD + printf("rule-based forwarding enabled, "); +#else + printf("rule-based forwarding disabled, "); #endif #ifdef IPFIREWALL_DEFAULT_TO_ACCEPT printf("default to accept, "); #endif #ifndef IPFIREWALL_VERBOSE printf("logging disabled\n"); #else if (fw_verbose_limit == 0) printf("unlimited logging\n"); else printf("logging limited to %d packets/entry\n", fw_verbose_limit); #endif } #ifdef IPFIREWALL_MODULE #include #include #include MOD_MISC(ipfw); static int ipfw_load(struct lkm_table *lkmtp, int cmd) { int s=splnet(); old_chk_ptr = ip_fw_chk_ptr; old_ctl_ptr = ip_fw_ctl_ptr; ip_fw_init(); splx(s); return 0; } static int ipfw_unload(struct lkm_table *lkmtp, int cmd) { int s=splnet(); ip_fw_chk_ptr = old_chk_ptr; ip_fw_ctl_ptr = old_ctl_ptr; while (LIST_FIRST(&ip_fw_chain) != NULL) { struct ip_fw_chain *fcp = LIST_FIRST(&ip_fw_chain); LIST_REMOVE(LIST_FIRST(&ip_fw_chain), chain); free(fcp->rule, M_IPFW); free(fcp, M_IPFW); } splx(s); printf("IP firewall unloaded\n"); return 0; } int ipfw_mod(struct lkm_table *lkmtp, int cmd, int ver) { MOD_DISPATCH(ipfw, lkmtp, cmd, ver, ipfw_load, ipfw_unload, lkm_nullcmd); } #endif Index: head/sys/netinet/ip_fw.h =================================================================== --- head/sys/netinet/ip_fw.h (revision 37408) +++ head/sys/netinet/ip_fw.h (revision 37409) @@ -1,186 +1,189 @@ /* * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. * * Redistribution in binary form may occur without any restrictions. * Obviously, it would be nice if you gave credit where credit is due * but requiring it would be too onerous. * * This software is provided ``AS IS'' without any warranties of any kind. * - * $Id: ip_fw.h,v 1.31 1998/01/08 03:03:54 alex Exp $ + * $Id: ip_fw.h,v 1.32 1998/02/03 22:15:03 bde Exp $ */ #ifndef _IP_FW_H #define _IP_FW_H #include /* * This union structure identifies an interface, either explicitly * by name or implicitly by IP address. The flags IP_FW_F_IIFNAME * and IP_FW_F_OIFNAME say how to interpret this structure. An * interface unit number of -1 matches any unit number, while an * IP address of 0.0.0.0 indicates matches any interface. * * The receive and transmit interfaces are only compared against the * the packet if the corresponding bit (IP_FW_F_IIFACE or IP_FW_F_OIFACE) * is set. Note some packets lack a receive or transmit interface * (in which case the missing "interface" never matches). */ union ip_fw_if { struct in_addr fu_via_ip; /* Specified by IP address */ struct { /* Specified by interface name */ #define FW_IFNLEN IFNAMSIZ char name[FW_IFNLEN]; short unit; /* -1 means match any unit */ } fu_via_if; }; /* * Format of an IP firewall descriptor * * fw_src, fw_dst, fw_smsk, fw_dmsk are always stored in network byte order. * fw_flg and fw_n*p are stored in host byte order (of course). * Port numbers are stored in HOST byte order. * Warning: setsockopt() will fail if sizeof(struct ip_fw) > MLEN (108) */ struct ip_fw { u_int64_t fw_pcnt,fw_bcnt; /* Packet and byte counters */ struct in_addr fw_src, fw_dst; /* Source and destination IP addr */ struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */ u_short fw_number; /* Rule number */ u_short fw_flg; /* Flags word */ #define IP_FW_MAX_PORTS 10 /* A reasonable maximum */ union { u_short fw_pts[IP_FW_MAX_PORTS]; /* Array of port numbers to match */ #define IP_FW_ICMPTYPES_MAX 128 #define IP_FW_ICMPTYPES_DIM (IP_FW_ICMPTYPES_MAX / (sizeof(unsigned) * 8)) unsigned fw_icmptypes[IP_FW_ICMPTYPES_DIM]; /* ICMP types bitmap */ } fw_uar; u_char fw_ipopt,fw_ipnopt; /* IP options set/unset */ u_char fw_tcpf,fw_tcpnf; /* TCP flags set/unset */ long timestamp; /* timestamp (tv_sec) of last match */ union ip_fw_if fw_in_if, fw_out_if; /* Incoming and outgoing interfaces */ union { u_short fu_divert_port; /* Divert/tee port (options IPDIVERT) */ u_short fu_skipto_rule; /* SKIPTO command rule number */ u_short fu_reject_code; /* REJECT response code */ + struct sockaddr_in fu_fwd_ip; } fw_un; u_char fw_prot; /* IP protocol */ u_char fw_nports; /* N'of src ports and # of dst ports */ /* in ports array (dst ports follow */ /* src ports; max of 10 ports in all; */ /* count of 0 means match all ports) */ }; #define IP_FW_GETNSRCP(rule) ((rule)->fw_nports & 0x0f) #define IP_FW_SETNSRCP(rule, n) do { \ (rule)->fw_nports &= ~0x0f; \ (rule)->fw_nports |= (n); \ } while (0) #define IP_FW_GETNDSTP(rule) ((rule)->fw_nports >> 4) #define IP_FW_SETNDSTP(rule, n) do { \ (rule)->fw_nports &= ~0xf0; \ (rule)->fw_nports |= (n) << 4;\ } while (0) #define fw_divert_port fw_un.fu_divert_port #define fw_skipto_rule fw_un.fu_skipto_rule #define fw_reject_code fw_un.fu_reject_code +#define fw_fwd_ip fw_un.fu_fwd_ip struct ip_fw_chain { LIST_ENTRY(ip_fw_chain) chain; struct ip_fw *rule; }; /* * Values for "flags" field . */ #define IP_FW_F_IN 0x0001 /* Check inbound packets */ #define IP_FW_F_OUT 0x0002 /* Check outbound packets */ #define IP_FW_F_IIFACE 0x0004 /* Apply inbound interface test */ #define IP_FW_F_OIFACE 0x0008 /* Apply outbound interface test */ #define IP_FW_F_COMMAND 0x0070 /* Mask for type of chain entry: */ #define IP_FW_F_DENY 0x0000 /* This is a deny rule */ #define IP_FW_F_REJECT 0x0010 /* Deny and send a response packet */ #define IP_FW_F_ACCEPT 0x0020 /* This is an accept rule */ #define IP_FW_F_COUNT 0x0030 /* This is a count rule */ #define IP_FW_F_DIVERT 0x0040 /* This is a divert rule */ #define IP_FW_F_TEE 0x0050 /* This is a tee rule */ #define IP_FW_F_SKIPTO 0x0060 /* This is a skipto rule */ +#define IP_FW_F_FWD 0x0070 /* This is a "change forwarding address" rule */ #define IP_FW_F_PRN 0x0080 /* Print if this rule matches */ #define IP_FW_F_SRNG 0x0100 /* The first two src ports are a min * * and max range (stored in host byte * * order). */ #define IP_FW_F_DRNG 0x0200 /* The first two dst ports are a min * * and max range (stored in host byte * * order). */ #define IP_FW_F_IIFNAME 0x0400 /* In interface by name/unit (not IP) */ #define IP_FW_F_OIFNAME 0x0800 /* Out interface by name/unit (not IP) */ #define IP_FW_F_INVSRC 0x1000 /* Invert sense of src check */ #define IP_FW_F_INVDST 0x2000 /* Invert sense of dst check */ #define IP_FW_F_FRAG 0x4000 /* Fragment */ #define IP_FW_F_ICMPBIT 0x8000 /* ICMP type bitmap is valid */ #define IP_FW_F_MASK 0xFFFF /* All possible flag bits mask */ /* * For backwards compatibility with rules specifying "via iface" but * not restricted to only "in" or "out" packets, we define this combination * of bits to represent this configuration. */ #define IF_FW_F_VIAHACK (IP_FW_F_IN|IP_FW_F_OUT|IP_FW_F_IIFACE|IP_FW_F_OIFACE) /* * Definitions for REJECT response codes. * Values less than 256 correspond to ICMP unreachable codes. */ #define IP_FW_REJECT_RST 0x0100 /* TCP packets: send RST */ /* * Definitions for IP option names. */ #define IP_FW_IPOPT_LSRR 0x01 #define IP_FW_IPOPT_SSRR 0x02 #define IP_FW_IPOPT_RR 0x04 #define IP_FW_IPOPT_TS 0x08 /* * Definitions for TCP flags. */ #define IP_FW_TCPF_FIN TH_FIN #define IP_FW_TCPF_SYN TH_SYN #define IP_FW_TCPF_RST TH_RST #define IP_FW_TCPF_PSH TH_PUSH #define IP_FW_TCPF_ACK TH_ACK #define IP_FW_TCPF_URG TH_URG #define IP_FW_TCPF_ESTAB 0x40 /* * Main firewall chains definitions and global var's definitions. */ #ifdef KERNEL /* * Function definitions. */ void ip_fw_init __P((void)); #endif /* KERNEL */ #endif /* _IP_FW_H */ Index: head/sys/netinet/ip_input.c =================================================================== --- head/sys/netinet/ip_input.c (revision 37408) +++ head/sys/netinet/ip_input.c (revision 37409) @@ -1,1544 +1,1580 @@ /* * Copyright (c) 1982, 1986, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 - * $Id: ip_input.c,v 1.90 1998/06/12 03:48:16 julian Exp $ + * $Id: ip_input.c,v 1.91 1998/07/02 05:49:12 julian Exp $ * $ANA: ip_input.c,v 1.5 1996/09/18 14:34:59 wollman Exp $ */ #define _IP_VHL #include "opt_bootp.h" #include "opt_ipfw.h" #include "opt_ipdivert.h" #include "opt_ipfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IPFIREWALL #include #endif int rsvp_on = 0; static int ip_rsvp_on; struct socket *ip_rsvpd; int ipforwarding = 0; SYSCTL_INT(_net_inet_ip, IPCTL_FORWARDING, forwarding, CTLFLAG_RW, &ipforwarding, 0, ""); static int ipsendredirects = 1; /* XXX */ SYSCTL_INT(_net_inet_ip, IPCTL_SENDREDIRECTS, redirect, CTLFLAG_RW, &ipsendredirects, 0, ""); int ip_defttl = IPDEFTTL; SYSCTL_INT(_net_inet_ip, IPCTL_DEFTTL, ttl, CTLFLAG_RW, &ip_defttl, 0, ""); static int ip_dosourceroute = 0; SYSCTL_INT(_net_inet_ip, IPCTL_SOURCEROUTE, sourceroute, CTLFLAG_RW, &ip_dosourceroute, 0, ""); static int ip_acceptsourceroute = 0; SYSCTL_INT(_net_inet_ip, IPCTL_ACCEPTSOURCEROUTE, accept_sourceroute, CTLFLAG_RW, &ip_acceptsourceroute, 0, ""); #ifdef DIAGNOSTIC static int ipprintfs = 0; #endif extern struct domain inetdomain; extern struct protosw inetsw[]; u_char ip_protox[IPPROTO_MAX]; static int ipqmaxlen = IFQ_MAXLEN; struct in_ifaddrhead in_ifaddrhead; /* first inet address */ struct ifqueue ipintrq; SYSCTL_INT(_net_inet_ip, IPCTL_INTRQMAXLEN, intr_queue_maxlen, CTLFLAG_RD, &ipintrq.ifq_maxlen, 0, ""); SYSCTL_INT(_net_inet_ip, IPCTL_INTRQDROPS, intr_queue_drops, CTLFLAG_RD, &ipintrq.ifq_drops, 0, ""); struct ipstat ipstat; SYSCTL_STRUCT(_net_inet_ip, IPCTL_STATS, stats, CTLFLAG_RD, &ipstat, ipstat, ""); /* Packet reassembly stuff */ #define IPREASS_NHASH_LOG2 6 #define IPREASS_NHASH (1 << IPREASS_NHASH_LOG2) #define IPREASS_HMASK (IPREASS_NHASH - 1) #define IPREASS_HASH(x,y) \ ((((x) & 0xF | ((((x) >> 8) & 0xF) << 4)) ^ (y)) & IPREASS_HMASK) static struct ipq ipq[IPREASS_NHASH]; static int nipq = 0; /* total # of reass queues */ static int maxnipq; #ifdef IPCTL_DEFMTU SYSCTL_INT(_net_inet_ip, IPCTL_DEFMTU, mtu, CTLFLAG_RW, &ip_mtu, 0, ""); #endif #if !defined(COMPAT_IPFW) || COMPAT_IPFW == 1 #undef COMPAT_IPFW #define COMPAT_IPFW 1 #else #undef COMPAT_IPFW #endif #ifdef COMPAT_IPFW /* Firewall hooks */ ip_fw_chk_t *ip_fw_chk_ptr; ip_fw_ctl_t *ip_fw_ctl_ptr; /* IP Network Address Translation (NAT) hooks */ ip_nat_t *ip_nat_ptr; ip_nat_ctl_t *ip_nat_ctl_ptr; #endif #if defined(IPFILTER_LKM) || defined(IPFILTER) int iplattach __P((void)); int (*fr_checkp) __P((struct ip *, int, struct ifnet *, int, struct mbuf **)) = NULL; #endif /* * We need to save the IP options in case a protocol wants to respond * to an incoming packet over the same route if the packet got here * using IP source routing. This allows connection establishment and * maintenance when the remote end is on a network that is not known * to us. */ static int ip_nhops = 0; static struct ip_srcrt { struct in_addr dst; /* final destination */ char nop; /* one NOP to align */ char srcopt[IPOPT_OFFSET + 1]; /* OPTVAL, OLEN and OFFSET */ struct in_addr route[MAX_IPOPTLEN/sizeof(struct in_addr)]; } ip_srcrt; #ifdef IPDIVERT /* * Shared variable between ip_input() and ip_reass() to communicate * about which packets, once assembled from fragments, get diverted, * and to which port. */ static u_short frag_divert_port; #endif +struct sockaddr_in *ip_fw_fwd_addr; + static void save_rte __P((u_char *, struct in_addr)); static void ip_deq __P((struct ipasfrag *)); static int ip_dooptions __P((struct mbuf *)); static void ip_enq __P((struct ipasfrag *, struct ipasfrag *)); static void ip_forward __P((struct mbuf *, int)); static void ip_freef __P((struct ipq *)); static struct ip * ip_reass __P((struct ipasfrag *, struct ipq *, struct ipq *)); static struct in_ifaddr * ip_rtaddr __P((struct in_addr)); static void ipintr __P((void)); /* * IP initialization: fill in IP protocol switch table. * All protocols not implemented in kernel go to raw IP protocol handler. */ void ip_init() { register struct protosw *pr; register int i; TAILQ_INIT(&in_ifaddrhead); pr = pffindproto(PF_INET, IPPROTO_RAW, SOCK_RAW); if (pr == 0) panic("ip_init"); for (i = 0; i < IPPROTO_MAX; i++) ip_protox[i] = pr - inetsw; for (pr = inetdomain.dom_protosw; pr < inetdomain.dom_protoswNPROTOSW; pr++) if (pr->pr_domain->dom_family == PF_INET && pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) ip_protox[pr->pr_protocol] = pr - inetsw; for (i = 0; i < IPREASS_NHASH; i++) ipq[i].next = ipq[i].prev = &ipq[i]; maxnipq = nmbclusters/4; ip_id = time_second & 0xffff; ipintrq.ifq_maxlen = ipqmaxlen; #ifdef IPFIREWALL ip_fw_init(); #endif #ifdef IPNAT ip_nat_init(); #endif #ifdef IPFILTER iplattach(); #endif } static struct sockaddr_in ipaddr = { sizeof(ipaddr), AF_INET }; static struct route ipforward_rt; /* * Ip input routine. Checksum and byte swap header. If fragmented * try to reassemble. Process options. Pass to next level. */ void ip_input(struct mbuf *m) { struct ip *ip; struct ipq *fp; struct in_ifaddr *ia; int i, hlen; u_short sum; #ifdef DIAGNOSTIC if (m == NULL || (m->m_flags & M_PKTHDR) == 0) panic("ip_input no HDR"); #endif /* * If no IP addresses have been set yet but the interfaces * are receiving, can't do anything with incoming packets yet. * XXX This is broken! We should be able to receive broadcasts * and multicasts even without any local addresses configured. */ if (TAILQ_EMPTY(&in_ifaddrhead)) goto bad; ipstat.ips_total++; if (m->m_pkthdr.len < sizeof(struct ip)) goto tooshort; if (m->m_len < sizeof (struct ip) && (m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; return; } ip = mtod(m, struct ip *); if (IP_VHL_V(ip->ip_vhl) != IPVERSION) { ipstat.ips_badvers++; goto bad; } hlen = IP_VHL_HL(ip->ip_vhl) << 2; if (hlen < sizeof(struct ip)) { /* minimum header length */ ipstat.ips_badhlen++; goto bad; } if (hlen > m->m_len) { if ((m = m_pullup(m, hlen)) == 0) { ipstat.ips_badhlen++; return; } ip = mtod(m, struct ip *); } if (hlen == sizeof(struct ip)) { sum = in_cksum_hdr(ip); } else { sum = in_cksum(m, hlen); } if (sum) { ipstat.ips_badsum++; goto bad; } /* * Convert fields to host representation. */ NTOHS(ip->ip_len); if (ip->ip_len < hlen) { ipstat.ips_badlen++; goto bad; } NTOHS(ip->ip_id); NTOHS(ip->ip_off); /* * Check that the amount of data in the buffers * is as at least much as the IP header would have us expect. * Trim mbufs if longer than we expect. * Drop packet if shorter than we expect. */ if (m->m_pkthdr.len < ip->ip_len) { tooshort: ipstat.ips_tooshort++; goto bad; } if (m->m_pkthdr.len > ip->ip_len) { if (m->m_len == m->m_pkthdr.len) { m->m_len = ip->ip_len; m->m_pkthdr.len = ip->ip_len; } else m_adj(m, ip->ip_len - m->m_pkthdr.len); } /* * IpHack's section. * Right now when no processing on packet has done * and it is still fresh out of network we do our black * deals with it. * - Firewall: deny/allow/divert * - Xlate: translate packet's addr/port (NAT). * - Wrap: fake packet's addr/port * - Encapsulate: put it in another IP and send out. */ #if defined(IPFILTER) || defined(IPFILTER_LKM) /* * Check if we want to allow this packet to be processed. * Consider it to be bad if not. */ if (fr_checkp) { struct mbuf *m1 = m; if ((*fr_checkp)(ip, hlen, m->m_pkthdr.rcvif, 0, &m1) || !m1) return; ip = mtod(m = m1, struct ip *); } #endif #ifdef COMPAT_IPFW if (ip_fw_chk_ptr) { -#ifdef IPDIVERT - u_short port; + u_int16_t port; - port = (*ip_fw_chk_ptr)(&ip, hlen, NULL, &ip_divert_cookie, &m); +#ifdef IPFIREWALL_FORWARD + /* + * If we've been forwarded from the output side, then + * skip the firewall a second time + */ + if (ip_fw_fwd_addr) + goto ours; +#endif /* IPFIREWALL_FORWARD */ +#ifdef IPDIVERT + port = (*ip_fw_chk_ptr)(&ip, hlen, NULL, &ip_divert_cookie, + &m, &ip_fw_fwd_addr); if (port) { /* Divert packet */ frag_divert_port = port; goto ours; } -#else - u_int16_t dummy = 0; - /* If ipfw says divert, we have to just drop packet */ - if ((*ip_fw_chk_ptr)(&ip, hlen, NULL, &dummy, &m)) { +#else /* !DIVERT */ + /* + * If ipfw says divert, we have to just drop packet */ + * Use port as a dummy argument. + */ + port = 0; + if ((*ip_fw_chk_ptr)(&ip, hlen, NULL, &port, + &m, &ip_fw_fwd_addr)) { m_freem(m); m = NULL; } -#endif +#endif /* !DIVERT */ if (!m) return; } if (ip_nat_ptr && !(*ip_nat_ptr)(&ip, &m, m->m_pkthdr.rcvif, IP_NAT_IN)) return; -#endif +#endif /* !COMPAT_IPFW */ /* * Process options and, if not destined for us, * ship it on. ip_dooptions returns 1 when an * error was detected (causing an icmp message * to be sent and the original packet to be freed). */ ip_nhops = 0; /* for source routed packets */ if (hlen > sizeof (struct ip) && ip_dooptions(m)) return; /* greedy RSVP, snatches any PATH packet of the RSVP protocol and no * matter if it is destined to another node, or whether it is * a multicast one, RSVP wants it! and prevents it from being forwarded * anywhere else. Also checks if the rsvp daemon is running before * grabbing the packet. */ if (rsvp_on && ip->ip_p==IPPROTO_RSVP) goto ours; /* * Check our list of addresses, to see if the packet is for us. */ - for (ia = in_ifaddrhead.tqh_first; ia; ia = ia->ia_link.tqe_next) { + for (ia = TAILQ_FIRST(in_ifaddrhead); ia; + ia = TAILQ_NEXT(ia, ia_link)) { #define satosin(sa) ((struct sockaddr_in *)(sa)) if (IA_SIN(ia)->sin_addr.s_addr == ip->ip_dst.s_addr) goto ours; #ifdef BOOTP_COMPAT if (IA_SIN(ia)->sin_addr.s_addr == INADDR_ANY) goto ours; #endif +#ifdef IPFIREWALL_FORWARD + /* + * If the addr to forward to is one of ours, we pretend to + * be the destination for this packet. + */ + if (ip_fw_fwd_addr != NULL && + IA_SIN(ia)->sin_addr.s_addr == + ip_fw_fwd_addr->sin_addr.s_addr) + goto ours; +#endif if (ia->ia_ifp && ia->ia_ifp->if_flags & IFF_BROADCAST) { if (satosin(&ia->ia_broadaddr)->sin_addr.s_addr == ip->ip_dst.s_addr) goto ours; if (ip->ip_dst.s_addr == ia->ia_netbroadcast.s_addr) goto ours; } } if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { struct in_multi *inm; if (ip_mrouter) { /* * If we are acting as a multicast router, all * incoming multicast packets are passed to the * kernel-level multicast forwarding function. * The packet is returned (relatively) intact; if * ip_mforward() returns a non-zero value, the packet * must be discarded, else it may be accepted below. * * (The IP ident field is put in the same byte order * as expected when ip_mforward() is called from * ip_output().) */ ip->ip_id = htons(ip->ip_id); if (ip_mforward(ip, m->m_pkthdr.rcvif, m, 0) != 0) { ipstat.ips_cantforward++; m_freem(m); return; } ip->ip_id = ntohs(ip->ip_id); /* * The process-level routing demon needs to receive * all multicast IGMP packets, whether or not this * host belongs to their destination groups. */ if (ip->ip_p == IPPROTO_IGMP) goto ours; ipstat.ips_forward++; } /* * See if we belong to the destination multicast group on the * arrival interface. */ IN_LOOKUP_MULTI(ip->ip_dst, m->m_pkthdr.rcvif, inm); if (inm == NULL) { ipstat.ips_notmember++; m_freem(m); return; } goto ours; } if (ip->ip_dst.s_addr == (u_long)INADDR_BROADCAST) goto ours; if (ip->ip_dst.s_addr == INADDR_ANY) goto ours; /* * Not for us; forward if possible and desirable. */ if (ipforwarding == 0) { ipstat.ips_cantforward++; m_freem(m); } else ip_forward(m, 0); return; ours: /* * If offset or IP_MF are set, must reassemble. * Otherwise, nothing need be done. * (We could look in the reassembly queue to see * if the packet was previously fragmented, * but it's not worth the time; just let them time out.) */ if (ip->ip_off & (IP_MF | IP_OFFMASK | IP_RF)) { if (m->m_flags & M_EXT) { /* XXX */ if ((m = m_pullup(m, sizeof (struct ip))) == 0) { ipstat.ips_toosmall++; #ifdef IPDIVERT frag_divert_port = 0; ip_divert_cookie = 0; #endif return; } ip = mtod(m, struct ip *); } sum = IPREASS_HASH(ip->ip_src.s_addr, ip->ip_id); /* * Look for queue of fragments * of this datagram. */ for (fp = ipq[sum].next; fp != &ipq[sum]; fp = fp->next) if (ip->ip_id == fp->ipq_id && ip->ip_src.s_addr == fp->ipq_src.s_addr && ip->ip_dst.s_addr == fp->ipq_dst.s_addr && ip->ip_p == fp->ipq_p) goto found; fp = 0; /* check if there's a place for the new queue */ if (nipq > maxnipq) { /* * drop something from the tail of the current queue * before proceeding further */ if (ipq[sum].prev == &ipq[sum]) { /* gak */ for (i = 0; i < IPREASS_NHASH; i++) { if (ipq[i].prev != &ipq[i]) { ip_freef(ipq[i].prev); break; } } } else ip_freef(ipq[sum].prev); } found: /* * Adjust ip_len to not reflect header, * set ip_mff if more fragments are expected, * convert offset of this to bytes. */ ip->ip_len -= hlen; ((struct ipasfrag *)ip)->ipf_mff &= ~1; if (ip->ip_off & IP_MF) ((struct ipasfrag *)ip)->ipf_mff |= 1; ip->ip_off <<= 3; /* * If datagram marked as having more fragments * or if this is not the first fragment, * attempt reassembly; if it succeeds, proceed. */ if (((struct ipasfrag *)ip)->ipf_mff & 1 || ip->ip_off) { ipstat.ips_fragments++; ip = ip_reass((struct ipasfrag *)ip, fp, &ipq[sum]); - if (ip == 0) + if (ip == 0) { +#ifdef IPFIREWALL_FORWARD + ip_fw_fwd_addr = NULL; +#endif return; + } /* Get the length of the reassembled packets header */ hlen = IP_VHL_HL(ip->ip_vhl) << 2; ipstat.ips_reassembled++; m = dtom(ip); #ifdef IPDIVERT if (frag_divert_port) { ip->ip_len += hlen; HTONS(ip->ip_len); HTONS(ip->ip_off); HTONS(ip->ip_id); ip->ip_sum = 0; ip->ip_sum = in_cksum_hdr(ip); NTOHS(ip->ip_id); NTOHS(ip->ip_off); NTOHS(ip->ip_len); ip->ip_len -= hlen; } #endif } else if (fp) ip_freef(fp); } else ip->ip_len -= hlen; #ifdef IPDIVERT /* * Divert reassembled packets to the divert protocol if required */ if (frag_divert_port) { ipstat.ips_delivered++; ip_divert_port = frag_divert_port; frag_divert_port = 0; (*inetsw[ip_protox[IPPROTO_DIVERT]].pr_input)(m, hlen); return; } /* Don't let packets divert themselves */ if (ip->ip_p == IPPROTO_DIVERT) { ipstat.ips_noproto++; goto bad; } #endif /* * Switch out to protocol's input routine. */ ipstat.ips_delivered++; (*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen); +#ifdef IPFIREWALL_FORWARD + ip_fw_fwd_addr = NULL; /* tcp needed it */ +#endif return; bad: +#ifdef IPFIREWALL_FORWARD + ip_fw_fwd_addr = NULL; +#endif m_freem(m); } /* * IP software interrupt routine - to go away sometime soon */ static void ipintr(void) { int s; struct mbuf *m; while(1) { s = splimp(); IF_DEQUEUE(&ipintrq, m); splx(s); if (m == 0) return; ip_input(m); } } NETISR_SET(NETISR_IP, ipintr); /* * Take incoming datagram fragment and try to * reassemble it into whole datagram. If a chain for * reassembly of this datagram already exists, then it * is given as fp; otherwise have to make a chain. */ static struct ip * ip_reass(ip, fp, where) register struct ipasfrag *ip; register struct ipq *fp; struct ipq *where; { register struct mbuf *m = dtom(ip); register struct ipasfrag *q; struct mbuf *t; int hlen = ip->ip_hl << 2; int i, next; /* * Presence of header sizes in mbufs * would confuse code below. */ m->m_data += hlen; m->m_len -= hlen; /* * If first fragment to arrive, create a reassembly queue. */ if (fp == 0) { if ((t = m_get(M_DONTWAIT, MT_FTABLE)) == NULL) goto dropfrag; fp = mtod(t, struct ipq *); insque(fp, where); nipq++; fp->ipq_ttl = IPFRAGTTL; fp->ipq_p = ip->ip_p; fp->ipq_id = ip->ip_id; fp->ipq_next = fp->ipq_prev = (struct ipasfrag *)fp; fp->ipq_src = ((struct ip *)ip)->ip_src; fp->ipq_dst = ((struct ip *)ip)->ip_dst; #ifdef IPDIVERT fp->ipq_divert = 0; fp->ipq_div_cookie = 0; #endif q = (struct ipasfrag *)fp; goto insert; } /* * Find a segment which begins after this one does. */ for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = q->ipf_next) if (q->ip_off > ip->ip_off) break; /* * If there is a preceding segment, it may provide some of * our data already. If so, drop the data from the incoming * segment. If it provides all of our data, drop us. */ if (q->ipf_prev != (struct ipasfrag *)fp) { i = q->ipf_prev->ip_off + q->ipf_prev->ip_len - ip->ip_off; if (i > 0) { if (i >= ip->ip_len) goto dropfrag; m_adj(dtom(ip), i); ip->ip_off += i; ip->ip_len -= i; } } /* * While we overlap succeeding segments trim them or, * if they are completely covered, dequeue them. */ while (q != (struct ipasfrag *)fp && ip->ip_off + ip->ip_len > q->ip_off) { struct mbuf *m0; i = (ip->ip_off + ip->ip_len) - q->ip_off; if (i < q->ip_len) { q->ip_len -= i; q->ip_off += i; m_adj(dtom(q), i); break; } m0 = dtom(q); q = q->ipf_next; ip_deq(q->ipf_prev); m_freem(m0); } insert: #ifdef IPDIVERT /* * Any fragment diverting causes the whole packet to divert */ if (frag_divert_port != 0) { fp->ipq_divert = frag_divert_port; fp->ipq_div_cookie = ip_divert_cookie; } frag_divert_port = 0; ip_divert_cookie = 0; #endif /* * Stick new segment in its place; * check for complete reassembly. */ ip_enq(ip, q->ipf_prev); next = 0; for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = q->ipf_next) { if (q->ip_off != next) return (0); next += q->ip_len; } if (q->ipf_prev->ipf_mff & 1) return (0); /* * Reassembly is complete. Make sure the packet is a sane size. */ if (next + (IP_VHL_HL(((struct ip *)fp->ipq_next)->ip_vhl) << 2) > IP_MAXPACKET) { ipstat.ips_toolong++; ip_freef(fp); return (0); } /* * Concatenate fragments. */ q = fp->ipq_next; m = dtom(q); t = m->m_next; m->m_next = 0; m_cat(m, t); q = q->ipf_next; while (q != (struct ipasfrag *)fp) { t = dtom(q); q = q->ipf_next; m_cat(m, t); } #ifdef IPDIVERT /* * extract divert port for packet, if any */ frag_divert_port = fp->ipq_divert; ip_divert_cookie = fp->ipq_div_cookie; #endif /* * Create header for new ip packet by * modifying header of first packet; * dequeue and discard fragment reassembly header. * Make header visible. */ ip = fp->ipq_next; ip->ip_len = next; ip->ipf_mff &= ~1; ((struct ip *)ip)->ip_src = fp->ipq_src; ((struct ip *)ip)->ip_dst = fp->ipq_dst; remque(fp); nipq--; (void) m_free(dtom(fp)); m = dtom(ip); m->m_len += (ip->ip_hl << 2); m->m_data -= (ip->ip_hl << 2); /* some debugging cruft by sklower, below, will go away soon */ if (m->m_flags & M_PKTHDR) { /* XXX this should be done elsewhere */ register int plen = 0; for (t = m; m; m = m->m_next) plen += m->m_len; t->m_pkthdr.len = plen; } return ((struct ip *)ip); dropfrag: ipstat.ips_fragdropped++; m_freem(m); return (0); } /* * Free a fragment reassembly header and all * associated datagrams. */ static void ip_freef(fp) struct ipq *fp; { register struct ipasfrag *q, *p; for (q = fp->ipq_next; q != (struct ipasfrag *)fp; q = p) { p = q->ipf_next; ip_deq(q); m_freem(dtom(q)); } remque(fp); (void) m_free(dtom(fp)); nipq--; } /* * Put an ip fragment on a reassembly chain. * Like insque, but pointers in middle of structure. */ static void ip_enq(p, prev) register struct ipasfrag *p, *prev; { p->ipf_prev = prev; p->ipf_next = prev->ipf_next; prev->ipf_next->ipf_prev = p; prev->ipf_next = p; } /* * To ip_enq as remque is to insque. */ static void ip_deq(p) register struct ipasfrag *p; { p->ipf_prev->ipf_next = p->ipf_next; p->ipf_next->ipf_prev = p->ipf_prev; } /* * IP timer processing; * if a timer expires on a reassembly * queue, discard it. */ void ip_slowtimo() { register struct ipq *fp; int s = splnet(); int i; for (i = 0; i < IPREASS_NHASH; i++) { fp = ipq[i].next; if (fp == 0) continue; while (fp != &ipq[i]) { --fp->ipq_ttl; fp = fp->next; if (fp->prev->ipq_ttl == 0) { ipstat.ips_fragtimeout++; ip_freef(fp->prev); } } } ipflow_slowtimo(); splx(s); } /* * Drain off all datagram fragments. */ void ip_drain() { int i; for (i = 0; i < IPREASS_NHASH; i++) { while (ipq[i].next != &ipq[i]) { ipstat.ips_fragdropped++; ip_freef(ipq[i].next); } } in_rtqdrain(); } /* * Do option processing on a datagram, * possibly discarding it if bad options are encountered, * or forwarding it if source-routed. * Returns 1 if packet has been forwarded/freed, * 0 if the packet should be processed further. */ static int ip_dooptions(m) struct mbuf *m; { register struct ip *ip = mtod(m, struct ip *); register u_char *cp; register struct ip_timestamp *ipt; register struct in_ifaddr *ia; int opt, optlen, cnt, off, code, type = ICMP_PARAMPROB, forward = 0; struct in_addr *sin, dst; n_time ntime; dst = ip->ip_dst; cp = (u_char *)(ip + 1); cnt = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > cnt) { code = &cp[IPOPT_OLEN] - (u_char *)ip; goto bad; } } switch (opt) { default: break; /* * Source routing with record. * Find interface with current destination address. * If none on this machine then drop if strictly routed, * or do nothing if loosely routed. * Record interface address and bring up next address * component. If strictly routed make sure next * address is on directly accessible net. */ case IPOPT_LSRR: case IPOPT_SSRR: if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { code = &cp[IPOPT_OFFSET] - (u_char *)ip; goto bad; } ipaddr.sin_addr = ip->ip_dst; ia = (struct in_ifaddr *) ifa_ifwithaddr((struct sockaddr *)&ipaddr); if (ia == 0) { if (opt == IPOPT_SSRR) { type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } if (!ip_dosourceroute) goto nosourcerouting; /* * Loose routing, and not at next destination * yet; nothing to do except forward. */ break; } off--; /* 0 origin */ if (off > optlen - sizeof(struct in_addr)) { /* * End of source route. Should be for us. */ if (!ip_acceptsourceroute) goto nosourcerouting; save_rte(cp, ip->ip_src); break; } if (!ip_dosourceroute) { char buf[4*sizeof "123"]; nosourcerouting: strcpy(buf, inet_ntoa(ip->ip_dst)); log(LOG_WARNING, "attempted source route from %s to %s\n", inet_ntoa(ip->ip_src), buf); type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } /* * locate outgoing interface */ (void)memcpy(&ipaddr.sin_addr, cp + off, sizeof(ipaddr.sin_addr)); if (opt == IPOPT_SSRR) { #define INA struct in_ifaddr * #define SA struct sockaddr * if ((ia = (INA)ifa_ifwithdstaddr((SA)&ipaddr)) == 0) ia = (INA)ifa_ifwithnet((SA)&ipaddr); } else ia = ip_rtaddr(ipaddr.sin_addr); if (ia == 0) { type = ICMP_UNREACH; code = ICMP_UNREACH_SRCFAIL; goto bad; } ip->ip_dst = ipaddr.sin_addr; (void)memcpy(cp + off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); cp[IPOPT_OFFSET] += sizeof(struct in_addr); /* * Let ip_intr's mcast routing check handle mcast pkts */ forward = !IN_MULTICAST(ntohl(ip->ip_dst.s_addr)); break; case IPOPT_RR: if ((off = cp[IPOPT_OFFSET]) < IPOPT_MINOFF) { code = &cp[IPOPT_OFFSET] - (u_char *)ip; goto bad; } /* * If no space remains, ignore. */ off--; /* 0 origin */ if (off > optlen - sizeof(struct in_addr)) break; (void)memcpy(&ipaddr.sin_addr, &ip->ip_dst, sizeof(ipaddr.sin_addr)); /* * locate outgoing interface; if we're the destination, * use the incoming interface (should be same). */ if ((ia = (INA)ifa_ifwithaddr((SA)&ipaddr)) == 0 && (ia = ip_rtaddr(ipaddr.sin_addr)) == 0) { type = ICMP_UNREACH; code = ICMP_UNREACH_HOST; goto bad; } (void)memcpy(cp + off, &(IA_SIN(ia)->sin_addr), sizeof(struct in_addr)); cp[IPOPT_OFFSET] += sizeof(struct in_addr); break; case IPOPT_TS: code = cp - (u_char *)ip; ipt = (struct ip_timestamp *)cp; if (ipt->ipt_len < 5) goto bad; if (ipt->ipt_ptr > ipt->ipt_len - sizeof (long)) { if (++ipt->ipt_oflw == 0) goto bad; break; } sin = (struct in_addr *)(cp + ipt->ipt_ptr - 1); switch (ipt->ipt_flg) { case IPOPT_TS_TSONLY: break; case IPOPT_TS_TSANDADDR: if (ipt->ipt_ptr - 1 + sizeof(n_time) + sizeof(struct in_addr) > ipt->ipt_len) goto bad; ipaddr.sin_addr = dst; ia = (INA)ifaof_ifpforaddr((SA)&ipaddr, m->m_pkthdr.rcvif); if (ia == 0) continue; (void)memcpy(sin, &IA_SIN(ia)->sin_addr, sizeof(struct in_addr)); ipt->ipt_ptr += sizeof(struct in_addr); break; case IPOPT_TS_PRESPEC: if (ipt->ipt_ptr - 1 + sizeof(n_time) + sizeof(struct in_addr) > ipt->ipt_len) goto bad; (void)memcpy(&ipaddr.sin_addr, sin, sizeof(struct in_addr)); if (ifa_ifwithaddr((SA)&ipaddr) == 0) continue; ipt->ipt_ptr += sizeof(struct in_addr); break; default: goto bad; } ntime = iptime(); (void)memcpy(cp + ipt->ipt_ptr - 1, &ntime, sizeof(n_time)); ipt->ipt_ptr += sizeof(n_time); } } if (forward && ipforwarding) { ip_forward(m, 1); return (1); } return (0); bad: ip->ip_len -= IP_VHL_HL(ip->ip_vhl) << 2; /* XXX icmp_error adds in hdr length */ icmp_error(m, type, code, 0, 0); ipstat.ips_badoptions++; return (1); } /* * Given address of next destination (final or next hop), * return internet address info of interface to be used to get there. */ static struct in_ifaddr * ip_rtaddr(dst) struct in_addr dst; { register struct sockaddr_in *sin; sin = (struct sockaddr_in *) &ipforward_rt.ro_dst; if (ipforward_rt.ro_rt == 0 || dst.s_addr != sin->sin_addr.s_addr) { if (ipforward_rt.ro_rt) { RTFREE(ipforward_rt.ro_rt); ipforward_rt.ro_rt = 0; } sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = dst; rtalloc_ign(&ipforward_rt, RTF_PRCLONING); } if (ipforward_rt.ro_rt == 0) return ((struct in_ifaddr *)0); return ((struct in_ifaddr *) ipforward_rt.ro_rt->rt_ifa); } /* * Save incoming source route for use in replies, * to be picked up later by ip_srcroute if the receiver is interested. */ void save_rte(option, dst) u_char *option; struct in_addr dst; { unsigned olen; olen = option[IPOPT_OLEN]; #ifdef DIAGNOSTIC if (ipprintfs) printf("save_rte: olen %d\n", olen); #endif if (olen > sizeof(ip_srcrt) - (1 + sizeof(dst))) return; bcopy(option, ip_srcrt.srcopt, olen); ip_nhops = (olen - IPOPT_OFFSET - 1) / sizeof(struct in_addr); ip_srcrt.dst = dst; } /* * Retrieve incoming source route for use in replies, * in the same form used by setsockopt. * The first hop is placed before the options, will be removed later. */ struct mbuf * ip_srcroute() { register struct in_addr *p, *q; register struct mbuf *m; if (ip_nhops == 0) return ((struct mbuf *)0); m = m_get(M_DONTWAIT, MT_SOOPTS); if (m == 0) return ((struct mbuf *)0); #define OPTSIZ (sizeof(ip_srcrt.nop) + sizeof(ip_srcrt.srcopt)) /* length is (nhops+1)*sizeof(addr) + sizeof(nop + srcrt header) */ m->m_len = ip_nhops * sizeof(struct in_addr) + sizeof(struct in_addr) + OPTSIZ; #ifdef DIAGNOSTIC if (ipprintfs) printf("ip_srcroute: nhops %d mlen %d", ip_nhops, m->m_len); #endif /* * First save first hop for return route */ p = &ip_srcrt.route[ip_nhops - 1]; *(mtod(m, struct in_addr *)) = *p--; #ifdef DIAGNOSTIC if (ipprintfs) printf(" hops %lx", ntohl(mtod(m, struct in_addr *)->s_addr)); #endif /* * Copy option fields and padding (nop) to mbuf. */ ip_srcrt.nop = IPOPT_NOP; ip_srcrt.srcopt[IPOPT_OFFSET] = IPOPT_MINOFF; (void)memcpy(mtod(m, caddr_t) + sizeof(struct in_addr), &ip_srcrt.nop, OPTSIZ); q = (struct in_addr *)(mtod(m, caddr_t) + sizeof(struct in_addr) + OPTSIZ); #undef OPTSIZ /* * Record return path as an IP source route, * reversing the path (pointers are now aligned). */ while (p >= ip_srcrt.route) { #ifdef DIAGNOSTIC if (ipprintfs) printf(" %lx", ntohl(q->s_addr)); #endif *q++ = *p--; } /* * Last hop goes to final destination. */ *q = ip_srcrt.dst; #ifdef DIAGNOSTIC if (ipprintfs) printf(" %lx\n", ntohl(q->s_addr)); #endif return (m); } /* * Strip out IP options, at higher * level protocol in the kernel. * Second argument is buffer to which options * will be moved, and return value is their length. * XXX should be deleted; last arg currently ignored. */ void ip_stripoptions(m, mopt) register struct mbuf *m; struct mbuf *mopt; { register int i; struct ip *ip = mtod(m, struct ip *); register caddr_t opts; int olen; olen = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); opts = (caddr_t)(ip + 1); i = m->m_len - (sizeof (struct ip) + olen); bcopy(opts + olen, opts, (unsigned)i); m->m_len -= olen; if (m->m_flags & M_PKTHDR) m->m_pkthdr.len -= olen; ip->ip_vhl = IP_MAKE_VHL(IPVERSION, sizeof(struct ip) >> 2); } u_char inetctlerrmap[PRC_NCMDS] = { 0, 0, 0, 0, 0, EMSGSIZE, EHOSTDOWN, EHOSTUNREACH, EHOSTUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, EMSGSIZE, EHOSTUNREACH, 0, 0, 0, 0, 0, 0, ENOPROTOOPT }; /* * Forward a packet. If some error occurs return the sender * an icmp packet. Note we can't always generate a meaningful * icmp message because icmp doesn't have a large enough repertoire * of codes and types. * * If not forwarding, just drop the packet. This could be confusing * if ipforwarding was zero but some routing protocol was advancing * us as a gateway to somewhere. However, we must let the routing * protocol deal with that. * * The srcrt parameter indicates whether the packet is being forwarded * via a source route. */ static void ip_forward(m, srcrt) struct mbuf *m; int srcrt; { register struct ip *ip = mtod(m, struct ip *); register struct sockaddr_in *sin; register struct rtentry *rt; int error, type = 0, code = 0; struct mbuf *mcopy; n_long dest; struct ifnet *destifp; dest = 0; #ifdef DIAGNOSTIC if (ipprintfs) printf("forward: src %lx dst %lx ttl %x\n", ip->ip_src.s_addr, ip->ip_dst.s_addr, ip->ip_ttl); #endif if (m->m_flags & M_BCAST || in_canforward(ip->ip_dst) == 0) { ipstat.ips_cantforward++; m_freem(m); return; } HTONS(ip->ip_id); if (ip->ip_ttl <= IPTTLDEC) { icmp_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, dest, 0); return; } ip->ip_ttl -= IPTTLDEC; sin = (struct sockaddr_in *)&ipforward_rt.ro_dst; if ((rt = ipforward_rt.ro_rt) == 0 || ip->ip_dst.s_addr != sin->sin_addr.s_addr) { if (ipforward_rt.ro_rt) { RTFREE(ipforward_rt.ro_rt); ipforward_rt.ro_rt = 0; } sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = ip->ip_dst; rtalloc_ign(&ipforward_rt, RTF_PRCLONING); if (ipforward_rt.ro_rt == 0) { icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, dest, 0); return; } rt = ipforward_rt.ro_rt; } /* * Save at most 64 bytes of the packet in case * we need to generate an ICMP message to the src. */ mcopy = m_copy(m, 0, imin((int)ip->ip_len, 64)); /* * If forwarding packet using same interface that it came in on, * perhaps should send a redirect to sender to shortcut a hop. * Only send redirect if source is sending directly to us, * and if packet was not source routed (or has any options). * Also, don't send redirect if forwarding using a default route * or a route modified by a redirect. */ #define satosin(sa) ((struct sockaddr_in *)(sa)) if (rt->rt_ifp == m->m_pkthdr.rcvif && (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0 && satosin(rt_key(rt))->sin_addr.s_addr != 0 && ipsendredirects && !srcrt) { #define RTA(rt) ((struct in_ifaddr *)(rt->rt_ifa)) u_long src = ntohl(ip->ip_src.s_addr); if (RTA(rt) && (src & RTA(rt)->ia_subnetmask) == RTA(rt)->ia_subnet) { if (rt->rt_flags & RTF_GATEWAY) dest = satosin(rt->rt_gateway)->sin_addr.s_addr; else dest = ip->ip_dst.s_addr; /* Router requirements says to only send host redirects */ type = ICMP_REDIRECT; code = ICMP_REDIRECT_HOST; #ifdef DIAGNOSTIC if (ipprintfs) printf("redirect (%d) to %lx\n", code, (u_long)dest); #endif } } error = ip_output(m, (struct mbuf *)0, &ipforward_rt, IP_FORWARDING, 0); if (error) ipstat.ips_cantforward++; else { ipstat.ips_forward++; if (type) ipstat.ips_redirectsent++; else { if (mcopy) { ipflow_create(&ipforward_rt, mcopy); m_freem(mcopy); } return; } } if (mcopy == NULL) return; destifp = NULL; switch (error) { case 0: /* forwarded, but need redirect */ /* type, code set above */ break; case ENETUNREACH: /* shouldn't happen, checked above */ case EHOSTUNREACH: case ENETDOWN: case EHOSTDOWN: default: type = ICMP_UNREACH; code = ICMP_UNREACH_HOST; break; case EMSGSIZE: type = ICMP_UNREACH; code = ICMP_UNREACH_NEEDFRAG; if (ipforward_rt.ro_rt) destifp = ipforward_rt.ro_rt->rt_ifp; ipstat.ips_cantfrag++; break; case ENOBUFS: type = ICMP_SOURCEQUENCH; code = 0; break; } icmp_error(mcopy, type, code, dest, destifp); } void ip_savecontrol(inp, mp, ip, m) register struct inpcb *inp; register struct mbuf **mp; register struct ip *ip; register struct mbuf *m; { if (inp->inp_socket->so_options & SO_TIMESTAMP) { struct timeval tv; microtime(&tv); *mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), SCM_TIMESTAMP, SOL_SOCKET); if (*mp) mp = &(*mp)->m_next; } if (inp->inp_flags & INP_RECVDSTADDR) { *mp = sbcreatecontrol((caddr_t) &ip->ip_dst, sizeof(struct in_addr), IP_RECVDSTADDR, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } #ifdef notyet /* XXX * Moving these out of udp_input() made them even more broken * than they already were. */ /* options were tossed already */ if (inp->inp_flags & INP_RECVOPTS) { *mp = sbcreatecontrol((caddr_t) opts_deleted_above, sizeof(struct in_addr), IP_RECVOPTS, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } /* ip_srcroute doesn't do what we want here, need to fix */ if (inp->inp_flags & INP_RECVRETOPTS) { *mp = sbcreatecontrol((caddr_t) ip_srcroute(), sizeof(struct in_addr), IP_RECVRETOPTS, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } #endif if (inp->inp_flags & INP_RECVIF) { struct ifnet *ifp; struct sdlbuf { struct sockaddr_dl sdl; u_char pad[32]; } sdlbuf; struct sockaddr_dl *sdp; struct sockaddr_dl *sdl2 = &sdlbuf.sdl; if (((ifp = m->m_pkthdr.rcvif)) && ( ifp->if_index && (ifp->if_index <= if_index))) { sdp = (struct sockaddr_dl *)(ifnet_addrs [ifp->if_index - 1]->ifa_addr); /* * Change our mind and don't try copy. */ if ((sdp->sdl_family != AF_LINK) || (sdp->sdl_len > sizeof(sdlbuf))) { goto makedummy; } bcopy(sdp, sdl2, sdp->sdl_len); } else { makedummy: sdl2->sdl_len = offsetof(struct sockaddr_dl, sdl_data[0]); sdl2->sdl_family = AF_LINK; sdl2->sdl_index = 0; sdl2->sdl_nlen = sdl2->sdl_alen = sdl2->sdl_slen = 0; } *mp = sbcreatecontrol((caddr_t) sdl2, sdl2->sdl_len, IP_RECVIF, IPPROTO_IP); if (*mp) mp = &(*mp)->m_next; } } int ip_rsvp_init(struct socket *so) { if (so->so_type != SOCK_RAW || so->so_proto->pr_protocol != IPPROTO_RSVP) return EOPNOTSUPP; if (ip_rsvpd != NULL) return EADDRINUSE; ip_rsvpd = so; /* * This may seem silly, but we need to be sure we don't over-increment * the RSVP counter, in case something slips up. */ if (!ip_rsvp_on) { ip_rsvp_on = 1; rsvp_on++; } return 0; } int ip_rsvp_done(void) { ip_rsvpd = NULL; /* * This may seem silly, but we need to be sure we don't over-decrement * the RSVP counter, in case something slips up. */ if (ip_rsvp_on) { ip_rsvp_on = 0; rsvp_on--; } return 0; } Index: head/sys/netinet/ip_output.c =================================================================== --- head/sys/netinet/ip_output.c (revision 37408) +++ head/sys/netinet/ip_output.c (revision 37409) @@ -1,1339 +1,1464 @@ /* * Copyright (c) 1982, 1986, 1988, 1990, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 - * $Id: ip_output.c,v 1.74 1998/06/15 00:35:47 julian Exp $ + * $Id: ip_output.c,v 1.75 1998/06/21 14:53:32 bde Exp $ */ #define _IP_VHL #include "opt_ipdivert.h" #include "opt_ipfilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef vax #include #endif #include static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "internet multicast options"); #if !defined(COMPAT_IPFW) || COMPAT_IPFW == 1 #undef COMPAT_IPFW #define COMPAT_IPFW 1 #else #undef COMPAT_IPFW #endif +#ifdef IPFIREWALL_FORWARD_DEBUG +#define print_ip(a) printf("%ld.%ld.%ld.%ld",(ntohl(a.s_addr)>>24)&0xFF,\ + (ntohl(a.s_addr)>>16)&0xFF,\ + (ntohl(a.s_addr)>>8)&0xFF,\ + (ntohl(a.s_addr))&0xFF); +#endif + u_short ip_id; static struct mbuf *ip_insertoptions __P((struct mbuf *, struct mbuf *, int *)); static void ip_mloopback __P((struct ifnet *, struct mbuf *, struct sockaddr_in *, int)); static int ip_getmoptions __P((int, struct ip_moptions *, struct mbuf **)); static int ip_pcbopts __P((struct mbuf **, struct mbuf *)); static int ip_setmoptions __P((int, struct ip_moptions **, struct mbuf *)); #if defined(IPFILTER_LKM) || defined(IPFILTER) int ip_optcopy __P((struct ip *, struct ip *)); extern int (*fr_checkp) __P((struct ip *, int, struct ifnet *, int, struct mbuf **)); #else static int ip_optcopy __P((struct ip *, struct ip *)); #endif extern struct protosw inetsw[]; /* * IP output. The packet in mbuf chain m contains a skeletal IP * header (with len, off, ttl, proto, tos, src, dst). * The mbuf chain containing the packet will be freed. * The mbuf opt, if present, will not be freed. */ int ip_output(m0, opt, ro, flags, imo) struct mbuf *m0; struct mbuf *opt; struct route *ro; int flags; struct ip_moptions *imo; { struct ip *ip, *mhip; struct ifnet *ifp; struct mbuf *m = m0; int hlen = sizeof (struct ip); int len, off, error = 0; struct sockaddr_in *dst; struct in_ifaddr *ia; int isbroadcast; +#ifdef IPFIREWALL_FORWARD + int fwd_rewrite_src = 0; +#endif #ifdef DIAGNOSTIC if ((m->m_flags & M_PKTHDR) == 0) panic("ip_output no HDR"); if (!ro) panic("ip_output no route, proto = %d", mtod(m, struct ip *)->ip_p); #endif if (opt) { m = ip_insertoptions(m, opt, &len); hlen = len; } ip = mtod(m, struct ip *); /* * Fill in IP header. */ if ((flags & (IP_FORWARDING|IP_RAWOUTPUT)) == 0) { ip->ip_vhl = IP_MAKE_VHL(IPVERSION, hlen >> 2); ip->ip_off &= IP_DF; ip->ip_id = htons(ip_id++); ipstat.ips_localout++; } else { hlen = IP_VHL_HL(ip->ip_vhl) << 2; } dst = (struct sockaddr_in *)&ro->ro_dst; /* * If there is a cached route, * check that it is to the same destination * and is still up. If not, free it and try again. */ if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 || dst->sin_addr.s_addr != ip->ip_dst.s_addr)) { RTFREE(ro->ro_rt); ro->ro_rt = (struct rtentry *)0; } if (ro->ro_rt == 0) { dst->sin_family = AF_INET; dst->sin_len = sizeof(*dst); dst->sin_addr = ip->ip_dst; } /* * If routing to interface only, * short circuit routing lookup. */ #define ifatoia(ifa) ((struct in_ifaddr *)(ifa)) #define sintosa(sin) ((struct sockaddr *)(sin)) if (flags & IP_ROUTETOIF) { if ((ia = ifatoia(ifa_ifwithdstaddr(sintosa(dst)))) == 0 && (ia = ifatoia(ifa_ifwithnet(sintosa(dst)))) == 0) { ipstat.ips_noroute++; error = ENETUNREACH; goto bad; } ifp = ia->ia_ifp; ip->ip_ttl = 1; isbroadcast = in_broadcast(dst->sin_addr, ifp); } else { /* * If this is the case, we probably don't want to allocate * a protocol-cloned route since we didn't get one from the * ULP. This lets TCP do its thing, while not burdening * forwarding or ICMP with the overhead of cloning a route. * Of course, we still want to do any cloning requested by * the link layer, as this is probably required in all cases * for correct operation (as it is for ARP). */ if (ro->ro_rt == 0) rtalloc_ign(ro, RTF_PRCLONING); if (ro->ro_rt == 0) { ipstat.ips_noroute++; error = EHOSTUNREACH; goto bad; } ia = ifatoia(ro->ro_rt->rt_ifa); ifp = ro->ro_rt->rt_ifp; ro->ro_rt->rt_use++; if (ro->ro_rt->rt_flags & RTF_GATEWAY) dst = (struct sockaddr_in *)ro->ro_rt->rt_gateway; if (ro->ro_rt->rt_flags & RTF_HOST) isbroadcast = (ro->ro_rt->rt_flags & RTF_BROADCAST); else isbroadcast = in_broadcast(dst->sin_addr, ifp); } if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { struct in_multi *inm; m->m_flags |= M_MCAST; /* * IP destination address is multicast. Make sure "dst" * still points to the address in "ro". (It may have been * changed to point to a gateway address, above.) */ dst = (struct sockaddr_in *)&ro->ro_dst; /* * See if the caller provided any multicast options */ if (imo != NULL) { ip->ip_ttl = imo->imo_multicast_ttl; if (imo->imo_multicast_ifp != NULL) ifp = imo->imo_multicast_ifp; if (imo->imo_multicast_vif != -1) ip->ip_src.s_addr = ip_mcast_src(imo->imo_multicast_vif); } else ip->ip_ttl = IP_DEFAULT_MULTICAST_TTL; /* * Confirm that the outgoing interface supports multicast. */ if ((imo == NULL) || (imo->imo_multicast_vif == -1)) { if ((ifp->if_flags & IFF_MULTICAST) == 0) { ipstat.ips_noroute++; error = ENETUNREACH; goto bad; } } /* * If source address not specified yet, use address * of outgoing interface. */ if (ip->ip_src.s_addr == INADDR_ANY) { register struct in_ifaddr *ia1; for (ia1 = in_ifaddrhead.tqh_first; ia1; ia1 = ia1->ia_link.tqe_next) if (ia1->ia_ifp == ifp) { ip->ip_src = IA_SIN(ia1)->sin_addr; break; } } IN_LOOKUP_MULTI(ip->ip_dst, ifp, inm); if (inm != NULL && (imo == NULL || imo->imo_multicast_loop)) { /* * If we belong to the destination multicast group * on the outgoing interface, and the caller did not * forbid loopback, loop back a copy. */ ip_mloopback(ifp, m, dst, hlen); } else { /* * If we are acting as a multicast router, perform * multicast forwarding as if the packet had just * arrived on the interface to which we are about * to send. The multicast forwarding function * recursively calls this function, using the * IP_FORWARDING flag to prevent infinite recursion. * * Multicasts that are looped back by ip_mloopback(), * above, will be forwarded by the ip_input() routine, * if necessary. */ if (ip_mrouter && (flags & IP_FORWARDING) == 0) { /* * Check if rsvp daemon is running. If not, don't * set ip_moptions. This ensures that the packet * is multicast and not just sent down one link * as prescribed by rsvpd. */ if (!rsvp_on) imo = NULL; if (ip_mforward(ip, ifp, m, imo) != 0) { m_freem(m); goto done; } } } /* * Multicasts with a time-to-live of zero may be looped- * back, above, but must not be transmitted on a network. * Also, multicasts addressed to the loopback interface * are not sent -- the above call to ip_mloopback() will * loop back a copy if this host actually belongs to the * destination group on the loopback interface. */ if (ip->ip_ttl == 0 || ifp->if_flags & IFF_LOOPBACK) { m_freem(m); goto done; } goto sendit; } #ifndef notdef /* * If source address not specified yet, use address * of outgoing interface. */ - if (ip->ip_src.s_addr == INADDR_ANY) + if (ip->ip_src.s_addr == INADDR_ANY) { ip->ip_src = IA_SIN(ia)->sin_addr; -#endif +#ifdef IPFIREWALL_FORWARD + /* Keep note that we did this - if the firewall changes + * the next-hop, our interface may change, changing the + * default source IP. It's a shame so much effort happens + * twice. Oh well. + */ + fwd_rewrite_src++; +#endif /* IPFIREWALL_FORWARD */ + } +#endif /* notdef */ /* * Verify that we have any chance at all of being able to queue * the packet or packet fragments */ if ((ifp->if_snd.ifq_len + ip->ip_len / ifp->if_mtu + 1) >= ifp->if_snd.ifq_maxlen) { error = ENOBUFS; goto bad; } /* * Look for broadcast address and * and verify user is allowed to send * such a packet. */ if (isbroadcast) { if ((ifp->if_flags & IFF_BROADCAST) == 0) { error = EADDRNOTAVAIL; goto bad; } if ((flags & IP_ALLOWBROADCAST) == 0) { error = EACCES; goto bad; } /* don't allow broadcast messages to be fragmented */ if ((u_short)ip->ip_len > ifp->if_mtu) { error = EMSGSIZE; goto bad; } m->m_flags |= M_BCAST; } else { m->m_flags &= ~M_BCAST; } sendit: /* * IpHack's section. * - Xlate: translate packet's addr/port (NAT). * - Firewall: deny/allow/etc. * - Wrap: fake packet's addr/port * - Encapsulate: put it in another IP and send out. */ #if defined(IPFILTER) || defined(IPFILTER_LKM) if (fr_checkp) { struct mbuf *m1 = m; if ((error = (*fr_checkp)(ip, hlen, ifp, 1, &m1)) || !m1) goto done; ip = mtod(m = m1, struct ip *); } #endif #ifdef COMPAT_IPFW if (ip_nat_ptr && !(*ip_nat_ptr)(&ip, &m, ifp, IP_NAT_OUT)) { error = EACCES; goto done; } /* * Check with the firewall... */ if (ip_fw_chk_ptr) { +#ifdef IPFIREWALL_FORWARD + struct sockaddr_in *old; + old = dst; +#endif #ifdef IPDIVERT ip_divert_port = (*ip_fw_chk_ptr)(&ip, - hlen, ifp, &ip_divert_cookie, &m); + hlen, ifp, &ip_divert_cookie, &m, &dst); if (ip_divert_port) { /* Divert packet */ (*inetsw[ip_protox[IPPROTO_DIVERT]].pr_input)(m, 0); goto done; } -#else +#else /* !IPDIVERT */ u_int16_t dummy = 0; /* If ipfw says divert, we have to just drop packet */ - if ((*ip_fw_chk_ptr)(&ip, hlen, ifp, &dummy, &m)) { + if ((*ip_fw_chk_ptr)(&ip, hlen, ifp, &dummy, &m, &dst)) { m_freem(m); goto done; } -#endif +#endif /* !IPDIVERT */ if (!m) { error = EACCES; goto done; } +#ifdef IPFIREWALL_FORWARD + /* Here we check dst to make sure it's directly reachable on the + * interface we previously thought it was. + * If it isn't (which may be likely in some situations) we have + * to re-route it (ie, find a route for the next-hop and the + * associated interface) and set them here. This is nested + * forwarding which in most cases is undesirable, except where + * such control is nigh impossible. So we do it here. + * And I'm babbling. + */ + if (old != dst) { + struct in_ifaddr *ia; + + /* It's changed... */ + /* There must be a better way to do this next line... */ + static struct route sro_fwd, *ro_fwd = &sro_fwd; +#ifdef IPFIREWALL_FORWARD_DEBUG + printf("IPFIREWALL_FORWARD: New dst ip: "); + print_ip(dst->sin_addr); + printf("\n"); +#endif + /* + * We need to figure out if we have been forwarded + * to a local socket. If so then we should somehow + * "loop back" to ip_input, and get directed to the + * PCB as if we had received this packet. This is + * because it may be dificult to identify the packets + * you want to forward until they are being output + * and have selected an interface. (e.g. locally + * initiated packets) If we used the loopback inteface, + * we would not be able to control what happens + * as the packet runs through ip_input() as + * it is done through a ISR. + */ + for (ia = TAILQ_FIRST(in_ifaddrhead); ia; + ia = TAILQ_NEXT(ia, ia_link)) { + /* + * If the addr to forward to is one + * of ours, we pretend to + * be the destination for this packet. + */ + if (IA_SIN(ia)->sin_addr.s_addr == + dst->sin_addr.s_addr) + break; + } + if (ia) { + /* tell ip_input "dont filter" */ + ip_fw_fwd_addr = dst; + if (m->m_pkthdr.rcvif == NULL) + m->m_pkthdr.rcvif = ifunit("lo0"); + ip->ip_len = htons((u_short)ip->ip_len); + ip->ip_off = htons((u_short)ip->ip_off); + ip->ip_sum = 0; + if (ip->ip_vhl == IP_VHL_BORING) { + ip->ip_sum = in_cksum_hdr(ip); + } else { + ip->ip_sum = in_cksum(m, hlen); + } + ip_input(m); + goto done; + } + /* Some of the logic for this was + * nicked from above. + * + * This rewrites the cached route in a local PCB. + * Is this what we want to do? + */ + bcopy(dst, &ro_fwd->ro_dst, sizeof(*dst)); + + ro_fwd->ro_rt = 0; + rtalloc_ign(ro_fwd, RTF_PRCLONING); + + if (ro_fwd->ro_rt == 0) { + ipstat.ips_noroute++; + error = EHOSTUNREACH; + goto bad; + } + + ia = ifatoia(ro_fwd->ro_rt->rt_ifa); + ifp = ro_fwd->ro_rt->rt_ifp; + ro_fwd->ro_rt->rt_use++; + if (ro_fwd->ro_rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in *)ro_fwd->ro_rt->rt_gateway; + if (ro_fwd->ro_rt->rt_flags & RTF_HOST) + isbroadcast = + (ro_fwd->ro_rt->rt_flags & RTF_BROADCAST); + else + isbroadcast = in_broadcast(dst->sin_addr, ifp); + RTFREE(ro->ro_rt); + ro->ro_rt = ro_fwd->ro_rt; + dst = (struct sockaddr_in *)&ro_fwd->ro_dst; + + /* + * If we added a default src ip earlier, + * which would have been gotten from the-then + * interface, do it again, from the new one. + */ + if (fwd_rewrite_src) + ip->ip_src = IA_SIN(ia)->sin_addr; + } +#endif /* IPFIREWALL_FORWARD */ } #endif /* COMPAT_IPFW */ + /* * If small enough for interface, can just send directly. */ if ((u_short)ip->ip_len <= ifp->if_mtu) { ip->ip_len = htons((u_short)ip->ip_len); ip->ip_off = htons((u_short)ip->ip_off); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m, hlen); } error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); goto done; } /* * Too large for interface; fragment if possible. * Must be able to put at least 8 bytes per fragment. */ if (ip->ip_off & IP_DF) { error = EMSGSIZE; /* * This case can happen if the user changed the MTU * of an interface after enabling IP on it. Because * most netifs don't keep track of routes pointing to * them, there is no way for one to update all its * routes when the MTU is changed. */ if ((ro->ro_rt->rt_flags & (RTF_UP | RTF_HOST)) && !(ro->ro_rt->rt_rmx.rmx_locks & RTV_MTU) && (ro->ro_rt->rt_rmx.rmx_mtu > ifp->if_mtu)) { ro->ro_rt->rt_rmx.rmx_mtu = ifp->if_mtu; } ipstat.ips_cantfrag++; goto bad; } len = (ifp->if_mtu - hlen) &~ 7; if (len < 8) { error = EMSGSIZE; goto bad; } { int mhlen, firstlen = len; struct mbuf **mnext = &m->m_nextpkt; /* * Loop through length of segment after first fragment, * make new header and copy data of each part and link onto chain. */ m0 = m; mhlen = sizeof (struct ip); for (off = hlen + len; off < (u_short)ip->ip_len; off += len) { MGETHDR(m, M_DONTWAIT, MT_HEADER); if (m == 0) { error = ENOBUFS; ipstat.ips_odropped++; goto sendorfree; } m->m_data += max_linkhdr; mhip = mtod(m, struct ip *); *mhip = *ip; if (hlen > sizeof (struct ip)) { mhlen = ip_optcopy(ip, mhip) + sizeof (struct ip); mhip->ip_vhl = IP_MAKE_VHL(IPVERSION, mhlen >> 2); } m->m_len = mhlen; mhip->ip_off = ((off - hlen) >> 3) + (ip->ip_off & ~IP_MF); if (ip->ip_off & IP_MF) mhip->ip_off |= IP_MF; if (off + len >= (u_short)ip->ip_len) len = (u_short)ip->ip_len - off; else mhip->ip_off |= IP_MF; mhip->ip_len = htons((u_short)(len + mhlen)); m->m_next = m_copy(m0, off, len); if (m->m_next == 0) { (void) m_free(m); error = ENOBUFS; /* ??? */ ipstat.ips_odropped++; goto sendorfree; } m->m_pkthdr.len = mhlen + len; m->m_pkthdr.rcvif = (struct ifnet *)0; mhip->ip_off = htons((u_short)mhip->ip_off); mhip->ip_sum = 0; if (mhip->ip_vhl == IP_VHL_BORING) { mhip->ip_sum = in_cksum_hdr(mhip); } else { mhip->ip_sum = in_cksum(m, mhlen); } *mnext = m; mnext = &m->m_nextpkt; ipstat.ips_ofragments++; } /* * Update first fragment by trimming what's been copied out * and updating header, then send each fragment (in order). */ m = m0; m_adj(m, hlen + firstlen - (u_short)ip->ip_len); m->m_pkthdr.len = hlen + firstlen; ip->ip_len = htons((u_short)m->m_pkthdr.len); ip->ip_off = htons((u_short)(ip->ip_off | IP_MF)); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(m, hlen); } sendorfree: for (m = m0; m; m = m0) { m0 = m->m_nextpkt; m->m_nextpkt = 0; if (error == 0) error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro->ro_rt); else m_freem(m); } if (error == 0) ipstat.ips_fragmented++; } done: return (error); bad: m_freem(m0); goto done; } /* * Insert IP options into preformed packet. * Adjust IP destination as required for IP source routing, * as indicated by a non-zero in_addr at the start of the options. * * XXX This routine assumes that the packet has no options in place. */ static struct mbuf * ip_insertoptions(m, opt, phlen) register struct mbuf *m; struct mbuf *opt; int *phlen; { register struct ipoption *p = mtod(opt, struct ipoption *); struct mbuf *n; register struct ip *ip = mtod(m, struct ip *); unsigned optlen; optlen = opt->m_len - sizeof(p->ipopt_dst); if (optlen + (u_short)ip->ip_len > IP_MAXPACKET) return (m); /* XXX should fail */ if (p->ipopt_dst.s_addr) ip->ip_dst = p->ipopt_dst; if (m->m_flags & M_EXT || m->m_data - optlen < m->m_pktdat) { MGETHDR(n, M_DONTWAIT, MT_HEADER); if (n == 0) return (m); n->m_pkthdr.len = m->m_pkthdr.len + optlen; m->m_len -= sizeof(struct ip); m->m_data += sizeof(struct ip); n->m_next = m; m = n; m->m_len = optlen + sizeof(struct ip); m->m_data += max_linkhdr; (void)memcpy(mtod(m, void *), ip, sizeof(struct ip)); } else { m->m_data -= optlen; m->m_len += optlen; m->m_pkthdr.len += optlen; ovbcopy((caddr_t)ip, mtod(m, caddr_t), sizeof(struct ip)); } ip = mtod(m, struct ip *); bcopy(p->ipopt_list, ip + 1, optlen); *phlen = sizeof(struct ip) + optlen; ip->ip_vhl = IP_MAKE_VHL(IPVERSION, *phlen >> 2); ip->ip_len += optlen; return (m); } /* * Copy options from ip to jp, * omitting those not copied during fragmentation. */ #if !defined(IPFILTER) && !defined(IPFILTER_LKM) static #endif int ip_optcopy(ip, jp) struct ip *ip, *jp; { register u_char *cp, *dp; int opt, optlen, cnt; cp = (u_char *)(ip + 1); dp = (u_char *)(jp + 1); cnt = (IP_VHL_HL(ip->ip_vhl) << 2) - sizeof (struct ip); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[0]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) { /* Preserve for IP mcast tunnel's LSRR alignment. */ *dp++ = IPOPT_NOP; optlen = 1; continue; } else optlen = cp[IPOPT_OLEN]; /* bogus lengths should have been caught by ip_dooptions */ if (optlen > cnt) optlen = cnt; if (IPOPT_COPIED(opt)) { bcopy(cp, dp, optlen); dp += optlen; } } for (optlen = dp - (u_char *)(jp+1); optlen & 0x3; optlen++) *dp++ = IPOPT_EOL; return (optlen); } /* * IP socket option processing. */ int ip_ctloutput(op, so, level, optname, mp, p) int op; struct socket *so; int level, optname; struct mbuf **mp; struct proc *p; { register struct inpcb *inp = sotoinpcb(so); register struct mbuf *m = *mp; register int optval = 0; int error = 0; if (level != IPPROTO_IP) { error = EINVAL; if (op == PRCO_SETOPT && *mp) (void) m_free(*mp); } else switch (op) { case PRCO_SETOPT: switch (optname) { case IP_OPTIONS: #ifdef notyet case IP_RETOPTS: return (ip_pcbopts(optname, &inp->inp_options, m)); #else return (ip_pcbopts(&inp->inp_options, m)); #endif case IP_TOS: case IP_TTL: case IP_RECVOPTS: case IP_RECVRETOPTS: case IP_RECVDSTADDR: case IP_RECVIF: if (m == 0 || m->m_len != sizeof(int)) error = EINVAL; else { optval = *mtod(m, int *); switch (optname) { case IP_TOS: inp->inp_ip_tos = optval; break; case IP_TTL: inp->inp_ip_ttl = optval; break; #define OPTSET(bit) \ if (optval) \ inp->inp_flags |= bit; \ else \ inp->inp_flags &= ~bit; case IP_RECVOPTS: OPTSET(INP_RECVOPTS); break; case IP_RECVRETOPTS: OPTSET(INP_RECVRETOPTS); break; case IP_RECVDSTADDR: OPTSET(INP_RECVDSTADDR); break; case IP_RECVIF: OPTSET(INP_RECVIF); break; } } break; #undef OPTSET case IP_MULTICAST_IF: case IP_MULTICAST_VIF: case IP_MULTICAST_TTL: case IP_MULTICAST_LOOP: case IP_ADD_MEMBERSHIP: case IP_DROP_MEMBERSHIP: error = ip_setmoptions(optname, &inp->inp_moptions, m); break; case IP_PORTRANGE: if (m == 0 || m->m_len != sizeof(int)) error = EINVAL; else { optval = *mtod(m, int *); switch (optval) { case IP_PORTRANGE_DEFAULT: inp->inp_flags &= ~(INP_LOWPORT); inp->inp_flags &= ~(INP_HIGHPORT); break; case IP_PORTRANGE_HIGH: inp->inp_flags &= ~(INP_LOWPORT); inp->inp_flags |= INP_HIGHPORT; break; case IP_PORTRANGE_LOW: inp->inp_flags &= ~(INP_HIGHPORT); inp->inp_flags |= INP_LOWPORT; break; default: error = EINVAL; break; } } break; default: error = ENOPROTOOPT; break; } if (m) (void)m_free(m); break; case PRCO_GETOPT: switch (optname) { case IP_OPTIONS: case IP_RETOPTS: *mp = m = m_get(M_WAIT, MT_SOOPTS); if (inp->inp_options) { m->m_len = inp->inp_options->m_len; bcopy(mtod(inp->inp_options, void *), mtod(m, void *), m->m_len); } else m->m_len = 0; break; case IP_TOS: case IP_TTL: case IP_RECVOPTS: case IP_RECVRETOPTS: case IP_RECVDSTADDR: case IP_RECVIF: *mp = m = m_get(M_WAIT, MT_SOOPTS); m->m_len = sizeof(int); switch (optname) { case IP_TOS: optval = inp->inp_ip_tos; break; case IP_TTL: optval = inp->inp_ip_ttl; break; #define OPTBIT(bit) (inp->inp_flags & bit ? 1 : 0) case IP_RECVOPTS: optval = OPTBIT(INP_RECVOPTS); break; case IP_RECVRETOPTS: optval = OPTBIT(INP_RECVRETOPTS); break; case IP_RECVDSTADDR: optval = OPTBIT(INP_RECVDSTADDR); break; case IP_RECVIF: optval = OPTBIT(INP_RECVIF); break; } *mtod(m, int *) = optval; break; case IP_MULTICAST_IF: case IP_MULTICAST_VIF: case IP_MULTICAST_TTL: case IP_MULTICAST_LOOP: case IP_ADD_MEMBERSHIP: case IP_DROP_MEMBERSHIP: error = ip_getmoptions(optname, inp->inp_moptions, mp); break; case IP_PORTRANGE: *mp = m = m_get(M_WAIT, MT_SOOPTS); m->m_len = sizeof(int); if (inp->inp_flags & INP_HIGHPORT) optval = IP_PORTRANGE_HIGH; else if (inp->inp_flags & INP_LOWPORT) optval = IP_PORTRANGE_LOW; else optval = 0; *mtod(m, int *) = optval; break; default: error = ENOPROTOOPT; break; } break; } return (error); } /* * Set up IP options in pcb for insertion in output packets. * Store in mbuf with pointer in pcbopt, adding pseudo-option * with destination address if source routed. */ static int #ifdef notyet ip_pcbopts(optname, pcbopt, m) int optname; #else ip_pcbopts(pcbopt, m) #endif struct mbuf **pcbopt; register struct mbuf *m; { register int cnt, optlen; register u_char *cp; u_char opt; /* turn off any old options */ if (*pcbopt) (void)m_free(*pcbopt); *pcbopt = 0; if (m == (struct mbuf *)0 || m->m_len == 0) { /* * Only turning off any previous options. */ if (m) (void)m_free(m); return (0); } #ifndef vax if (m->m_len % sizeof(long)) goto bad; #endif /* * IP first-hop destination address will be stored before * actual options; move other options back * and clear it when none present. */ if (m->m_data + m->m_len + sizeof(struct in_addr) >= &m->m_dat[MLEN]) goto bad; cnt = m->m_len; m->m_len += sizeof(struct in_addr); cp = mtod(m, u_char *) + sizeof(struct in_addr); ovbcopy(mtod(m, caddr_t), (caddr_t)cp, (unsigned)cnt); bzero(mtod(m, caddr_t), sizeof(struct in_addr)); for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= IPOPT_OLEN || optlen > cnt) goto bad; } switch (opt) { default: break; case IPOPT_LSRR: case IPOPT_SSRR: /* * user process specifies route as: * ->A->B->C->D * D must be our final destination (but we can't * check that since we may not have connected yet). * A is first hop destination, which doesn't appear in * actual IP option, but is stored before the options. */ if (optlen < IPOPT_MINOFF - 1 + sizeof(struct in_addr)) goto bad; m->m_len -= sizeof(struct in_addr); cnt -= sizeof(struct in_addr); optlen -= sizeof(struct in_addr); cp[IPOPT_OLEN] = optlen; /* * Move first hop before start of options. */ bcopy((caddr_t)&cp[IPOPT_OFFSET+1], mtod(m, caddr_t), sizeof(struct in_addr)); /* * Then copy rest of options back * to close up the deleted entry. */ ovbcopy((caddr_t)(&cp[IPOPT_OFFSET+1] + sizeof(struct in_addr)), (caddr_t)&cp[IPOPT_OFFSET+1], (unsigned)cnt + sizeof(struct in_addr)); break; } } if (m->m_len > MAX_IPOPTLEN + sizeof(struct in_addr)) goto bad; *pcbopt = m; return (0); bad: (void)m_free(m); return (EINVAL); } /* * Set the IP multicast options in response to user setsockopt(). */ static int ip_setmoptions(optname, imop, m) int optname; struct ip_moptions **imop; struct mbuf *m; { register int error = 0; u_char loop; register int i; struct in_addr addr; register struct ip_mreq *mreq; register struct ifnet *ifp; register struct ip_moptions *imo = *imop; struct route ro; register struct sockaddr_in *dst; int s; if (imo == NULL) { /* * No multicast option buffer attached to the pcb; * allocate one and initialize to default values. */ imo = (struct ip_moptions*)malloc(sizeof(*imo), M_IPMOPTS, M_WAITOK); if (imo == NULL) return (ENOBUFS); *imop = imo; imo->imo_multicast_ifp = NULL; imo->imo_multicast_vif = -1; imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL; imo->imo_multicast_loop = IP_DEFAULT_MULTICAST_LOOP; imo->imo_num_memberships = 0; } switch (optname) { /* store an index number for the vif you wanna use in the send */ case IP_MULTICAST_VIF: if (!legal_vif_num) { error = EOPNOTSUPP; break; } if (m == NULL || m->m_len != sizeof(int)) { error = EINVAL; break; } i = *(mtod(m, int *)); if (!legal_vif_num(i) && (i != -1)) { error = EINVAL; break; } imo->imo_multicast_vif = i; break; case IP_MULTICAST_IF: /* * Select the interface for outgoing multicast packets. */ if (m == NULL || m->m_len != sizeof(struct in_addr)) { error = EINVAL; break; } addr = *(mtod(m, struct in_addr *)); /* * INADDR_ANY is used to remove a previous selection. * When no interface is selected, a default one is * chosen every time a multicast packet is sent. */ if (addr.s_addr == INADDR_ANY) { imo->imo_multicast_ifp = NULL; break; } /* * The selected interface is identified by its local * IP address. Find the interface and confirm that * it supports multicasting. */ s = splimp(); INADDR_TO_IFP(addr, ifp); if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { splx(s); error = EADDRNOTAVAIL; break; } imo->imo_multicast_ifp = ifp; splx(s); break; case IP_MULTICAST_TTL: /* * Set the IP time-to-live for outgoing multicast packets. */ if (m == NULL || m->m_len != 1) { error = EINVAL; break; } imo->imo_multicast_ttl = *(mtod(m, u_char *)); break; case IP_MULTICAST_LOOP: /* * Set the loopback flag for outgoing multicast packets. * Must be zero or one. */ if (m == NULL || m->m_len != 1 || (loop = *(mtod(m, u_char *))) > 1) { error = EINVAL; break; } imo->imo_multicast_loop = loop; break; case IP_ADD_MEMBERSHIP: /* * Add a multicast group membership. * Group must be a valid IP multicast address. */ if (m == NULL || m->m_len != sizeof(struct ip_mreq)) { error = EINVAL; break; } mreq = mtod(m, struct ip_mreq *); if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) { error = EINVAL; break; } s = splimp(); /* * If no interface address was provided, use the interface of * the route to the given multicast address. */ if (mreq->imr_interface.s_addr == INADDR_ANY) { bzero((caddr_t)&ro, sizeof(ro)); dst = (struct sockaddr_in *)&ro.ro_dst; dst->sin_len = sizeof(*dst); dst->sin_family = AF_INET; dst->sin_addr = mreq->imr_multiaddr; rtalloc(&ro); if (ro.ro_rt == NULL) { error = EADDRNOTAVAIL; splx(s); break; } ifp = ro.ro_rt->rt_ifp; rtfree(ro.ro_rt); } else { INADDR_TO_IFP(mreq->imr_interface, ifp); } /* * See if we found an interface, and confirm that it * supports multicast. */ if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { error = EADDRNOTAVAIL; splx(s); break; } /* * See if the membership already exists or if all the * membership slots are full. */ for (i = 0; i < imo->imo_num_memberships; ++i) { if (imo->imo_membership[i]->inm_ifp == ifp && imo->imo_membership[i]->inm_addr.s_addr == mreq->imr_multiaddr.s_addr) break; } if (i < imo->imo_num_memberships) { error = EADDRINUSE; splx(s); break; } if (i == IP_MAX_MEMBERSHIPS) { error = ETOOMANYREFS; splx(s); break; } /* * Everything looks good; add a new record to the multicast * address list for the given interface. */ if ((imo->imo_membership[i] = in_addmulti(&mreq->imr_multiaddr, ifp)) == NULL) { error = ENOBUFS; splx(s); break; } ++imo->imo_num_memberships; splx(s); break; case IP_DROP_MEMBERSHIP: /* * Drop a multicast group membership. * Group must be a valid IP multicast address. */ if (m == NULL || m->m_len != sizeof(struct ip_mreq)) { error = EINVAL; break; } mreq = mtod(m, struct ip_mreq *); if (!IN_MULTICAST(ntohl(mreq->imr_multiaddr.s_addr))) { error = EINVAL; break; } s = splimp(); /* * If an interface address was specified, get a pointer * to its ifnet structure. */ if (mreq->imr_interface.s_addr == INADDR_ANY) ifp = NULL; else { INADDR_TO_IFP(mreq->imr_interface, ifp); if (ifp == NULL) { error = EADDRNOTAVAIL; splx(s); break; } } /* * Find the membership in the membership array. */ for (i = 0; i < imo->imo_num_memberships; ++i) { if ((ifp == NULL || imo->imo_membership[i]->inm_ifp == ifp) && imo->imo_membership[i]->inm_addr.s_addr == mreq->imr_multiaddr.s_addr) break; } if (i == imo->imo_num_memberships) { error = EADDRNOTAVAIL; splx(s); break; } /* * Give up the multicast address record to which the * membership points. */ in_delmulti(imo->imo_membership[i]); /* * Remove the gap in the membership array. */ for (++i; i < imo->imo_num_memberships; ++i) imo->imo_membership[i-1] = imo->imo_membership[i]; --imo->imo_num_memberships; splx(s); break; default: error = EOPNOTSUPP; break; } /* * If all options have default values, no need to keep the mbuf. */ if (imo->imo_multicast_ifp == NULL && imo->imo_multicast_vif == -1 && imo->imo_multicast_ttl == IP_DEFAULT_MULTICAST_TTL && imo->imo_multicast_loop == IP_DEFAULT_MULTICAST_LOOP && imo->imo_num_memberships == 0) { free(*imop, M_IPMOPTS); *imop = NULL; } return (error); } /* * Return the IP multicast options in response to user getsockopt(). */ static int ip_getmoptions(optname, imo, mp) int optname; register struct ip_moptions *imo; register struct mbuf **mp; { u_char *ttl; u_char *loop; struct in_addr *addr; struct in_ifaddr *ia; *mp = m_get(M_WAIT, MT_SOOPTS); switch (optname) { case IP_MULTICAST_VIF: if (imo != NULL) *(mtod(*mp, int *)) = imo->imo_multicast_vif; else *(mtod(*mp, int *)) = -1; (*mp)->m_len = sizeof(int); return(0); case IP_MULTICAST_IF: addr = mtod(*mp, struct in_addr *); (*mp)->m_len = sizeof(struct in_addr); if (imo == NULL || imo->imo_multicast_ifp == NULL) addr->s_addr = INADDR_ANY; else { IFP_TO_IA(imo->imo_multicast_ifp, ia); addr->s_addr = (ia == NULL) ? INADDR_ANY : IA_SIN(ia)->sin_addr.s_addr; } return (0); case IP_MULTICAST_TTL: ttl = mtod(*mp, u_char *); (*mp)->m_len = 1; *ttl = (imo == NULL) ? IP_DEFAULT_MULTICAST_TTL : imo->imo_multicast_ttl; return (0); case IP_MULTICAST_LOOP: loop = mtod(*mp, u_char *); (*mp)->m_len = 1; *loop = (imo == NULL) ? IP_DEFAULT_MULTICAST_LOOP : imo->imo_multicast_loop; return (0); default: return (EOPNOTSUPP); } } /* * Discard the IP multicast options. */ void ip_freemoptions(imo) register struct ip_moptions *imo; { register int i; if (imo != NULL) { for (i = 0; i < imo->imo_num_memberships; ++i) in_delmulti(imo->imo_membership[i]); free(imo, M_IPMOPTS); } } /* * Routine called from ip_output() to loop back a copy of an IP multicast * packet to the input queue of a specified interface. Note that this * calls the output routine of the loopback "driver", but with an interface * pointer that might NOT be a loopback interface -- evil, but easier than * replicating that code here. */ static void ip_mloopback(ifp, m, dst, hlen) struct ifnet *ifp; register struct mbuf *m; register struct sockaddr_in *dst; int hlen; { register struct ip *ip; struct mbuf *copym; copym = m_copy(m, 0, M_COPYALL); if (copym != NULL && (copym->m_flags & M_EXT || copym->m_len < hlen)) copym = m_pullup(copym, hlen); if (copym != NULL) { /* * We don't bother to fragment if the IP length is greater * than the interface's MTU. Can this possibly matter? */ ip = mtod(copym, struct ip *); ip->ip_len = htons((u_short)ip->ip_len); ip->ip_off = htons((u_short)ip->ip_off); ip->ip_sum = 0; if (ip->ip_vhl == IP_VHL_BORING) { ip->ip_sum = in_cksum_hdr(ip); } else { ip->ip_sum = in_cksum(copym, hlen); } /* * NB: * It's not clear whether there are any lingering * reentrancy problems in other areas which might * be exposed by using ip_input directly (in * particular, everything which modifies the packet * in-place). Yet another option is using the * protosw directly to deliver the looped back * packet. For the moment, we'll err on the side * of safety by using if_simloop(). */ #if 1 /* XXX */ if (dst->sin_family != AF_INET) { printf("ip_mloopback: bad address family %d\n", dst->sin_family); dst->sin_family = AF_INET; } #endif #ifdef notdef copym->m_pkthdr.rcvif = ifp; ip_input(copym); #else if_simloop(ifp, copym, (struct sockaddr *)dst, 0); #endif } } Index: head/sys/netinet/ip_var.h =================================================================== --- head/sys/netinet/ip_var.h (revision 37408) +++ head/sys/netinet/ip_var.h (revision 37409) @@ -1,216 +1,219 @@ /* * Copyright (c) 1982, 1986, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)ip_var.h 8.2 (Berkeley) 1/9/95 - * $Id: ip_var.h,v 1.40 1998/06/06 19:39:10 julian Exp $ + * $Id: ip_var.h,v 1.41 1998/06/08 09:47:42 bde Exp $ */ #ifndef _NETINET_IP_VAR_H_ #define _NETINET_IP_VAR_H_ /* * Overlay for ip header used by other protocols (tcp, udp). */ struct ipovly { caddr_t ih_next, ih_prev; /* for protocol sequence q's */ u_char ih_x1; /* (unused) */ u_char ih_pr; /* protocol */ u_short ih_len; /* protocol length */ struct in_addr ih_src; /* source internet address */ struct in_addr ih_dst; /* destination internet address */ }; /* * Ip reassembly queue structure. Each fragment * being reassembled is attached to one of these structures. * They are timed out after ipq_ttl drops to 0, and may also * be reclaimed if memory becomes tight. */ struct ipq { struct ipq *next,*prev; /* to other reass headers */ u_char ipq_ttl; /* time for reass q to live */ u_char ipq_p; /* protocol of this fragment */ u_short ipq_id; /* sequence id for reassembly */ struct ipasfrag *ipq_next,*ipq_prev; /* to ip headers of fragments */ struct in_addr ipq_src,ipq_dst; #ifdef IPDIVERT u_short ipq_divert; /* divert protocol port */ u_short ipq_div_cookie; /* divert protocol cookie */ #endif }; /* * Ip header, when holding a fragment. * * Note: ipf_next must be at same offset as ipq_next above */ struct ipasfrag { #if BYTE_ORDER == LITTLE_ENDIAN u_int ip_hl:4, ip_v:4; #endif #if BYTE_ORDER == BIG_ENDIAN u_int ip_v:4, ip_hl:4; #endif u_char ipf_mff; /* XXX overlays ip_tos: use low bit * to avoid destroying tos; * copied from (ip_off&IP_MF) */ u_short ip_len; u_short ip_id; u_short ip_off; u_char ip_ttl; u_char ip_p; u_short ip_sum; struct ipasfrag *ipf_next; /* next fragment */ struct ipasfrag *ipf_prev; /* previous fragment */ }; /* * Structure stored in mbuf in inpcb.ip_options * and passed to ip_output when ip options are in use. * The actual length of the options (including ipopt_dst) * is in m_len. */ #define MAX_IPOPTLEN 40 struct ipoption { struct in_addr ipopt_dst; /* first-hop dst if source routed */ char ipopt_list[MAX_IPOPTLEN]; /* options proper */ }; /* * Structure attached to inpcb.ip_moptions and * passed to ip_output when IP multicast options are in use. */ struct ip_moptions { struct ifnet *imo_multicast_ifp; /* ifp for outgoing multicasts */ u_char imo_multicast_ttl; /* TTL for outgoing multicasts */ u_char imo_multicast_loop; /* 1 => hear sends if a member */ u_short imo_num_memberships; /* no. memberships this socket */ struct in_multi *imo_membership[IP_MAX_MEMBERSHIPS]; u_long imo_multicast_vif; /* vif num outgoing multicasts */ }; struct ipstat { u_long ips_total; /* total packets received */ u_long ips_badsum; /* checksum bad */ u_long ips_tooshort; /* packet too short */ u_long ips_toosmall; /* not enough data */ u_long ips_badhlen; /* ip header length < data size */ u_long ips_badlen; /* ip length < ip header length */ u_long ips_fragments; /* fragments received */ u_long ips_fragdropped; /* frags dropped (dups, out of space) */ u_long ips_fragtimeout; /* fragments timed out */ u_long ips_forward; /* packets forwarded */ u_long ips_fastforward; /* packets fast forwarded */ u_long ips_cantforward; /* packets rcvd for unreachable dest */ u_long ips_redirectsent; /* packets forwarded on same net */ u_long ips_noproto; /* unknown or unsupported protocol */ u_long ips_delivered; /* datagrams delivered to upper level*/ u_long ips_localout; /* total ip packets generated here */ u_long ips_odropped; /* lost packets due to nobufs, etc. */ u_long ips_reassembled; /* total packets reassembled ok */ u_long ips_fragmented; /* datagrams successfully fragmented */ u_long ips_ofragments; /* output fragments created */ u_long ips_cantfrag; /* don't fragment flag was set, etc. */ u_long ips_badoptions; /* error in option processing */ u_long ips_noroute; /* packets discarded due to no route */ u_long ips_badvers; /* ip version != 4 */ u_long ips_rawout; /* total raw ip packets generated */ u_long ips_toolong; /* ip length > max ip packet size */ u_long ips_notmember; /* multicasts for unregistered grps */ }; #ifdef KERNEL /* flags passed to ip_output as last parameter */ #define IP_FORWARDING 0x1 /* most of ip header exists */ #define IP_RAWOUTPUT 0x2 /* raw ip header exists */ #define IP_ROUTETOIF SO_DONTROUTE /* bypass routing tables */ #define IP_ALLOWBROADCAST SO_BROADCAST /* can send broadcast packets */ struct inpcb; struct route; extern struct ipstat ipstat; extern u_short ip_id; /* ip packet ctr, for ids */ extern int ip_defttl; /* default IP ttl */ extern int ipforwarding; /* ip forwarding */ extern u_char ip_protox[]; extern struct socket *ip_rsvpd; /* reservation protocol daemon */ extern struct socket *ip_mrouter; /* multicast routing daemon */ extern int (*legal_vif_num) __P((int)); extern u_long (*ip_mcast_src) __P((int)); extern int rsvp_on; extern struct pr_usrreqs rip_usrreqs; int ip_ctloutput __P((int, struct socket *, int, int, struct mbuf **, struct proc *)); void ip_drain __P((void)); void ip_freemoptions __P((struct ip_moptions *)); void ip_init __P((void)); extern int (*ip_mforward) __P((struct ip *, struct ifnet *, struct mbuf *, struct ip_moptions *)); int ip_output __P((struct mbuf *, struct mbuf *, struct route *, int, struct ip_moptions *)); void ip_savecontrol __P((struct inpcb *, struct mbuf **, struct ip *, struct mbuf *)); void ip_slowtimo __P((void)); struct mbuf * ip_srcroute __P((void)); void ip_stripoptions __P((struct mbuf *, struct mbuf *)); int rip_ctloutput __P((int, struct socket *, int, int, struct mbuf **, struct proc *p)); void rip_ctlinput __P((int, struct sockaddr *, void *)); void rip_init __P((void)); void rip_input __P((struct mbuf *, int)); int rip_output __P((struct mbuf *, struct socket *, u_long)); void ipip_input __P((struct mbuf *, int)); void rsvp_input __P((struct mbuf *, int)); int ip_rsvp_init __P((struct socket *)); int ip_rsvp_done __P((void)); int ip_rsvp_vif_init __P((struct socket *, struct mbuf *)); int ip_rsvp_vif_done __P((struct socket *, struct mbuf *)); void ip_rsvp_force_done __P((struct socket *)); #ifdef IPDIVERT void div_init __P((void)); void div_input __P((struct mbuf *, int)); extern struct pr_usrreqs div_usrreqs; extern u_short ip_divert_port; extern u_short ip_divert_cookie; #endif /* IPDIVERT */ + +struct sockaddr_in; +extern struct sockaddr_in *ip_fw_fwd_addr; #endif /* KERNEL */ #endif /* _NETINET_IP_VAR_H_ */ Index: head/sys/netinet/tcp_input.c =================================================================== --- head/sys/netinet/tcp_input.c (revision 37408) +++ head/sys/netinet/tcp_input.c (revision 37409) @@ -1,2116 +1,2144 @@ /* * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)tcp_input.c 8.12 (Berkeley) 5/24/95 - * $Id: tcp_input.c,v 1.77 1998/05/18 17:11:24 guido Exp $ + * $Id: tcp_input.c,v 1.78 1998/05/31 18:42:49 peter Exp $ */ +#include "opt_ipfw.h" /* for ipfw_fwd */ #include "opt_tcpdebug.h" #include #include #include #include #include #include #include /* for proc0 declaration */ #include #include #include #include #include /* before tcp_seq.h, for tcp_random18() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TCPDEBUG #include static struct tcpiphdr tcp_saveti; #endif static int tcprexmtthresh = 3; tcp_seq tcp_iss; tcp_cc tcp_ccgen; struct tcpstat tcpstat; SYSCTL_STRUCT(_net_inet_tcp, TCPCTL_STATS, stats, CTLFLAG_RD, &tcpstat , tcpstat, ""); static int log_in_vain = 0; SYSCTL_INT(_net_inet_tcp, OID_AUTO, log_in_vain, CTLFLAG_RW, &log_in_vain, 0, ""); int tcp_delack_enabled = 1; SYSCTL_INT(_net_inet_tcp, OID_AUTO, delayed_ack, CTLFLAG_RW, &tcp_delack_enabled, 0, ""); u_long tcp_now; struct inpcbhead tcb; struct inpcbinfo tcbinfo; static void tcp_dooptions __P((struct tcpcb *, u_char *, int, struct tcpiphdr *, struct tcpopt *)); static void tcp_pulloutofband __P((struct socket *, struct tcpiphdr *, struct mbuf *)); static int tcp_reass __P((struct tcpcb *, struct tcpiphdr *, struct mbuf *)); static void tcp_xmit_timer __P((struct tcpcb *, int)); /* * Insert segment ti into reassembly queue of tcp with * control block tp. Return TH_FIN if reassembly now includes * a segment with FIN. The macro form does the common case inline * (segment is the next to be received on an established connection, * and the queue is empty), avoiding linkage into and removal * from the queue and repetition of various conversions. * Set DELACK for segments received in order, but ack immediately * when segments are out of order (so fast retransmit can work). */ #define TCP_REASS(tp, ti, m, so, flags) { \ if ((ti)->ti_seq == (tp)->rcv_nxt && \ (tp)->seg_next == (struct tcpiphdr *)(tp) && \ (tp)->t_state == TCPS_ESTABLISHED) { \ if (tcp_delack_enabled) \ tp->t_flags |= TF_DELACK; \ else \ tp->t_flags |= TF_ACKNOW; \ (tp)->rcv_nxt += (ti)->ti_len; \ flags = (ti)->ti_flags & TH_FIN; \ tcpstat.tcps_rcvpack++;\ tcpstat.tcps_rcvbyte += (ti)->ti_len;\ sbappend(&(so)->so_rcv, (m)); \ sorwakeup(so); \ } else { \ (flags) = tcp_reass((tp), (ti), (m)); \ tp->t_flags |= TF_ACKNOW; \ } \ } static int tcp_reass(tp, ti, m) register struct tcpcb *tp; register struct tcpiphdr *ti; struct mbuf *m; { register struct tcpiphdr *q; struct socket *so = tp->t_inpcb->inp_socket; int flags; /* * Call with ti==0 after become established to * force pre-ESTABLISHED data up to user socket. */ if (ti == 0) goto present; /* * Find a segment which begins after this one does. */ for (q = tp->seg_next; q != (struct tcpiphdr *)tp; q = (struct tcpiphdr *)q->ti_next) if (SEQ_GT(q->ti_seq, ti->ti_seq)) break; /* * If there is a preceding segment, it may provide some of * our data already. If so, drop the data from the incoming * segment. If it provides all of our data, drop us. */ if ((struct tcpiphdr *)q->ti_prev != (struct tcpiphdr *)tp) { register int i; q = (struct tcpiphdr *)q->ti_prev; /* conversion to int (in i) handles seq wraparound */ i = q->ti_seq + q->ti_len - ti->ti_seq; if (i > 0) { if (i >= ti->ti_len) { tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += ti->ti_len; m_freem(m); /* * Try to present any queued data * at the left window edge to the user. * This is needed after the 3-WHS * completes. */ goto present; /* ??? */ } m_adj(m, i); ti->ti_len -= i; ti->ti_seq += i; } q = (struct tcpiphdr *)(q->ti_next); } tcpstat.tcps_rcvoopack++; tcpstat.tcps_rcvoobyte += ti->ti_len; REASS_MBUF(ti) = m; /* XXX */ /* * While we overlap succeeding segments trim them or, * if they are completely covered, dequeue them. */ while (q != (struct tcpiphdr *)tp) { register int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; if (i <= 0) break; if (i < q->ti_len) { q->ti_seq += i; q->ti_len -= i; m_adj(REASS_MBUF(q), i); break; } q = (struct tcpiphdr *)q->ti_next; m = REASS_MBUF((struct tcpiphdr *)q->ti_prev); remque(q->ti_prev); m_freem(m); } /* * Stick new segment in its place. */ insque(ti, q->ti_prev); present: /* * Present data to user, advancing rcv_nxt through * completed sequence space. */ if (!TCPS_HAVEESTABLISHED(tp->t_state)) return (0); ti = tp->seg_next; if (ti == (struct tcpiphdr *)tp || ti->ti_seq != tp->rcv_nxt) return (0); do { tp->rcv_nxt += ti->ti_len; flags = ti->ti_flags & TH_FIN; remque(ti); m = REASS_MBUF(ti); ti = (struct tcpiphdr *)ti->ti_next; if (so->so_state & SS_CANTRCVMORE) m_freem(m); else sbappend(&so->so_rcv, m); } while (ti != (struct tcpiphdr *)tp && ti->ti_seq == tp->rcv_nxt); sorwakeup(so); return (flags); } /* * TCP input routine, follows pages 65-76 of the * protocol specification dated September, 1981 very closely. */ void tcp_input(m, iphlen) register struct mbuf *m; int iphlen; { register struct tcpiphdr *ti; register struct inpcb *inp; u_char *optp = NULL; int optlen = 0; int len, tlen, off; register struct tcpcb *tp = 0; register int tiflags; struct socket *so = 0; int todrop, acked, ourfinisacked, needoutput = 0; struct in_addr laddr; int dropsocket = 0; int iss = 0; u_long tiwin; struct tcpopt to; /* options in this segment */ struct rmxp_tao *taop; /* pointer to our TAO cache entry */ struct rmxp_tao tao_noncached; /* in case there's no cached entry */ #ifdef TCPDEBUG short ostate = 0; #endif bzero((char *)&to, sizeof(to)); tcpstat.tcps_rcvtotal++; /* * Get IP and TCP header together in first mbuf. * Note: IP leaves IP header in first mbuf. */ ti = mtod(m, struct tcpiphdr *); if (iphlen > sizeof (struct ip)) ip_stripoptions(m, (struct mbuf *)0); if (m->m_len < sizeof (struct tcpiphdr)) { if ((m = m_pullup(m, sizeof (struct tcpiphdr))) == 0) { tcpstat.tcps_rcvshort++; return; } ti = mtod(m, struct tcpiphdr *); } /* * Checksum extended TCP header and data. */ tlen = ((struct ip *)ti)->ip_len; len = sizeof (struct ip) + tlen; ti->ti_next = ti->ti_prev = 0; ti->ti_x1 = 0; ti->ti_len = (u_short)tlen; HTONS(ti->ti_len); ti->ti_sum = in_cksum(m, len); if (ti->ti_sum) { tcpstat.tcps_rcvbadsum++; goto drop; } /* * Check that TCP offset makes sense, * pull out TCP options and adjust length. XXX */ off = ti->ti_off << 2; if (off < sizeof (struct tcphdr) || off > tlen) { tcpstat.tcps_rcvbadoff++; goto drop; } tlen -= off; ti->ti_len = tlen; if (off > sizeof (struct tcphdr)) { if (m->m_len < sizeof(struct ip) + off) { if ((m = m_pullup(m, sizeof (struct ip) + off)) == 0) { tcpstat.tcps_rcvshort++; return; } ti = mtod(m, struct tcpiphdr *); } optlen = off - sizeof (struct tcphdr); optp = mtod(m, u_char *) + sizeof (struct tcpiphdr); } tiflags = ti->ti_flags; /* * Convert TCP protocol specific fields to host format. */ NTOHL(ti->ti_seq); NTOHL(ti->ti_ack); NTOHS(ti->ti_win); NTOHS(ti->ti_urp); /* * Drop TCP, IP headers and TCP options. */ m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); /* * Locate pcb for segment. */ findpcb: +#ifdef IPFIREWALL_FORWARD + if (ip_fw_fwd_addr != NULL) { + /* + * Diverted. Pretend to be the destination. + * already got one like this? + */ + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, ti->ti_sport, + ti->ti_dst, ti->ti_dport, 0); + if (!inp) { + /* + * No, then it's new. Try find the ambushing socket + */ + if (!ip_fw_fwd_addr->sin_port) { + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, + ti->ti_sport, ip_fw_fwd_addr->sin_addr, + ti->ti_dport, 1); + } else { + inp = in_pcblookup_hash(&tcbinfo, + ti->ti_src, ti->ti_sport, + ip_fw_fwd_addr->sin_addr, + ntohs(ip_fw_fwd_addr->sin_port), 1); + } + } + ip_fw_fwd_addr = NULL; + } else +#endif /* IPFIREWALL_FORWARD */ + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, ti->ti_sport, ti->ti_dst, ti->ti_dport, 1); /* * If the state is CLOSED (i.e., TCB does not exist) then * all data in the incoming segment is discarded. * If the TCB exists but is in CLOSED state, it is embryonic, * but should either do a listen or a connect soon. */ if (inp == NULL) { if (log_in_vain && tiflags & TH_SYN) { char buf[4*sizeof "123"]; strcpy(buf, inet_ntoa(ti->ti_dst)); log(LOG_INFO, "Connection attempt to TCP %s:%d from %s:%d\n", buf, ntohs(ti->ti_dport), inet_ntoa(ti->ti_src), ntohs(ti->ti_sport)); } goto dropwithreset; } tp = intotcpcb(inp); if (tp == 0) goto dropwithreset; if (tp->t_state == TCPS_CLOSED) goto drop; /* Unscale the window into a 32-bit value. */ if ((tiflags & TH_SYN) == 0) tiwin = ti->ti_win << tp->snd_scale; else tiwin = ti->ti_win; so = inp->inp_socket; if (so->so_options & (SO_DEBUG|SO_ACCEPTCONN)) { #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) { ostate = tp->t_state; tcp_saveti = *ti; } #endif if (so->so_options & SO_ACCEPTCONN) { register struct tcpcb *tp0 = tp; struct socket *so2; if ((tiflags & (TH_RST|TH_ACK|TH_SYN)) != TH_SYN) { /* * Note: dropwithreset makes sure we don't * send a RST in response to a RST. */ if (tiflags & TH_ACK) { tcpstat.tcps_badsyn++; goto dropwithreset; } goto drop; } so2 = sonewconn(so, 0); if (so2 == 0) { tcpstat.tcps_listendrop++; so2 = sodropablereq(so); if (so2) { tcp_drop(sototcpcb(so2), ETIMEDOUT); so2 = sonewconn(so, 0); } if (!so2) goto drop; } so = so2; /* * This is ugly, but .... * * Mark socket as temporary until we're * committed to keeping it. The code at * ``drop'' and ``dropwithreset'' check the * flag dropsocket to see if the temporary * socket created here should be discarded. * We mark the socket as discardable until * we're committed to it below in TCPS_LISTEN. */ dropsocket++; inp = (struct inpcb *)so->so_pcb; inp->inp_laddr = ti->ti_dst; inp->inp_lport = ti->ti_dport; if (in_pcbinshash(inp) != 0) { /* * Undo the assignments above if we failed to put * the PCB on the hash lists. */ inp->inp_laddr.s_addr = INADDR_ANY; inp->inp_lport = 0; goto drop; } inp->inp_options = ip_srcroute(); tp = intotcpcb(inp); tp->t_state = TCPS_LISTEN; tp->t_flags |= tp0->t_flags & (TF_NOPUSH|TF_NOOPT); /* Compute proper scaling value from buffer space */ while (tp->request_r_scale < TCP_MAX_WINSHIFT && TCP_MAXWIN << tp->request_r_scale < so->so_rcv.sb_hiwat) tp->request_r_scale++; } } /* * Segment received on connection. * Reset idle time and keep-alive timer. */ tp->t_idle = 0; if (TCPS_HAVEESTABLISHED(tp->t_state)) tp->t_timer[TCPT_KEEP] = tcp_keepidle; /* * Process options if not in LISTEN state, * else do it below (after getting remote address). */ if (tp->t_state != TCPS_LISTEN) tcp_dooptions(tp, optp, optlen, ti, &to); /* * Header prediction: check for the two common cases * of a uni-directional data xfer. If the packet has * no control flags, is in-sequence, the window didn't * change and we're not retransmitting, it's a * candidate. If the length is zero and the ack moved * forward, we're the sender side of the xfer. Just * free the data acked & wake any higher level process * that was blocked waiting for space. If the length * is non-zero and the ack didn't move, we're the * receiver side. If we're getting packets in-order * (the reassembly queue is empty), add the data to * the socket buffer and note that we need a delayed ack. * Make sure that the hidden state-flags are also off. * Since we check for TCPS_ESTABLISHED above, it can only * be TH_NEEDSYN. */ if (tp->t_state == TCPS_ESTABLISHED && (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK && ((tp->t_flags & (TF_NEEDSYN|TF_NEEDFIN)) == 0) && ((to.to_flag & TOF_TS) == 0 || TSTMP_GEQ(to.to_tsval, tp->ts_recent)) && /* * Using the CC option is compulsory if once started: * the segment is OK if no T/TCP was negotiated or * if the segment has a CC option equal to CCrecv */ ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) != (TF_REQ_CC|TF_RCVD_CC) || (to.to_flag & TOF_CC) != 0 && to.to_cc == tp->cc_recv) && ti->ti_seq == tp->rcv_nxt && tiwin && tiwin == tp->snd_wnd && tp->snd_nxt == tp->snd_max) { /* * If last ACK falls within this segment's sequence numbers, * record the timestamp. * NOTE that the test is modified according to the latest * proposal of the tcplw@cray.com list (Braden 1993/04/26). */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { tp->ts_recent_age = tcp_now; tp->ts_recent = to.to_tsval; } if (ti->ti_len == 0) { if (SEQ_GT(ti->ti_ack, tp->snd_una) && SEQ_LEQ(ti->ti_ack, tp->snd_max) && tp->snd_cwnd >= tp->snd_wnd && tp->t_dupacks < tcprexmtthresh) { /* * this is a pure ack for outstanding data. */ ++tcpstat.tcps_predack; if ((to.to_flag & TOF_TS) != 0) tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 1); else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) tcp_xmit_timer(tp, tp->t_rtt); acked = ti->ti_ack - tp->snd_una; tcpstat.tcps_rcvackpack++; tcpstat.tcps_rcvackbyte += acked; sbdrop(&so->so_snd, acked); tp->snd_una = ti->ti_ack; m_freem(m); /* * If all outstanding data are acked, stop * retransmit timer, otherwise restart timer * using current (possibly backed-off) value. * If process is waiting for space, * wakeup/selwakeup/signal. If data * are ready to send, let tcp_output * decide between more output or persist. */ if (tp->snd_una == tp->snd_max) tp->t_timer[TCPT_REXMT] = 0; else if (tp->t_timer[TCPT_PERSIST] == 0) tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; sowwakeup(so); if (so->so_snd.sb_cc) (void) tcp_output(tp); return; } } else if (ti->ti_ack == tp->snd_una && tp->seg_next == (struct tcpiphdr *)tp && ti->ti_len <= sbspace(&so->so_rcv)) { /* * this is a pure, in-sequence data packet * with nothing on the reassembly queue and * we have enough buffer space to take it. */ ++tcpstat.tcps_preddat; tp->rcv_nxt += ti->ti_len; tcpstat.tcps_rcvpack++; tcpstat.tcps_rcvbyte += ti->ti_len; /* * Add data to socket buffer. */ sbappend(&so->so_rcv, m); sorwakeup(so); if (tcp_delack_enabled) { tp->t_flags |= TF_DELACK; } else { tp->t_flags |= TF_ACKNOW; tcp_output(tp); } return; } } /* * Calculate amount of space in receive window, * and then do TCP input processing. * Receive window is amount of space in rcv queue, * but not less than advertised window. */ { int win; win = sbspace(&so->so_rcv); if (win < 0) win = 0; tp->rcv_wnd = imax(win, (int)(tp->rcv_adv - tp->rcv_nxt)); } switch (tp->t_state) { /* * If the state is LISTEN then ignore segment if it contains an RST. * If the segment contains an ACK then it is bad and send a RST. * If it does not contain a SYN then it is not interesting; drop it. * If it is from this socket, drop it, it must be forged. * Don't bother responding if the destination was a broadcast. * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial * tp->iss, and send a segment: * * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. * Fill in remote peer address fields if not previously specified. * Enter SYN_RECEIVED state, and process any other fields of this * segment in this state. */ case TCPS_LISTEN: { register struct sockaddr_in *sin; if (tiflags & TH_RST) goto drop; if (tiflags & TH_ACK) goto dropwithreset; if ((tiflags & TH_SYN) == 0) goto drop; if ((ti->ti_dport == ti->ti_sport) && (ti->ti_dst.s_addr == ti->ti_src.s_addr)) goto drop; /* * RFC1122 4.2.3.10, p. 104: discard bcast/mcast SYN * in_broadcast() should never return true on a received * packet with M_BCAST not set. */ if (m->m_flags & (M_BCAST|M_MCAST) || IN_MULTICAST(ntohl(ti->ti_dst.s_addr))) goto drop; MALLOC(sin, struct sockaddr_in *, sizeof *sin, M_SONAME, M_NOWAIT); if (sin == NULL) goto drop; sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = ti->ti_src; sin->sin_port = ti->ti_sport; bzero((caddr_t)sin->sin_zero, sizeof(sin->sin_zero)); laddr = inp->inp_laddr; if (inp->inp_laddr.s_addr == INADDR_ANY) inp->inp_laddr = ti->ti_dst; if (in_pcbconnect(inp, (struct sockaddr *)sin, &proc0)) { inp->inp_laddr = laddr; FREE(sin, M_SONAME); goto drop; } FREE(sin, M_SONAME); tp->t_template = tcp_template(tp); if (tp->t_template == 0) { tp = tcp_drop(tp, ENOBUFS); dropsocket = 0; /* socket is already gone */ goto drop; } if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } tcp_dooptions(tp, optp, optlen, ti, &to); if (iss) tp->iss = iss; else tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/4; tp->irs = ti->ti_seq; tcp_sendseqinit(tp); tcp_rcvseqinit(tp); /* * Initialization of the tcpcb for transaction; * set SND.WND = SEG.WND, * initialize CCsend and CCrecv. */ tp->snd_wnd = tiwin; /* initial send-window */ tp->cc_send = CC_INC(tcp_ccgen); tp->cc_recv = to.to_cc; /* * Perform TAO test on incoming CC (SEG.CC) option, if any. * - compare SEG.CC against cached CC from the same host, * if any. * - if SEG.CC > chached value, SYN must be new and is accepted * immediately: save new CC in the cache, mark the socket * connected, enter ESTABLISHED state, turn on flag to * send a SYN in the next segment. * A virtual advertised window is set in rcv_adv to * initialize SWS prevention. Then enter normal segment * processing: drop SYN, process data and FIN. * - otherwise do a normal 3-way handshake. */ if ((to.to_flag & TOF_CC) != 0) { if (((tp->t_flags & TF_NOPUSH) != 0) && taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { taop->tao_cc = to.to_cc; tp->t_state = TCPS_ESTABLISHED; /* * If there is a FIN, or if there is data and the * connection is local, then delay SYN,ACK(SYN) in * the hope of piggy-backing it on a response * segment. Otherwise must send ACK now in case * the other side is slow starting. */ if (tcp_delack_enabled && ((tiflags & TH_FIN) || (ti->ti_len != 0 && in_localaddr(inp->inp_faddr)))) tp->t_flags |= (TF_DELACK | TF_NEEDSYN); else tp->t_flags |= (TF_ACKNOW | TF_NEEDSYN); /* * Limit the `virtual advertised window' to TCP_MAXWIN * here. Even if we requested window scaling, it will * become effective only later when our SYN is acked. */ tp->rcv_adv += min(tp->rcv_wnd, TCP_MAXWIN); tcpstat.tcps_connects++; soisconnected(so); tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* else do standard 3-way handshake */ } else { /* * No CC option, but maybe CC.NEW: * invalidate cached value. */ taop->tao_cc = 0; } /* * TAO test failed or there was no CC option, * do a standard 3-way handshake. */ tp->t_flags |= TF_ACKNOW; tp->t_state = TCPS_SYN_RECEIVED; tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* * If the state is SYN_RECEIVED: * if seg contains an ACK, but not for our SYN/ACK, send a RST. */ case TCPS_SYN_RECEIVED: if ((tiflags & TH_ACK) && (SEQ_LEQ(ti->ti_ack, tp->snd_una) || SEQ_GT(ti->ti_ack, tp->snd_max))) goto dropwithreset; break; /* * If the state is SYN_SENT: * if seg contains an ACK, but not for our SYN, drop the input. * if seg contains a RST, then drop the connection. * if seg does not contain SYN, then drop it. * Otherwise this is an acceptable SYN segment * initialize tp->rcv_nxt and tp->irs * if seg contains ack then advance tp->snd_una * if SYN has been acked change to ESTABLISHED else SYN_RCVD state * arrange for segment to be acked (eventually) * continue processing rest of data/controls, beginning with URG */ case TCPS_SYN_SENT: if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } if ((tiflags & TH_ACK) && (SEQ_LEQ(ti->ti_ack, tp->iss) || SEQ_GT(ti->ti_ack, tp->snd_max))) { /* * If we have a cached CCsent for the remote host, * hence we haven't just crashed and restarted, * do not send a RST. This may be a retransmission * from the other side after our earlier ACK was lost. * Our new SYN, when it arrives, will serve as the * needed ACK. */ if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } if (tiflags & TH_RST) { if (tiflags & TH_ACK) tp = tcp_drop(tp, ECONNREFUSED); goto drop; } if ((tiflags & TH_SYN) == 0) goto drop; tp->snd_wnd = ti->ti_win; /* initial send window */ tp->cc_recv = to.to_cc; /* foreign CC */ tp->irs = ti->ti_seq; tcp_rcvseqinit(tp); if (tiflags & TH_ACK) { /* * Our SYN was acked. If segment contains CC.ECHO * option, check it to make sure this segment really * matches our SYN. If not, just drop it as old * duplicate, but send an RST if we're still playing * by the old rules. If no CC.ECHO option, make sure * we don't get fooled into using T/TCP. */ if (to.to_flag & TOF_CCECHO) { if (tp->cc_send != to.to_ccecho) if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } else tp->t_flags &= ~TF_RCVD_CC; tcpstat.tcps_connects++; soisconnected(so); /* Do window scaling on this connection? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } /* Segment is acceptable, update cache if undefined. */ if (taop->tao_ccsent == 0) taop->tao_ccsent = to.to_ccecho; tp->rcv_adv += tp->rcv_wnd; tp->snd_una++; /* SYN is acked */ /* * If there's data, delay ACK; if there's also a FIN * ACKNOW will be turned on later. */ if (tcp_delack_enabled && ti->ti_len != 0) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; /* * Received in SYN_SENT[*] state. * Transitions: * SYN_SENT --> ESTABLISHED * SYN_SENT* --> FIN_WAIT_1 */ if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; tiflags &= ~TH_SYN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } } else { /* * Received initial SYN in SYN-SENT[*] state => simul- * taneous open. If segment contains CC option and there is * a cached CC, apply TAO test; if it succeeds, connection is * half-synchronized. Otherwise, do 3-way handshake: * SYN-SENT -> SYN-RECEIVED * SYN-SENT* -> SYN-RECEIVED* * If there was no CC option, clear cached CC value. */ tp->t_flags |= TF_ACKNOW; tp->t_timer[TCPT_REXMT] = 0; if (to.to_flag & TOF_CC) { if (taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { /* * update cache and make transition: * SYN-SENT -> ESTABLISHED* * SYN-SENT* -> FIN-WAIT-1* */ taop->tao_cc = to.to_cc; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } tp->t_flags |= TF_NEEDSYN; } else tp->t_state = TCPS_SYN_RECEIVED; } else { /* CC.NEW or no option => invalidate cache */ taop->tao_cc = 0; tp->t_state = TCPS_SYN_RECEIVED; } } trimthenstep6: /* * Advance ti->ti_seq to correspond to first data byte. * If data, trim to stay within window, * dropping FIN if necessary. */ ti->ti_seq++; if (ti->ti_len > tp->rcv_wnd) { todrop = ti->ti_len - tp->rcv_wnd; m_adj(m, -todrop); ti->ti_len = tp->rcv_wnd; tiflags &= ~TH_FIN; tcpstat.tcps_rcvpackafterwin++; tcpstat.tcps_rcvbyteafterwin += todrop; } tp->snd_wl1 = ti->ti_seq - 1; tp->rcv_up = ti->ti_seq; /* * Client side of transaction: already sent SYN and data. * If the remote host used T/TCP to validate the SYN, * our data will be ACK'd; if so, enter normal data segment * processing in the middle of step 5, ack processing. * Otherwise, goto step 6. */ if (tiflags & TH_ACK) goto process_ACK; goto step6; /* * If the state is LAST_ACK or CLOSING or TIME_WAIT: * if segment contains a SYN and CC [not CC.NEW] option: * if state == TIME_WAIT and connection duration > MSL, * drop packet and send RST; * * if SEG.CC > CCrecv then is new SYN, and can implicitly * ack the FIN (and data) in retransmission queue. * Complete close and delete TCPCB. Then reprocess * segment, hoping to find new TCPCB in LISTEN state; * * else must be old SYN; drop it. * else do normal processing. */ case TCPS_LAST_ACK: case TCPS_CLOSING: case TCPS_TIME_WAIT: if ((tiflags & TH_SYN) && (to.to_flag & TOF_CC) && tp->cc_recv != 0) { if (tp->t_state == TCPS_TIME_WAIT && tp->t_duration > TCPTV_MSL) goto dropwithreset; if (CC_GT(to.to_cc, tp->cc_recv)) { tp = tcp_close(tp); goto findpcb; } else goto drop; } break; /* continue normal processing */ } /* * States other than LISTEN or SYN_SENT. * First check timestamp, if present. * Then check the connection count, if present. * Then check that at least some bytes of segment are within * receive window. If segment begins before rcv_nxt, * drop leading data (and SYN); if nothing left, just ack. * * RFC 1323 PAWS: If we have a timestamp reply on this segment * and it's less than ts_recent, drop it. */ if ((to.to_flag & TOF_TS) != 0 && (tiflags & TH_RST) == 0 && tp->ts_recent && TSTMP_LT(to.to_tsval, tp->ts_recent)) { /* Check to see if ts_recent is over 24 days old. */ if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) { /* * Invalidate ts_recent. If this segment updates * ts_recent, the age will be reset later and ts_recent * will get a valid value. If it does not, setting * ts_recent to zero will at least satisfy the * requirement that zero be placed in the timestamp * echo reply when ts_recent isn't valid. The * age isn't reset until we get a valid ts_recent * because we don't want out-of-order segments to be * dropped when ts_recent is old. */ tp->ts_recent = 0; } else { tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += ti->ti_len; tcpstat.tcps_pawsdrop++; goto dropafterack; } } /* * T/TCP mechanism * If T/TCP was negotiated and the segment doesn't have CC, * or if its CC is wrong then drop the segment. * RST segments do not have to comply with this. */ if ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) == (TF_REQ_CC|TF_RCVD_CC) && ((to.to_flag & TOF_CC) == 0 || tp->cc_recv != to.to_cc) && (tiflags & TH_RST) == 0) goto dropafterack; todrop = tp->rcv_nxt - ti->ti_seq; if (todrop > 0) { if (tiflags & TH_SYN) { tiflags &= ~TH_SYN; ti->ti_seq++; if (ti->ti_urp > 1) ti->ti_urp--; else tiflags &= ~TH_URG; todrop--; } /* * Following if statement from Stevens, vol. 2, p. 960. */ if (todrop > ti->ti_len || (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { /* * Any valid FIN must be to the left of the window. * At this point the FIN must be a duplicate or out * of sequence; drop it. */ tiflags &= ~TH_FIN; /* * Send an ACK to resynchronize and drop any data. * But keep on processing for RST or ACK. */ tp->t_flags |= TF_ACKNOW; todrop = ti->ti_len; tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += todrop; } else { tcpstat.tcps_rcvpartduppack++; tcpstat.tcps_rcvpartdupbyte += todrop; } m_adj(m, todrop); ti->ti_seq += todrop; ti->ti_len -= todrop; if (ti->ti_urp > todrop) ti->ti_urp -= todrop; else { tiflags &= ~TH_URG; ti->ti_urp = 0; } } /* * If new data are received on a connection after the * user processes are gone, then RST the other end. */ if ((so->so_state & SS_NOFDREF) && tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) { tp = tcp_close(tp); tcpstat.tcps_rcvafterclose++; goto dropwithreset; } /* * If segment ends after window, drop trailing data * (and PUSH and FIN); if nothing left, just ACK. */ todrop = (ti->ti_seq+ti->ti_len) - (tp->rcv_nxt+tp->rcv_wnd); if (todrop > 0) { tcpstat.tcps_rcvpackafterwin++; if (todrop >= ti->ti_len) { tcpstat.tcps_rcvbyteafterwin += ti->ti_len; /* * If a new connection request is received * while in TIME_WAIT, drop the old connection * and start over if the sequence numbers * are above the previous ones. */ if (tiflags & TH_SYN && tp->t_state == TCPS_TIME_WAIT && SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { iss = tp->rcv_nxt + TCP_ISSINCR; tp = tcp_close(tp); goto findpcb; } /* * If window is closed can only take segments at * window edge, and have to drop data and PUSH from * incoming segments. Continue processing, but * remember to ack. Otherwise, drop segment * and ack. */ if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { tp->t_flags |= TF_ACKNOW; tcpstat.tcps_rcvwinprobe++; } else goto dropafterack; } else tcpstat.tcps_rcvbyteafterwin += todrop; m_adj(m, -todrop); ti->ti_len -= todrop; tiflags &= ~(TH_PUSH|TH_FIN); } /* * If last ACK falls within this segment's sequence numbers, * record its timestamp. * NOTE that the test is modified according to the latest * proposal of the tcplw@cray.com list (Braden 1993/04/26). */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { tp->ts_recent_age = tcp_now; tp->ts_recent = to.to_tsval; } /* * If the RST bit is set examine the state: * SYN_RECEIVED STATE: * If passive open, return to LISTEN state. * If active open, inform user that connection was refused. * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: * Inform user that connection was reset, and close tcb. * CLOSING, LAST_ACK, TIME_WAIT STATES * Close the tcb. */ if (tiflags&TH_RST) switch (tp->t_state) { case TCPS_SYN_RECEIVED: so->so_error = ECONNREFUSED; goto close; case TCPS_ESTABLISHED: case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: case TCPS_CLOSE_WAIT: so->so_error = ECONNRESET; close: tp->t_state = TCPS_CLOSED; tcpstat.tcps_drops++; tp = tcp_close(tp); goto drop; case TCPS_CLOSING: case TCPS_LAST_ACK: case TCPS_TIME_WAIT: tp = tcp_close(tp); goto drop; } /* * If a SYN is in the window, then this is an * error and we send an RST and drop the connection. */ if (tiflags & TH_SYN) { tp = tcp_drop(tp, ECONNRESET); goto dropwithreset; } /* * If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN * flag is on (half-synchronized state), then queue data for * later processing; else drop segment and return. */ if ((tiflags & TH_ACK) == 0) { if (tp->t_state == TCPS_SYN_RECEIVED || (tp->t_flags & TF_NEEDSYN)) goto step6; else goto drop; } /* * Ack processing. */ switch (tp->t_state) { /* * In SYN_RECEIVED state, the ack ACKs our SYN, so enter * ESTABLISHED state and continue processing. * The ACK was checked above. */ case TCPS_SYN_RECEIVED: tcpstat.tcps_connects++; soisconnected(so); /* Do window scaling? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } /* * Upon successful completion of 3-way handshake, * update cache.CC if it was undefined, pass any queued * data to the user, and advance state appropriately. */ if ((taop = tcp_gettaocache(inp)) != NULL && taop->tao_cc == 0) taop->tao_cc = tp->cc_recv; /* * Make transitions: * SYN-RECEIVED -> ESTABLISHED * SYN-RECEIVED* -> FIN-WAIT-1 */ if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } /* * If segment contains data or ACK, will call tcp_reass() * later; if not, do so now to pass queued data to user. */ if (ti->ti_len == 0 && (tiflags & TH_FIN) == 0) (void) tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); tp->snd_wl1 = ti->ti_seq - 1; /* fall into ... */ /* * In ESTABLISHED state: drop duplicate ACKs; ACK out of range * ACKs. If the ack is in the range * tp->snd_una < ti->ti_ack <= tp->snd_max * then advance tp->snd_una to ti->ti_ack and drop * data from the retransmission queue. If this ACK reflects * more up to date window information we update our window information. */ case TCPS_ESTABLISHED: case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: case TCPS_CLOSE_WAIT: case TCPS_CLOSING: case TCPS_LAST_ACK: case TCPS_TIME_WAIT: if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { tcpstat.tcps_rcvdupack++; /* * If we have outstanding data (other than * a window probe), this is a completely * duplicate ack (ie, window info didn't * change), the ack is the biggest we've * seen and we've seen exactly our rexmt * threshhold of them, assume a packet * has been dropped and retransmit it. * Kludge snd_nxt & the congestion * window so we send only this one * packet. * * We know we're losing at the current * window size so do congestion avoidance * (set ssthresh to half the current window * and pull our congestion window back to * the new ssthresh). * * Dup acks mean that packets have left the * network (they're now cached at the receiver) * so bump cwnd by the amount in the receiver * to keep a constant cwnd packets in the * network. */ if (tp->t_timer[TCPT_REXMT] == 0 || ti->ti_ack != tp->snd_una) tp->t_dupacks = 0; else if (++tp->t_dupacks == tcprexmtthresh) { tcp_seq onxt = tp->snd_nxt; u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; if (win < 2) win = 2; tp->snd_ssthresh = win * tp->t_maxseg; tp->t_timer[TCPT_REXMT] = 0; tp->t_rtt = 0; tp->snd_nxt = ti->ti_ack; tp->snd_cwnd = tp->t_maxseg; (void) tcp_output(tp); tp->snd_cwnd = tp->snd_ssthresh + tp->t_maxseg * tp->t_dupacks; if (SEQ_GT(onxt, tp->snd_nxt)) tp->snd_nxt = onxt; goto drop; } else if (tp->t_dupacks > tcprexmtthresh) { tp->snd_cwnd += tp->t_maxseg; (void) tcp_output(tp); goto drop; } } else tp->t_dupacks = 0; break; } /* * If the congestion window was inflated to account * for the other side's cached packets, retract it. */ if (tp->t_dupacks >= tcprexmtthresh && tp->snd_cwnd > tp->snd_ssthresh) tp->snd_cwnd = tp->snd_ssthresh; tp->t_dupacks = 0; if (SEQ_GT(ti->ti_ack, tp->snd_max)) { tcpstat.tcps_rcvacktoomuch++; goto dropafterack; } /* * If we reach this point, ACK is not a duplicate, * i.e., it ACKs something we sent. */ if (tp->t_flags & TF_NEEDSYN) { /* * T/TCP: Connection was half-synchronized, and our * SYN has been ACK'd (so connection is now fully * synchronized). Go to non-starred state, * increment snd_una for ACK of SYN, and check if * we can do window scaling. */ tp->t_flags &= ~TF_NEEDSYN; tp->snd_una++; /* Do window scaling? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } } process_ACK: acked = ti->ti_ack - tp->snd_una; tcpstat.tcps_rcvackpack++; tcpstat.tcps_rcvackbyte += acked; /* * If we have a timestamp reply, update smoothed * round trip time. If no timestamp is present but * transmit timer is running and timed sequence * number was acked, update smoothed round trip time. * Since we now have an rtt measurement, cancel the * timer backoff (cf., Phil Karn's retransmit alg.). * Recompute the initial retransmit timer. */ if (to.to_flag & TOF_TS) tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 1); else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) tcp_xmit_timer(tp,tp->t_rtt); /* * If all outstanding data is acked, stop retransmit * timer and remember to restart (more output or persist). * If there is more data to be acked, restart retransmit * timer, using current (possibly backed-off) value. */ if (ti->ti_ack == tp->snd_max) { tp->t_timer[TCPT_REXMT] = 0; needoutput = 1; } else if (tp->t_timer[TCPT_PERSIST] == 0) tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; /* * If no data (only SYN) was ACK'd, * skip rest of ACK processing. */ if (acked == 0) goto step6; /* * When new data is acked, open the congestion window. * If the window gives us less than ssthresh packets * in flight, open exponentially (maxseg per packet). * Otherwise open linearly: maxseg per window * (maxseg^2 / cwnd per packet). */ { register u_int cw = tp->snd_cwnd; register u_int incr = tp->t_maxseg; if (cw > tp->snd_ssthresh) incr = incr * incr / cw; tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<snd_scale); } if (acked > so->so_snd.sb_cc) { tp->snd_wnd -= so->so_snd.sb_cc; sbdrop(&so->so_snd, (int)so->so_snd.sb_cc); ourfinisacked = 1; } else { sbdrop(&so->so_snd, acked); tp->snd_wnd -= acked; ourfinisacked = 0; } sowwakeup(so); tp->snd_una = ti->ti_ack; if (SEQ_LT(tp->snd_nxt, tp->snd_una)) tp->snd_nxt = tp->snd_una; switch (tp->t_state) { /* * In FIN_WAIT_1 STATE in addition to the processing * for the ESTABLISHED state if our FIN is now acknowledged * then enter FIN_WAIT_2. */ case TCPS_FIN_WAIT_1: if (ourfinisacked) { /* * If we can't receive any more * data, then closing user can proceed. * Starting the timer is contrary to the * specification, but if we don't get a FIN * we'll hang forever. */ if (so->so_state & SS_CANTRCVMORE) { soisdisconnected(so); tp->t_timer[TCPT_2MSL] = tcp_maxidle; } tp->t_state = TCPS_FIN_WAIT_2; } break; /* * In CLOSING STATE in addition to the processing for * the ESTABLISHED state if the ACK acknowledges our FIN * then enter the TIME-WAIT state, otherwise ignore * the segment. */ case TCPS_CLOSING: if (ourfinisacked) { tp->t_state = TCPS_TIME_WAIT; tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && tp->t_duration < TCPTV_MSL) tp->t_timer[TCPT_2MSL] = tp->t_rxtcur * TCPTV_TWTRUNC; else tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; soisdisconnected(so); } break; /* * In LAST_ACK, we may still be waiting for data to drain * and/or to be acked, as well as for the ack of our FIN. * If our FIN is now acknowledged, delete the TCB, * enter the closed state and return. */ case TCPS_LAST_ACK: if (ourfinisacked) { tp = tcp_close(tp); goto drop; } break; /* * In TIME_WAIT state the only thing that should arrive * is a retransmission of the remote FIN. Acknowledge * it and restart the finack timer. */ case TCPS_TIME_WAIT: tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; goto dropafterack; } } step6: /* * Update window information. * Don't look at window if no ACK: TAC's send garbage on first SYN. */ if ((tiflags & TH_ACK) && (SEQ_LT(tp->snd_wl1, ti->ti_seq) || (tp->snd_wl1 == ti->ti_seq && (SEQ_LT(tp->snd_wl2, ti->ti_ack) || (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))))) { /* keep track of pure window updates */ if (ti->ti_len == 0 && tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd) tcpstat.tcps_rcvwinupd++; tp->snd_wnd = tiwin; tp->snd_wl1 = ti->ti_seq; tp->snd_wl2 = ti->ti_ack; if (tp->snd_wnd > tp->max_sndwnd) tp->max_sndwnd = tp->snd_wnd; needoutput = 1; } /* * Process segments with URG. */ if ((tiflags & TH_URG) && ti->ti_urp && TCPS_HAVERCVDFIN(tp->t_state) == 0) { /* * This is a kludge, but if we receive and accept * random urgent pointers, we'll crash in * soreceive. It's hard to imagine someone * actually wanting to send this much urgent data. */ if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) { ti->ti_urp = 0; /* XXX */ tiflags &= ~TH_URG; /* XXX */ goto dodata; /* XXX */ } /* * If this segment advances the known urgent pointer, * then mark the data stream. This should not happen * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since * a FIN has been received from the remote side. * In these states we ignore the URG. * * According to RFC961 (Assigned Protocols), * the urgent pointer points to the last octet * of urgent data. We continue, however, * to consider it to indicate the first octet * of data past the urgent section as the original * spec states (in one of two places). */ if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) { tp->rcv_up = ti->ti_seq + ti->ti_urp; so->so_oobmark = so->so_rcv.sb_cc + (tp->rcv_up - tp->rcv_nxt) - 1; if (so->so_oobmark == 0) so->so_state |= SS_RCVATMARK; sohasoutofband(so); tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA); } /* * Remove out of band data so doesn't get presented to user. * This can happen independent of advancing the URG pointer, * but if two URG's are pending at once, some out-of-band * data may creep in... ick. */ if (ti->ti_urp <= (u_long)ti->ti_len #ifdef SO_OOBINLINE && (so->so_options & SO_OOBINLINE) == 0 #endif ) tcp_pulloutofband(so, ti, m); } else /* * If no out of band data is expected, * pull receive urgent pointer along * with the receive window. */ if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) tp->rcv_up = tp->rcv_nxt; dodata: /* XXX */ /* * Process the segment text, merging it into the TCP sequencing queue, * and arranging for acknowledgment of receipt if necessary. * This process logically involves adjusting tp->rcv_wnd as data * is presented to the user (this happens in tcp_usrreq.c, * case PRU_RCVD). If a FIN has already been received on this * connection then we just ignore the text. */ if ((ti->ti_len || (tiflags&TH_FIN)) && TCPS_HAVERCVDFIN(tp->t_state) == 0) { TCP_REASS(tp, ti, m, so, tiflags); /* * Note the amount of data that peer has sent into * our window, in order to estimate the sender's * buffer size. */ len = so->so_rcv.sb_hiwat - (tp->rcv_adv - tp->rcv_nxt); } else { m_freem(m); tiflags &= ~TH_FIN; } /* * If FIN is received ACK the FIN and let the user know * that the connection is closing. */ if (tiflags & TH_FIN) { if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { socantrcvmore(so); /* * If connection is half-synchronized * (ie NEEDSYN flag on) then delay ACK, * so it may be piggybacked when SYN is sent. * Otherwise, since we received a FIN then no * more input can be expected, send ACK now. */ if (tcp_delack_enabled && (tp->t_flags & TF_NEEDSYN)) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; tp->rcv_nxt++; } switch (tp->t_state) { /* * In SYN_RECEIVED and ESTABLISHED STATES * enter the CLOSE_WAIT state. */ case TCPS_SYN_RECEIVED: case TCPS_ESTABLISHED: tp->t_state = TCPS_CLOSE_WAIT; break; /* * If still in FIN_WAIT_1 STATE FIN has not been acked so * enter the CLOSING state. */ case TCPS_FIN_WAIT_1: tp->t_state = TCPS_CLOSING; break; /* * In FIN_WAIT_2 state enter the TIME_WAIT state, * starting the time-wait timer, turning off the other * standard timers. */ case TCPS_FIN_WAIT_2: tp->t_state = TCPS_TIME_WAIT; tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && tp->t_duration < TCPTV_MSL) { tp->t_timer[TCPT_2MSL] = tp->t_rxtcur * TCPTV_TWTRUNC; /* For transaction client, force ACK now. */ tp->t_flags |= TF_ACKNOW; } else tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; soisdisconnected(so); break; /* * In TIME_WAIT state restart the 2 MSL time_wait timer. */ case TCPS_TIME_WAIT: tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; break; } } #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) tcp_trace(TA_INPUT, ostate, tp, &tcp_saveti, 0); #endif /* * Return any desired output. */ if (needoutput || (tp->t_flags & TF_ACKNOW)) (void) tcp_output(tp); return; dropafterack: /* * Generate an ACK dropping incoming segment if it occupies * sequence space, where the ACK reflects our state. */ if (tiflags & TH_RST) goto drop; #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif m_freem(m); tp->t_flags |= TF_ACKNOW; (void) tcp_output(tp); return; dropwithreset: /* * Generate a RST, dropping incoming segment. * Make ACK acceptable to originator of segment. * Don't bother to respond if destination was broadcast/multicast. */ if ((tiflags & TH_RST) || m->m_flags & (M_BCAST|M_MCAST) || IN_MULTICAST(ntohl(ti->ti_dst.s_addr))) goto drop; #ifdef TCPDEBUG if (tp == 0 || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif if (tiflags & TH_ACK) tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST); else { if (tiflags & TH_SYN) ti->ti_len++; tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0, TH_RST|TH_ACK); } /* destroy temporarily created socket */ if (dropsocket) (void) soabort(so); return; drop: /* * Drop space held by incoming segment and return. */ #ifdef TCPDEBUG if (tp == 0 || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif m_freem(m); /* destroy temporarily created socket */ if (dropsocket) (void) soabort(so); return; } static void tcp_dooptions(tp, cp, cnt, ti, to) struct tcpcb *tp; u_char *cp; int cnt; struct tcpiphdr *ti; struct tcpopt *to; { u_short mss = 0; int opt, optlen; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[0]; if (opt == TCPOPT_EOL) break; if (opt == TCPOPT_NOP) optlen = 1; else { optlen = cp[1]; if (optlen <= 0) break; } switch (opt) { default: continue; case TCPOPT_MAXSEG: if (optlen != TCPOLEN_MAXSEG) continue; if (!(ti->ti_flags & TH_SYN)) continue; bcopy((char *) cp + 2, (char *) &mss, sizeof(mss)); NTOHS(mss); break; case TCPOPT_WINDOW: if (optlen != TCPOLEN_WINDOW) continue; if (!(ti->ti_flags & TH_SYN)) continue; tp->t_flags |= TF_RCVD_SCALE; tp->requested_s_scale = min(cp[2], TCP_MAX_WINSHIFT); break; case TCPOPT_TIMESTAMP: if (optlen != TCPOLEN_TIMESTAMP) continue; to->to_flag |= TOF_TS; bcopy((char *)cp + 2, (char *)&to->to_tsval, sizeof(to->to_tsval)); NTOHL(to->to_tsval); bcopy((char *)cp + 6, (char *)&to->to_tsecr, sizeof(to->to_tsecr)); NTOHL(to->to_tsecr); /* * A timestamp received in a SYN makes * it ok to send timestamp requests and replies. */ if (ti->ti_flags & TH_SYN) { tp->t_flags |= TF_RCVD_TSTMP; tp->ts_recent = to->to_tsval; tp->ts_recent_age = tcp_now; } break; case TCPOPT_CC: if (optlen != TCPOLEN_CC) continue; to->to_flag |= TOF_CC; bcopy((char *)cp + 2, (char *)&to->to_cc, sizeof(to->to_cc)); NTOHL(to->to_cc); /* * A CC or CC.new option received in a SYN makes * it ok to send CC in subsequent segments. */ if (ti->ti_flags & TH_SYN) tp->t_flags |= TF_RCVD_CC; break; case TCPOPT_CCNEW: if (optlen != TCPOLEN_CC) continue; if (!(ti->ti_flags & TH_SYN)) continue; to->to_flag |= TOF_CCNEW; bcopy((char *)cp + 2, (char *)&to->to_cc, sizeof(to->to_cc)); NTOHL(to->to_cc); /* * A CC or CC.new option received in a SYN makes * it ok to send CC in subsequent segments. */ tp->t_flags |= TF_RCVD_CC; break; case TCPOPT_CCECHO: if (optlen != TCPOLEN_CC) continue; if (!(ti->ti_flags & TH_SYN)) continue; to->to_flag |= TOF_CCECHO; bcopy((char *)cp + 2, (char *)&to->to_ccecho, sizeof(to->to_ccecho)); NTOHL(to->to_ccecho); break; } } if (ti->ti_flags & TH_SYN) tcp_mss(tp, mss); /* sets t_maxseg */ } /* * Pull out of band byte out of a segment so * it doesn't appear in the user's data queue. * It is still reflected in the segment length for * sequencing purposes. */ static void tcp_pulloutofband(so, ti, m) struct socket *so; struct tcpiphdr *ti; register struct mbuf *m; { int cnt = ti->ti_urp - 1; while (cnt >= 0) { if (m->m_len > cnt) { char *cp = mtod(m, caddr_t) + cnt; struct tcpcb *tp = sototcpcb(so); tp->t_iobc = *cp; tp->t_oobflags |= TCPOOB_HAVEDATA; bcopy(cp+1, cp, (unsigned)(m->m_len - cnt - 1)); m->m_len--; return; } cnt -= m->m_len; m = m->m_next; if (m == 0) break; } panic("tcp_pulloutofband"); } /* * Collect new round-trip time estimate * and update averages and current timeout. */ static void tcp_xmit_timer(tp, rtt) register struct tcpcb *tp; short rtt; { register int delta; tcpstat.tcps_rttupdated++; tp->t_rttupdated++; if (tp->t_srtt != 0) { /* * srtt is stored as fixed point with 5 bits after the * binary point (i.e., scaled by 8). The following magic * is equivalent to the smoothing algorithm in rfc793 with * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed * point). Adjust rtt to origin 0. */ delta = ((rtt - 1) << TCP_DELTA_SHIFT) - (tp->t_srtt >> (TCP_RTT_SHIFT - TCP_DELTA_SHIFT)); if ((tp->t_srtt += delta) <= 0) tp->t_srtt = 1; /* * We accumulate a smoothed rtt variance (actually, a * smoothed mean difference), then set the retransmit * timer to smoothed rtt + 4 times the smoothed variance. * rttvar is stored as fixed point with 4 bits after the * binary point (scaled by 16). The following is * equivalent to rfc793 smoothing with an alpha of .75 * (rttvar = rttvar*3/4 + |delta| / 4). This replaces * rfc793's wired-in beta. */ if (delta < 0) delta = -delta; delta -= tp->t_rttvar >> (TCP_RTTVAR_SHIFT - TCP_DELTA_SHIFT); if ((tp->t_rttvar += delta) <= 0) tp->t_rttvar = 1; } else { /* * No rtt measurement yet - use the unsmoothed rtt. * Set the variance to half the rtt (so our first * retransmit happens at 3*rtt). */ tp->t_srtt = rtt << TCP_RTT_SHIFT; tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); } tp->t_rtt = 0; tp->t_rxtshift = 0; /* * the retransmit should happen at rtt + 4 * rttvar. * Because of the way we do the smoothing, srtt and rttvar * will each average +1/2 tick of bias. When we compute * the retransmit timer, we want 1/2 tick of rounding and * 1 extra tick because of +-1/2 tick uncertainty in the * firing of the timer. The bias will give us exactly the * 1.5 tick we need. But, because the bias is * statistical, we have to test that we don't drop below * the minimum feasible timer (which is 2 ticks). */ TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), max(tp->t_rttmin, rtt + 2), TCPTV_REXMTMAX); /* * We received an ack for a packet that wasn't retransmitted; * it is probably safe to discard any error indications we've * received recently. This isn't quite right, but close enough * for now (a route might have failed after we sent a segment, * and the return path might not be symmetrical). */ tp->t_softerror = 0; } /* * Determine a reasonable value for maxseg size. * If the route is known, check route for mtu. * If none, use an mss that can be handled on the outgoing * interface without forcing IP to fragment; if bigger than * an mbuf cluster (MCLBYTES), round down to nearest multiple of MCLBYTES * to utilize large mbufs. If no route is found, route has no mtu, * or the destination isn't local, use a default, hopefully conservative * size (usually 512 or the default IP max size, but no more than the mtu * of the interface), as we can't discover anything about intervening * gateways or networks. We also initialize the congestion/slow start * window to be a single segment if the destination isn't local. * While looking at the routing entry, we also initialize other path-dependent * parameters from pre-set or cached values in the routing entry. * * Also take into account the space needed for options that we * send regularly. Make maxseg shorter by that amount to assure * that we can send maxseg amount of data even when the options * are present. Store the upper limit of the length of options plus * data in maxopd. * * NOTE that this routine is only called when we process an incoming * segment, for outgoing segments only tcp_mssopt is called. * * In case of T/TCP, we call this routine during implicit connection * setup as well (offer = -1), to initialize maxseg from the cached * MSS of our peer. */ void tcp_mss(tp, offer) struct tcpcb *tp; int offer; { register struct rtentry *rt; struct ifnet *ifp; register int rtt, mss; u_long bufsize; struct inpcb *inp; struct socket *so; struct rmxp_tao *taop; int origoffer = offer; inp = tp->t_inpcb; if ((rt = tcp_rtlookup(inp)) == NULL) { tp->t_maxopd = tp->t_maxseg = tcp_mssdflt; return; } ifp = rt->rt_ifp; so = inp->inp_socket; taop = rmx_taop(rt->rt_rmx); /* * Offer == -1 means that we didn't receive SYN yet, * use cached value in that case; */ if (offer == -1) offer = taop->tao_mssopt; /* * Offer == 0 means that there was no MSS on the SYN segment, * in this case we use tcp_mssdflt. */ if (offer == 0) offer = tcp_mssdflt; else /* * Sanity check: make sure that maxopd will be large * enough to allow some data on segments even is the * all the option space is used (40bytes). Otherwise * funny things may happen in tcp_output. */ offer = max(offer, 64); taop->tao_mssopt = offer; /* * While we're here, check if there's an initial rtt * or rttvar. Convert from the route-table units * to scaled multiples of the slow timeout timer. */ if (tp->t_srtt == 0 && (rtt = rt->rt_rmx.rmx_rtt)) { /* * XXX the lock bit for RTT indicates that the value * is also a minimum value; this is subject to time. */ if (rt->rt_rmx.rmx_locks & RTV_RTT) tp->t_rttmin = rtt / (RTM_RTTUNIT / PR_SLOWHZ); tp->t_srtt = rtt / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); tcpstat.tcps_usedrtt++; if (rt->rt_rmx.rmx_rttvar) { tp->t_rttvar = rt->rt_rmx.rmx_rttvar / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); tcpstat.tcps_usedrttvar++; } else { /* default variation is +- 1 rtt */ tp->t_rttvar = tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; } TCPT_RANGESET(tp->t_rxtcur, ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, tp->t_rttmin, TCPTV_REXMTMAX); } /* * if there's an mtu associated with the route, use it */ if (rt->rt_rmx.rmx_mtu) mss = rt->rt_rmx.rmx_mtu - sizeof(struct tcpiphdr); else { mss = ifp->if_mtu - sizeof(struct tcpiphdr); if (!in_localaddr(inp->inp_faddr)) mss = min(mss, tcp_mssdflt); } mss = min(mss, offer); /* * maxopd stores the maximum length of data AND options * in a segment; maxseg is the amount of data in a normal * segment. We need to store this value (maxopd) apart * from maxseg, because now every segment carries options * and thus we normally have somewhat less data in segments. */ tp->t_maxopd = mss; /* * In case of T/TCP, origoffer==-1 indicates, that no segments * were received yet. In this case we just guess, otherwise * we do the same as before T/TCP. */ if ((tp->t_flags & (TF_REQ_TSTMP|TF_NOOPT)) == TF_REQ_TSTMP && (origoffer == -1 || (tp->t_flags & TF_RCVD_TSTMP) == TF_RCVD_TSTMP)) mss -= TCPOLEN_TSTAMP_APPA; if ((tp->t_flags & (TF_REQ_CC|TF_NOOPT)) == TF_REQ_CC && (origoffer == -1 || (tp->t_flags & TF_RCVD_CC) == TF_RCVD_CC)) mss -= TCPOLEN_CC_APPA; #if (MCLBYTES & (MCLBYTES - 1)) == 0 if (mss > MCLBYTES) mss &= ~(MCLBYTES-1); #else if (mss > MCLBYTES) mss = mss / MCLBYTES * MCLBYTES; #endif /* * If there's a pipesize, change the socket buffer * to that size. Make the socket buffers an integral * number of mss units; if the mss is larger than * the socket buffer, decrease the mss. */ #ifdef RTV_SPIPE if ((bufsize = rt->rt_rmx.rmx_sendpipe) == 0) #endif bufsize = so->so_snd.sb_hiwat; if (bufsize < mss) mss = bufsize; else { bufsize = roundup(bufsize, mss); if (bufsize > sb_max) bufsize = sb_max; (void)sbreserve(&so->so_snd, bufsize); } tp->t_maxseg = mss; #ifdef RTV_RPIPE if ((bufsize = rt->rt_rmx.rmx_recvpipe) == 0) #endif bufsize = so->so_rcv.sb_hiwat; if (bufsize > mss) { bufsize = roundup(bufsize, mss); if (bufsize > sb_max) bufsize = sb_max; (void)sbreserve(&so->so_rcv, bufsize); } /* * Don't force slow-start on local network. */ if (!in_localaddr(inp->inp_faddr)) tp->snd_cwnd = mss; if (rt->rt_rmx.rmx_ssthresh) { /* * There's some sort of gateway or interface * buffer limit on the path. Use this to set * the slow start threshhold, but set the * threshold to no less than 2*mss. */ tp->snd_ssthresh = max(2 * mss, rt->rt_rmx.rmx_ssthresh); tcpstat.tcps_usedssthresh++; } } /* * Determine the MSS option to send on an outgoing SYN. */ int tcp_mssopt(tp) struct tcpcb *tp; { struct rtentry *rt; rt = tcp_rtlookup(tp->t_inpcb); if (rt == NULL) return tcp_mssdflt; return rt->rt_ifp->if_mtu - sizeof(struct tcpiphdr); } Index: head/sys/netinet/tcp_reass.c =================================================================== --- head/sys/netinet/tcp_reass.c (revision 37408) +++ head/sys/netinet/tcp_reass.c (revision 37409) @@ -1,2116 +1,2144 @@ /* * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1994, 1995 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)tcp_input.c 8.12 (Berkeley) 5/24/95 - * $Id: tcp_input.c,v 1.77 1998/05/18 17:11:24 guido Exp $ + * $Id: tcp_input.c,v 1.78 1998/05/31 18:42:49 peter Exp $ */ +#include "opt_ipfw.h" /* for ipfw_fwd */ #include "opt_tcpdebug.h" #include #include #include #include #include #include #include /* for proc0 declaration */ #include #include #include #include #include /* before tcp_seq.h, for tcp_random18() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TCPDEBUG #include static struct tcpiphdr tcp_saveti; #endif static int tcprexmtthresh = 3; tcp_seq tcp_iss; tcp_cc tcp_ccgen; struct tcpstat tcpstat; SYSCTL_STRUCT(_net_inet_tcp, TCPCTL_STATS, stats, CTLFLAG_RD, &tcpstat , tcpstat, ""); static int log_in_vain = 0; SYSCTL_INT(_net_inet_tcp, OID_AUTO, log_in_vain, CTLFLAG_RW, &log_in_vain, 0, ""); int tcp_delack_enabled = 1; SYSCTL_INT(_net_inet_tcp, OID_AUTO, delayed_ack, CTLFLAG_RW, &tcp_delack_enabled, 0, ""); u_long tcp_now; struct inpcbhead tcb; struct inpcbinfo tcbinfo; static void tcp_dooptions __P((struct tcpcb *, u_char *, int, struct tcpiphdr *, struct tcpopt *)); static void tcp_pulloutofband __P((struct socket *, struct tcpiphdr *, struct mbuf *)); static int tcp_reass __P((struct tcpcb *, struct tcpiphdr *, struct mbuf *)); static void tcp_xmit_timer __P((struct tcpcb *, int)); /* * Insert segment ti into reassembly queue of tcp with * control block tp. Return TH_FIN if reassembly now includes * a segment with FIN. The macro form does the common case inline * (segment is the next to be received on an established connection, * and the queue is empty), avoiding linkage into and removal * from the queue and repetition of various conversions. * Set DELACK for segments received in order, but ack immediately * when segments are out of order (so fast retransmit can work). */ #define TCP_REASS(tp, ti, m, so, flags) { \ if ((ti)->ti_seq == (tp)->rcv_nxt && \ (tp)->seg_next == (struct tcpiphdr *)(tp) && \ (tp)->t_state == TCPS_ESTABLISHED) { \ if (tcp_delack_enabled) \ tp->t_flags |= TF_DELACK; \ else \ tp->t_flags |= TF_ACKNOW; \ (tp)->rcv_nxt += (ti)->ti_len; \ flags = (ti)->ti_flags & TH_FIN; \ tcpstat.tcps_rcvpack++;\ tcpstat.tcps_rcvbyte += (ti)->ti_len;\ sbappend(&(so)->so_rcv, (m)); \ sorwakeup(so); \ } else { \ (flags) = tcp_reass((tp), (ti), (m)); \ tp->t_flags |= TF_ACKNOW; \ } \ } static int tcp_reass(tp, ti, m) register struct tcpcb *tp; register struct tcpiphdr *ti; struct mbuf *m; { register struct tcpiphdr *q; struct socket *so = tp->t_inpcb->inp_socket; int flags; /* * Call with ti==0 after become established to * force pre-ESTABLISHED data up to user socket. */ if (ti == 0) goto present; /* * Find a segment which begins after this one does. */ for (q = tp->seg_next; q != (struct tcpiphdr *)tp; q = (struct tcpiphdr *)q->ti_next) if (SEQ_GT(q->ti_seq, ti->ti_seq)) break; /* * If there is a preceding segment, it may provide some of * our data already. If so, drop the data from the incoming * segment. If it provides all of our data, drop us. */ if ((struct tcpiphdr *)q->ti_prev != (struct tcpiphdr *)tp) { register int i; q = (struct tcpiphdr *)q->ti_prev; /* conversion to int (in i) handles seq wraparound */ i = q->ti_seq + q->ti_len - ti->ti_seq; if (i > 0) { if (i >= ti->ti_len) { tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += ti->ti_len; m_freem(m); /* * Try to present any queued data * at the left window edge to the user. * This is needed after the 3-WHS * completes. */ goto present; /* ??? */ } m_adj(m, i); ti->ti_len -= i; ti->ti_seq += i; } q = (struct tcpiphdr *)(q->ti_next); } tcpstat.tcps_rcvoopack++; tcpstat.tcps_rcvoobyte += ti->ti_len; REASS_MBUF(ti) = m; /* XXX */ /* * While we overlap succeeding segments trim them or, * if they are completely covered, dequeue them. */ while (q != (struct tcpiphdr *)tp) { register int i = (ti->ti_seq + ti->ti_len) - q->ti_seq; if (i <= 0) break; if (i < q->ti_len) { q->ti_seq += i; q->ti_len -= i; m_adj(REASS_MBUF(q), i); break; } q = (struct tcpiphdr *)q->ti_next; m = REASS_MBUF((struct tcpiphdr *)q->ti_prev); remque(q->ti_prev); m_freem(m); } /* * Stick new segment in its place. */ insque(ti, q->ti_prev); present: /* * Present data to user, advancing rcv_nxt through * completed sequence space. */ if (!TCPS_HAVEESTABLISHED(tp->t_state)) return (0); ti = tp->seg_next; if (ti == (struct tcpiphdr *)tp || ti->ti_seq != tp->rcv_nxt) return (0); do { tp->rcv_nxt += ti->ti_len; flags = ti->ti_flags & TH_FIN; remque(ti); m = REASS_MBUF(ti); ti = (struct tcpiphdr *)ti->ti_next; if (so->so_state & SS_CANTRCVMORE) m_freem(m); else sbappend(&so->so_rcv, m); } while (ti != (struct tcpiphdr *)tp && ti->ti_seq == tp->rcv_nxt); sorwakeup(so); return (flags); } /* * TCP input routine, follows pages 65-76 of the * protocol specification dated September, 1981 very closely. */ void tcp_input(m, iphlen) register struct mbuf *m; int iphlen; { register struct tcpiphdr *ti; register struct inpcb *inp; u_char *optp = NULL; int optlen = 0; int len, tlen, off; register struct tcpcb *tp = 0; register int tiflags; struct socket *so = 0; int todrop, acked, ourfinisacked, needoutput = 0; struct in_addr laddr; int dropsocket = 0; int iss = 0; u_long tiwin; struct tcpopt to; /* options in this segment */ struct rmxp_tao *taop; /* pointer to our TAO cache entry */ struct rmxp_tao tao_noncached; /* in case there's no cached entry */ #ifdef TCPDEBUG short ostate = 0; #endif bzero((char *)&to, sizeof(to)); tcpstat.tcps_rcvtotal++; /* * Get IP and TCP header together in first mbuf. * Note: IP leaves IP header in first mbuf. */ ti = mtod(m, struct tcpiphdr *); if (iphlen > sizeof (struct ip)) ip_stripoptions(m, (struct mbuf *)0); if (m->m_len < sizeof (struct tcpiphdr)) { if ((m = m_pullup(m, sizeof (struct tcpiphdr))) == 0) { tcpstat.tcps_rcvshort++; return; } ti = mtod(m, struct tcpiphdr *); } /* * Checksum extended TCP header and data. */ tlen = ((struct ip *)ti)->ip_len; len = sizeof (struct ip) + tlen; ti->ti_next = ti->ti_prev = 0; ti->ti_x1 = 0; ti->ti_len = (u_short)tlen; HTONS(ti->ti_len); ti->ti_sum = in_cksum(m, len); if (ti->ti_sum) { tcpstat.tcps_rcvbadsum++; goto drop; } /* * Check that TCP offset makes sense, * pull out TCP options and adjust length. XXX */ off = ti->ti_off << 2; if (off < sizeof (struct tcphdr) || off > tlen) { tcpstat.tcps_rcvbadoff++; goto drop; } tlen -= off; ti->ti_len = tlen; if (off > sizeof (struct tcphdr)) { if (m->m_len < sizeof(struct ip) + off) { if ((m = m_pullup(m, sizeof (struct ip) + off)) == 0) { tcpstat.tcps_rcvshort++; return; } ti = mtod(m, struct tcpiphdr *); } optlen = off - sizeof (struct tcphdr); optp = mtod(m, u_char *) + sizeof (struct tcpiphdr); } tiflags = ti->ti_flags; /* * Convert TCP protocol specific fields to host format. */ NTOHL(ti->ti_seq); NTOHL(ti->ti_ack); NTOHS(ti->ti_win); NTOHS(ti->ti_urp); /* * Drop TCP, IP headers and TCP options. */ m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr); /* * Locate pcb for segment. */ findpcb: +#ifdef IPFIREWALL_FORWARD + if (ip_fw_fwd_addr != NULL) { + /* + * Diverted. Pretend to be the destination. + * already got one like this? + */ + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, ti->ti_sport, + ti->ti_dst, ti->ti_dport, 0); + if (!inp) { + /* + * No, then it's new. Try find the ambushing socket + */ + if (!ip_fw_fwd_addr->sin_port) { + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, + ti->ti_sport, ip_fw_fwd_addr->sin_addr, + ti->ti_dport, 1); + } else { + inp = in_pcblookup_hash(&tcbinfo, + ti->ti_src, ti->ti_sport, + ip_fw_fwd_addr->sin_addr, + ntohs(ip_fw_fwd_addr->sin_port), 1); + } + } + ip_fw_fwd_addr = NULL; + } else +#endif /* IPFIREWALL_FORWARD */ + inp = in_pcblookup_hash(&tcbinfo, ti->ti_src, ti->ti_sport, ti->ti_dst, ti->ti_dport, 1); /* * If the state is CLOSED (i.e., TCB does not exist) then * all data in the incoming segment is discarded. * If the TCB exists but is in CLOSED state, it is embryonic, * but should either do a listen or a connect soon. */ if (inp == NULL) { if (log_in_vain && tiflags & TH_SYN) { char buf[4*sizeof "123"]; strcpy(buf, inet_ntoa(ti->ti_dst)); log(LOG_INFO, "Connection attempt to TCP %s:%d from %s:%d\n", buf, ntohs(ti->ti_dport), inet_ntoa(ti->ti_src), ntohs(ti->ti_sport)); } goto dropwithreset; } tp = intotcpcb(inp); if (tp == 0) goto dropwithreset; if (tp->t_state == TCPS_CLOSED) goto drop; /* Unscale the window into a 32-bit value. */ if ((tiflags & TH_SYN) == 0) tiwin = ti->ti_win << tp->snd_scale; else tiwin = ti->ti_win; so = inp->inp_socket; if (so->so_options & (SO_DEBUG|SO_ACCEPTCONN)) { #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) { ostate = tp->t_state; tcp_saveti = *ti; } #endif if (so->so_options & SO_ACCEPTCONN) { register struct tcpcb *tp0 = tp; struct socket *so2; if ((tiflags & (TH_RST|TH_ACK|TH_SYN)) != TH_SYN) { /* * Note: dropwithreset makes sure we don't * send a RST in response to a RST. */ if (tiflags & TH_ACK) { tcpstat.tcps_badsyn++; goto dropwithreset; } goto drop; } so2 = sonewconn(so, 0); if (so2 == 0) { tcpstat.tcps_listendrop++; so2 = sodropablereq(so); if (so2) { tcp_drop(sototcpcb(so2), ETIMEDOUT); so2 = sonewconn(so, 0); } if (!so2) goto drop; } so = so2; /* * This is ugly, but .... * * Mark socket as temporary until we're * committed to keeping it. The code at * ``drop'' and ``dropwithreset'' check the * flag dropsocket to see if the temporary * socket created here should be discarded. * We mark the socket as discardable until * we're committed to it below in TCPS_LISTEN. */ dropsocket++; inp = (struct inpcb *)so->so_pcb; inp->inp_laddr = ti->ti_dst; inp->inp_lport = ti->ti_dport; if (in_pcbinshash(inp) != 0) { /* * Undo the assignments above if we failed to put * the PCB on the hash lists. */ inp->inp_laddr.s_addr = INADDR_ANY; inp->inp_lport = 0; goto drop; } inp->inp_options = ip_srcroute(); tp = intotcpcb(inp); tp->t_state = TCPS_LISTEN; tp->t_flags |= tp0->t_flags & (TF_NOPUSH|TF_NOOPT); /* Compute proper scaling value from buffer space */ while (tp->request_r_scale < TCP_MAX_WINSHIFT && TCP_MAXWIN << tp->request_r_scale < so->so_rcv.sb_hiwat) tp->request_r_scale++; } } /* * Segment received on connection. * Reset idle time and keep-alive timer. */ tp->t_idle = 0; if (TCPS_HAVEESTABLISHED(tp->t_state)) tp->t_timer[TCPT_KEEP] = tcp_keepidle; /* * Process options if not in LISTEN state, * else do it below (after getting remote address). */ if (tp->t_state != TCPS_LISTEN) tcp_dooptions(tp, optp, optlen, ti, &to); /* * Header prediction: check for the two common cases * of a uni-directional data xfer. If the packet has * no control flags, is in-sequence, the window didn't * change and we're not retransmitting, it's a * candidate. If the length is zero and the ack moved * forward, we're the sender side of the xfer. Just * free the data acked & wake any higher level process * that was blocked waiting for space. If the length * is non-zero and the ack didn't move, we're the * receiver side. If we're getting packets in-order * (the reassembly queue is empty), add the data to * the socket buffer and note that we need a delayed ack. * Make sure that the hidden state-flags are also off. * Since we check for TCPS_ESTABLISHED above, it can only * be TH_NEEDSYN. */ if (tp->t_state == TCPS_ESTABLISHED && (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK && ((tp->t_flags & (TF_NEEDSYN|TF_NEEDFIN)) == 0) && ((to.to_flag & TOF_TS) == 0 || TSTMP_GEQ(to.to_tsval, tp->ts_recent)) && /* * Using the CC option is compulsory if once started: * the segment is OK if no T/TCP was negotiated or * if the segment has a CC option equal to CCrecv */ ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) != (TF_REQ_CC|TF_RCVD_CC) || (to.to_flag & TOF_CC) != 0 && to.to_cc == tp->cc_recv) && ti->ti_seq == tp->rcv_nxt && tiwin && tiwin == tp->snd_wnd && tp->snd_nxt == tp->snd_max) { /* * If last ACK falls within this segment's sequence numbers, * record the timestamp. * NOTE that the test is modified according to the latest * proposal of the tcplw@cray.com list (Braden 1993/04/26). */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { tp->ts_recent_age = tcp_now; tp->ts_recent = to.to_tsval; } if (ti->ti_len == 0) { if (SEQ_GT(ti->ti_ack, tp->snd_una) && SEQ_LEQ(ti->ti_ack, tp->snd_max) && tp->snd_cwnd >= tp->snd_wnd && tp->t_dupacks < tcprexmtthresh) { /* * this is a pure ack for outstanding data. */ ++tcpstat.tcps_predack; if ((to.to_flag & TOF_TS) != 0) tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 1); else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) tcp_xmit_timer(tp, tp->t_rtt); acked = ti->ti_ack - tp->snd_una; tcpstat.tcps_rcvackpack++; tcpstat.tcps_rcvackbyte += acked; sbdrop(&so->so_snd, acked); tp->snd_una = ti->ti_ack; m_freem(m); /* * If all outstanding data are acked, stop * retransmit timer, otherwise restart timer * using current (possibly backed-off) value. * If process is waiting for space, * wakeup/selwakeup/signal. If data * are ready to send, let tcp_output * decide between more output or persist. */ if (tp->snd_una == tp->snd_max) tp->t_timer[TCPT_REXMT] = 0; else if (tp->t_timer[TCPT_PERSIST] == 0) tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; sowwakeup(so); if (so->so_snd.sb_cc) (void) tcp_output(tp); return; } } else if (ti->ti_ack == tp->snd_una && tp->seg_next == (struct tcpiphdr *)tp && ti->ti_len <= sbspace(&so->so_rcv)) { /* * this is a pure, in-sequence data packet * with nothing on the reassembly queue and * we have enough buffer space to take it. */ ++tcpstat.tcps_preddat; tp->rcv_nxt += ti->ti_len; tcpstat.tcps_rcvpack++; tcpstat.tcps_rcvbyte += ti->ti_len; /* * Add data to socket buffer. */ sbappend(&so->so_rcv, m); sorwakeup(so); if (tcp_delack_enabled) { tp->t_flags |= TF_DELACK; } else { tp->t_flags |= TF_ACKNOW; tcp_output(tp); } return; } } /* * Calculate amount of space in receive window, * and then do TCP input processing. * Receive window is amount of space in rcv queue, * but not less than advertised window. */ { int win; win = sbspace(&so->so_rcv); if (win < 0) win = 0; tp->rcv_wnd = imax(win, (int)(tp->rcv_adv - tp->rcv_nxt)); } switch (tp->t_state) { /* * If the state is LISTEN then ignore segment if it contains an RST. * If the segment contains an ACK then it is bad and send a RST. * If it does not contain a SYN then it is not interesting; drop it. * If it is from this socket, drop it, it must be forged. * Don't bother responding if the destination was a broadcast. * Otherwise initialize tp->rcv_nxt, and tp->irs, select an initial * tp->iss, and send a segment: * * Also initialize tp->snd_nxt to tp->iss+1 and tp->snd_una to tp->iss. * Fill in remote peer address fields if not previously specified. * Enter SYN_RECEIVED state, and process any other fields of this * segment in this state. */ case TCPS_LISTEN: { register struct sockaddr_in *sin; if (tiflags & TH_RST) goto drop; if (tiflags & TH_ACK) goto dropwithreset; if ((tiflags & TH_SYN) == 0) goto drop; if ((ti->ti_dport == ti->ti_sport) && (ti->ti_dst.s_addr == ti->ti_src.s_addr)) goto drop; /* * RFC1122 4.2.3.10, p. 104: discard bcast/mcast SYN * in_broadcast() should never return true on a received * packet with M_BCAST not set. */ if (m->m_flags & (M_BCAST|M_MCAST) || IN_MULTICAST(ntohl(ti->ti_dst.s_addr))) goto drop; MALLOC(sin, struct sockaddr_in *, sizeof *sin, M_SONAME, M_NOWAIT); if (sin == NULL) goto drop; sin->sin_family = AF_INET; sin->sin_len = sizeof(*sin); sin->sin_addr = ti->ti_src; sin->sin_port = ti->ti_sport; bzero((caddr_t)sin->sin_zero, sizeof(sin->sin_zero)); laddr = inp->inp_laddr; if (inp->inp_laddr.s_addr == INADDR_ANY) inp->inp_laddr = ti->ti_dst; if (in_pcbconnect(inp, (struct sockaddr *)sin, &proc0)) { inp->inp_laddr = laddr; FREE(sin, M_SONAME); goto drop; } FREE(sin, M_SONAME); tp->t_template = tcp_template(tp); if (tp->t_template == 0) { tp = tcp_drop(tp, ENOBUFS); dropsocket = 0; /* socket is already gone */ goto drop; } if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } tcp_dooptions(tp, optp, optlen, ti, &to); if (iss) tp->iss = iss; else tp->iss = tcp_iss; tcp_iss += TCP_ISSINCR/4; tp->irs = ti->ti_seq; tcp_sendseqinit(tp); tcp_rcvseqinit(tp); /* * Initialization of the tcpcb for transaction; * set SND.WND = SEG.WND, * initialize CCsend and CCrecv. */ tp->snd_wnd = tiwin; /* initial send-window */ tp->cc_send = CC_INC(tcp_ccgen); tp->cc_recv = to.to_cc; /* * Perform TAO test on incoming CC (SEG.CC) option, if any. * - compare SEG.CC against cached CC from the same host, * if any. * - if SEG.CC > chached value, SYN must be new and is accepted * immediately: save new CC in the cache, mark the socket * connected, enter ESTABLISHED state, turn on flag to * send a SYN in the next segment. * A virtual advertised window is set in rcv_adv to * initialize SWS prevention. Then enter normal segment * processing: drop SYN, process data and FIN. * - otherwise do a normal 3-way handshake. */ if ((to.to_flag & TOF_CC) != 0) { if (((tp->t_flags & TF_NOPUSH) != 0) && taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { taop->tao_cc = to.to_cc; tp->t_state = TCPS_ESTABLISHED; /* * If there is a FIN, or if there is data and the * connection is local, then delay SYN,ACK(SYN) in * the hope of piggy-backing it on a response * segment. Otherwise must send ACK now in case * the other side is slow starting. */ if (tcp_delack_enabled && ((tiflags & TH_FIN) || (ti->ti_len != 0 && in_localaddr(inp->inp_faddr)))) tp->t_flags |= (TF_DELACK | TF_NEEDSYN); else tp->t_flags |= (TF_ACKNOW | TF_NEEDSYN); /* * Limit the `virtual advertised window' to TCP_MAXWIN * here. Even if we requested window scaling, it will * become effective only later when our SYN is acked. */ tp->rcv_adv += min(tp->rcv_wnd, TCP_MAXWIN); tcpstat.tcps_connects++; soisconnected(so); tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* else do standard 3-way handshake */ } else { /* * No CC option, but maybe CC.NEW: * invalidate cached value. */ taop->tao_cc = 0; } /* * TAO test failed or there was no CC option, * do a standard 3-way handshake. */ tp->t_flags |= TF_ACKNOW; tp->t_state = TCPS_SYN_RECEIVED; tp->t_timer[TCPT_KEEP] = tcp_keepinit; dropsocket = 0; /* committed to socket */ tcpstat.tcps_accepts++; goto trimthenstep6; } /* * If the state is SYN_RECEIVED: * if seg contains an ACK, but not for our SYN/ACK, send a RST. */ case TCPS_SYN_RECEIVED: if ((tiflags & TH_ACK) && (SEQ_LEQ(ti->ti_ack, tp->snd_una) || SEQ_GT(ti->ti_ack, tp->snd_max))) goto dropwithreset; break; /* * If the state is SYN_SENT: * if seg contains an ACK, but not for our SYN, drop the input. * if seg contains a RST, then drop the connection. * if seg does not contain SYN, then drop it. * Otherwise this is an acceptable SYN segment * initialize tp->rcv_nxt and tp->irs * if seg contains ack then advance tp->snd_una * if SYN has been acked change to ESTABLISHED else SYN_RCVD state * arrange for segment to be acked (eventually) * continue processing rest of data/controls, beginning with URG */ case TCPS_SYN_SENT: if ((taop = tcp_gettaocache(inp)) == NULL) { taop = &tao_noncached; bzero(taop, sizeof(*taop)); } if ((tiflags & TH_ACK) && (SEQ_LEQ(ti->ti_ack, tp->iss) || SEQ_GT(ti->ti_ack, tp->snd_max))) { /* * If we have a cached CCsent for the remote host, * hence we haven't just crashed and restarted, * do not send a RST. This may be a retransmission * from the other side after our earlier ACK was lost. * Our new SYN, when it arrives, will serve as the * needed ACK. */ if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } if (tiflags & TH_RST) { if (tiflags & TH_ACK) tp = tcp_drop(tp, ECONNREFUSED); goto drop; } if ((tiflags & TH_SYN) == 0) goto drop; tp->snd_wnd = ti->ti_win; /* initial send window */ tp->cc_recv = to.to_cc; /* foreign CC */ tp->irs = ti->ti_seq; tcp_rcvseqinit(tp); if (tiflags & TH_ACK) { /* * Our SYN was acked. If segment contains CC.ECHO * option, check it to make sure this segment really * matches our SYN. If not, just drop it as old * duplicate, but send an RST if we're still playing * by the old rules. If no CC.ECHO option, make sure * we don't get fooled into using T/TCP. */ if (to.to_flag & TOF_CCECHO) { if (tp->cc_send != to.to_ccecho) if (taop->tao_ccsent != 0) goto drop; else goto dropwithreset; } else tp->t_flags &= ~TF_RCVD_CC; tcpstat.tcps_connects++; soisconnected(so); /* Do window scaling on this connection? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } /* Segment is acceptable, update cache if undefined. */ if (taop->tao_ccsent == 0) taop->tao_ccsent = to.to_ccecho; tp->rcv_adv += tp->rcv_wnd; tp->snd_una++; /* SYN is acked */ /* * If there's data, delay ACK; if there's also a FIN * ACKNOW will be turned on later. */ if (tcp_delack_enabled && ti->ti_len != 0) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; /* * Received in SYN_SENT[*] state. * Transitions: * SYN_SENT --> ESTABLISHED * SYN_SENT* --> FIN_WAIT_1 */ if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; tiflags &= ~TH_SYN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } } else { /* * Received initial SYN in SYN-SENT[*] state => simul- * taneous open. If segment contains CC option and there is * a cached CC, apply TAO test; if it succeeds, connection is * half-synchronized. Otherwise, do 3-way handshake: * SYN-SENT -> SYN-RECEIVED * SYN-SENT* -> SYN-RECEIVED* * If there was no CC option, clear cached CC value. */ tp->t_flags |= TF_ACKNOW; tp->t_timer[TCPT_REXMT] = 0; if (to.to_flag & TOF_CC) { if (taop->tao_cc != 0 && CC_GT(to.to_cc, taop->tao_cc)) { /* * update cache and make transition: * SYN-SENT -> ESTABLISHED* * SYN-SENT* -> FIN-WAIT-1* */ taop->tao_cc = to.to_cc; if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } tp->t_flags |= TF_NEEDSYN; } else tp->t_state = TCPS_SYN_RECEIVED; } else { /* CC.NEW or no option => invalidate cache */ taop->tao_cc = 0; tp->t_state = TCPS_SYN_RECEIVED; } } trimthenstep6: /* * Advance ti->ti_seq to correspond to first data byte. * If data, trim to stay within window, * dropping FIN if necessary. */ ti->ti_seq++; if (ti->ti_len > tp->rcv_wnd) { todrop = ti->ti_len - tp->rcv_wnd; m_adj(m, -todrop); ti->ti_len = tp->rcv_wnd; tiflags &= ~TH_FIN; tcpstat.tcps_rcvpackafterwin++; tcpstat.tcps_rcvbyteafterwin += todrop; } tp->snd_wl1 = ti->ti_seq - 1; tp->rcv_up = ti->ti_seq; /* * Client side of transaction: already sent SYN and data. * If the remote host used T/TCP to validate the SYN, * our data will be ACK'd; if so, enter normal data segment * processing in the middle of step 5, ack processing. * Otherwise, goto step 6. */ if (tiflags & TH_ACK) goto process_ACK; goto step6; /* * If the state is LAST_ACK or CLOSING or TIME_WAIT: * if segment contains a SYN and CC [not CC.NEW] option: * if state == TIME_WAIT and connection duration > MSL, * drop packet and send RST; * * if SEG.CC > CCrecv then is new SYN, and can implicitly * ack the FIN (and data) in retransmission queue. * Complete close and delete TCPCB. Then reprocess * segment, hoping to find new TCPCB in LISTEN state; * * else must be old SYN; drop it. * else do normal processing. */ case TCPS_LAST_ACK: case TCPS_CLOSING: case TCPS_TIME_WAIT: if ((tiflags & TH_SYN) && (to.to_flag & TOF_CC) && tp->cc_recv != 0) { if (tp->t_state == TCPS_TIME_WAIT && tp->t_duration > TCPTV_MSL) goto dropwithreset; if (CC_GT(to.to_cc, tp->cc_recv)) { tp = tcp_close(tp); goto findpcb; } else goto drop; } break; /* continue normal processing */ } /* * States other than LISTEN or SYN_SENT. * First check timestamp, if present. * Then check the connection count, if present. * Then check that at least some bytes of segment are within * receive window. If segment begins before rcv_nxt, * drop leading data (and SYN); if nothing left, just ack. * * RFC 1323 PAWS: If we have a timestamp reply on this segment * and it's less than ts_recent, drop it. */ if ((to.to_flag & TOF_TS) != 0 && (tiflags & TH_RST) == 0 && tp->ts_recent && TSTMP_LT(to.to_tsval, tp->ts_recent)) { /* Check to see if ts_recent is over 24 days old. */ if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) { /* * Invalidate ts_recent. If this segment updates * ts_recent, the age will be reset later and ts_recent * will get a valid value. If it does not, setting * ts_recent to zero will at least satisfy the * requirement that zero be placed in the timestamp * echo reply when ts_recent isn't valid. The * age isn't reset until we get a valid ts_recent * because we don't want out-of-order segments to be * dropped when ts_recent is old. */ tp->ts_recent = 0; } else { tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += ti->ti_len; tcpstat.tcps_pawsdrop++; goto dropafterack; } } /* * T/TCP mechanism * If T/TCP was negotiated and the segment doesn't have CC, * or if its CC is wrong then drop the segment. * RST segments do not have to comply with this. */ if ((tp->t_flags & (TF_REQ_CC|TF_RCVD_CC)) == (TF_REQ_CC|TF_RCVD_CC) && ((to.to_flag & TOF_CC) == 0 || tp->cc_recv != to.to_cc) && (tiflags & TH_RST) == 0) goto dropafterack; todrop = tp->rcv_nxt - ti->ti_seq; if (todrop > 0) { if (tiflags & TH_SYN) { tiflags &= ~TH_SYN; ti->ti_seq++; if (ti->ti_urp > 1) ti->ti_urp--; else tiflags &= ~TH_URG; todrop--; } /* * Following if statement from Stevens, vol. 2, p. 960. */ if (todrop > ti->ti_len || (todrop == ti->ti_len && (tiflags & TH_FIN) == 0)) { /* * Any valid FIN must be to the left of the window. * At this point the FIN must be a duplicate or out * of sequence; drop it. */ tiflags &= ~TH_FIN; /* * Send an ACK to resynchronize and drop any data. * But keep on processing for RST or ACK. */ tp->t_flags |= TF_ACKNOW; todrop = ti->ti_len; tcpstat.tcps_rcvduppack++; tcpstat.tcps_rcvdupbyte += todrop; } else { tcpstat.tcps_rcvpartduppack++; tcpstat.tcps_rcvpartdupbyte += todrop; } m_adj(m, todrop); ti->ti_seq += todrop; ti->ti_len -= todrop; if (ti->ti_urp > todrop) ti->ti_urp -= todrop; else { tiflags &= ~TH_URG; ti->ti_urp = 0; } } /* * If new data are received on a connection after the * user processes are gone, then RST the other end. */ if ((so->so_state & SS_NOFDREF) && tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) { tp = tcp_close(tp); tcpstat.tcps_rcvafterclose++; goto dropwithreset; } /* * If segment ends after window, drop trailing data * (and PUSH and FIN); if nothing left, just ACK. */ todrop = (ti->ti_seq+ti->ti_len) - (tp->rcv_nxt+tp->rcv_wnd); if (todrop > 0) { tcpstat.tcps_rcvpackafterwin++; if (todrop >= ti->ti_len) { tcpstat.tcps_rcvbyteafterwin += ti->ti_len; /* * If a new connection request is received * while in TIME_WAIT, drop the old connection * and start over if the sequence numbers * are above the previous ones. */ if (tiflags & TH_SYN && tp->t_state == TCPS_TIME_WAIT && SEQ_GT(ti->ti_seq, tp->rcv_nxt)) { iss = tp->rcv_nxt + TCP_ISSINCR; tp = tcp_close(tp); goto findpcb; } /* * If window is closed can only take segments at * window edge, and have to drop data and PUSH from * incoming segments. Continue processing, but * remember to ack. Otherwise, drop segment * and ack. */ if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) { tp->t_flags |= TF_ACKNOW; tcpstat.tcps_rcvwinprobe++; } else goto dropafterack; } else tcpstat.tcps_rcvbyteafterwin += todrop; m_adj(m, -todrop); ti->ti_len -= todrop; tiflags &= ~(TH_PUSH|TH_FIN); } /* * If last ACK falls within this segment's sequence numbers, * record its timestamp. * NOTE that the test is modified according to the latest * proposal of the tcplw@cray.com list (Braden 1993/04/26). */ if ((to.to_flag & TOF_TS) != 0 && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent)) { tp->ts_recent_age = tcp_now; tp->ts_recent = to.to_tsval; } /* * If the RST bit is set examine the state: * SYN_RECEIVED STATE: * If passive open, return to LISTEN state. * If active open, inform user that connection was refused. * ESTABLISHED, FIN_WAIT_1, FIN_WAIT2, CLOSE_WAIT STATES: * Inform user that connection was reset, and close tcb. * CLOSING, LAST_ACK, TIME_WAIT STATES * Close the tcb. */ if (tiflags&TH_RST) switch (tp->t_state) { case TCPS_SYN_RECEIVED: so->so_error = ECONNREFUSED; goto close; case TCPS_ESTABLISHED: case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: case TCPS_CLOSE_WAIT: so->so_error = ECONNRESET; close: tp->t_state = TCPS_CLOSED; tcpstat.tcps_drops++; tp = tcp_close(tp); goto drop; case TCPS_CLOSING: case TCPS_LAST_ACK: case TCPS_TIME_WAIT: tp = tcp_close(tp); goto drop; } /* * If a SYN is in the window, then this is an * error and we send an RST and drop the connection. */ if (tiflags & TH_SYN) { tp = tcp_drop(tp, ECONNRESET); goto dropwithreset; } /* * If the ACK bit is off: if in SYN-RECEIVED state or SENDSYN * flag is on (half-synchronized state), then queue data for * later processing; else drop segment and return. */ if ((tiflags & TH_ACK) == 0) { if (tp->t_state == TCPS_SYN_RECEIVED || (tp->t_flags & TF_NEEDSYN)) goto step6; else goto drop; } /* * Ack processing. */ switch (tp->t_state) { /* * In SYN_RECEIVED state, the ack ACKs our SYN, so enter * ESTABLISHED state and continue processing. * The ACK was checked above. */ case TCPS_SYN_RECEIVED: tcpstat.tcps_connects++; soisconnected(so); /* Do window scaling? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } /* * Upon successful completion of 3-way handshake, * update cache.CC if it was undefined, pass any queued * data to the user, and advance state appropriately. */ if ((taop = tcp_gettaocache(inp)) != NULL && taop->tao_cc == 0) taop->tao_cc = tp->cc_recv; /* * Make transitions: * SYN-RECEIVED -> ESTABLISHED * SYN-RECEIVED* -> FIN-WAIT-1 */ if (tp->t_flags & TF_NEEDFIN) { tp->t_state = TCPS_FIN_WAIT_1; tp->t_flags &= ~TF_NEEDFIN; } else { tp->t_state = TCPS_ESTABLISHED; tp->t_timer[TCPT_KEEP] = tcp_keepidle; } /* * If segment contains data or ACK, will call tcp_reass() * later; if not, do so now to pass queued data to user. */ if (ti->ti_len == 0 && (tiflags & TH_FIN) == 0) (void) tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0); tp->snd_wl1 = ti->ti_seq - 1; /* fall into ... */ /* * In ESTABLISHED state: drop duplicate ACKs; ACK out of range * ACKs. If the ack is in the range * tp->snd_una < ti->ti_ack <= tp->snd_max * then advance tp->snd_una to ti->ti_ack and drop * data from the retransmission queue. If this ACK reflects * more up to date window information we update our window information. */ case TCPS_ESTABLISHED: case TCPS_FIN_WAIT_1: case TCPS_FIN_WAIT_2: case TCPS_CLOSE_WAIT: case TCPS_CLOSING: case TCPS_LAST_ACK: case TCPS_TIME_WAIT: if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) { if (ti->ti_len == 0 && tiwin == tp->snd_wnd) { tcpstat.tcps_rcvdupack++; /* * If we have outstanding data (other than * a window probe), this is a completely * duplicate ack (ie, window info didn't * change), the ack is the biggest we've * seen and we've seen exactly our rexmt * threshhold of them, assume a packet * has been dropped and retransmit it. * Kludge snd_nxt & the congestion * window so we send only this one * packet. * * We know we're losing at the current * window size so do congestion avoidance * (set ssthresh to half the current window * and pull our congestion window back to * the new ssthresh). * * Dup acks mean that packets have left the * network (they're now cached at the receiver) * so bump cwnd by the amount in the receiver * to keep a constant cwnd packets in the * network. */ if (tp->t_timer[TCPT_REXMT] == 0 || ti->ti_ack != tp->snd_una) tp->t_dupacks = 0; else if (++tp->t_dupacks == tcprexmtthresh) { tcp_seq onxt = tp->snd_nxt; u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg; if (win < 2) win = 2; tp->snd_ssthresh = win * tp->t_maxseg; tp->t_timer[TCPT_REXMT] = 0; tp->t_rtt = 0; tp->snd_nxt = ti->ti_ack; tp->snd_cwnd = tp->t_maxseg; (void) tcp_output(tp); tp->snd_cwnd = tp->snd_ssthresh + tp->t_maxseg * tp->t_dupacks; if (SEQ_GT(onxt, tp->snd_nxt)) tp->snd_nxt = onxt; goto drop; } else if (tp->t_dupacks > tcprexmtthresh) { tp->snd_cwnd += tp->t_maxseg; (void) tcp_output(tp); goto drop; } } else tp->t_dupacks = 0; break; } /* * If the congestion window was inflated to account * for the other side's cached packets, retract it. */ if (tp->t_dupacks >= tcprexmtthresh && tp->snd_cwnd > tp->snd_ssthresh) tp->snd_cwnd = tp->snd_ssthresh; tp->t_dupacks = 0; if (SEQ_GT(ti->ti_ack, tp->snd_max)) { tcpstat.tcps_rcvacktoomuch++; goto dropafterack; } /* * If we reach this point, ACK is not a duplicate, * i.e., it ACKs something we sent. */ if (tp->t_flags & TF_NEEDSYN) { /* * T/TCP: Connection was half-synchronized, and our * SYN has been ACK'd (so connection is now fully * synchronized). Go to non-starred state, * increment snd_una for ACK of SYN, and check if * we can do window scaling. */ tp->t_flags &= ~TF_NEEDSYN; tp->snd_una++; /* Do window scaling? */ if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) == (TF_RCVD_SCALE|TF_REQ_SCALE)) { tp->snd_scale = tp->requested_s_scale; tp->rcv_scale = tp->request_r_scale; } } process_ACK: acked = ti->ti_ack - tp->snd_una; tcpstat.tcps_rcvackpack++; tcpstat.tcps_rcvackbyte += acked; /* * If we have a timestamp reply, update smoothed * round trip time. If no timestamp is present but * transmit timer is running and timed sequence * number was acked, update smoothed round trip time. * Since we now have an rtt measurement, cancel the * timer backoff (cf., Phil Karn's retransmit alg.). * Recompute the initial retransmit timer. */ if (to.to_flag & TOF_TS) tcp_xmit_timer(tp, tcp_now - to.to_tsecr + 1); else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq)) tcp_xmit_timer(tp,tp->t_rtt); /* * If all outstanding data is acked, stop retransmit * timer and remember to restart (more output or persist). * If there is more data to be acked, restart retransmit * timer, using current (possibly backed-off) value. */ if (ti->ti_ack == tp->snd_max) { tp->t_timer[TCPT_REXMT] = 0; needoutput = 1; } else if (tp->t_timer[TCPT_PERSIST] == 0) tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; /* * If no data (only SYN) was ACK'd, * skip rest of ACK processing. */ if (acked == 0) goto step6; /* * When new data is acked, open the congestion window. * If the window gives us less than ssthresh packets * in flight, open exponentially (maxseg per packet). * Otherwise open linearly: maxseg per window * (maxseg^2 / cwnd per packet). */ { register u_int cw = tp->snd_cwnd; register u_int incr = tp->t_maxseg; if (cw > tp->snd_ssthresh) incr = incr * incr / cw; tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<snd_scale); } if (acked > so->so_snd.sb_cc) { tp->snd_wnd -= so->so_snd.sb_cc; sbdrop(&so->so_snd, (int)so->so_snd.sb_cc); ourfinisacked = 1; } else { sbdrop(&so->so_snd, acked); tp->snd_wnd -= acked; ourfinisacked = 0; } sowwakeup(so); tp->snd_una = ti->ti_ack; if (SEQ_LT(tp->snd_nxt, tp->snd_una)) tp->snd_nxt = tp->snd_una; switch (tp->t_state) { /* * In FIN_WAIT_1 STATE in addition to the processing * for the ESTABLISHED state if our FIN is now acknowledged * then enter FIN_WAIT_2. */ case TCPS_FIN_WAIT_1: if (ourfinisacked) { /* * If we can't receive any more * data, then closing user can proceed. * Starting the timer is contrary to the * specification, but if we don't get a FIN * we'll hang forever. */ if (so->so_state & SS_CANTRCVMORE) { soisdisconnected(so); tp->t_timer[TCPT_2MSL] = tcp_maxidle; } tp->t_state = TCPS_FIN_WAIT_2; } break; /* * In CLOSING STATE in addition to the processing for * the ESTABLISHED state if the ACK acknowledges our FIN * then enter the TIME-WAIT state, otherwise ignore * the segment. */ case TCPS_CLOSING: if (ourfinisacked) { tp->t_state = TCPS_TIME_WAIT; tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && tp->t_duration < TCPTV_MSL) tp->t_timer[TCPT_2MSL] = tp->t_rxtcur * TCPTV_TWTRUNC; else tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; soisdisconnected(so); } break; /* * In LAST_ACK, we may still be waiting for data to drain * and/or to be acked, as well as for the ack of our FIN. * If our FIN is now acknowledged, delete the TCB, * enter the closed state and return. */ case TCPS_LAST_ACK: if (ourfinisacked) { tp = tcp_close(tp); goto drop; } break; /* * In TIME_WAIT state the only thing that should arrive * is a retransmission of the remote FIN. Acknowledge * it and restart the finack timer. */ case TCPS_TIME_WAIT: tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; goto dropafterack; } } step6: /* * Update window information. * Don't look at window if no ACK: TAC's send garbage on first SYN. */ if ((tiflags & TH_ACK) && (SEQ_LT(tp->snd_wl1, ti->ti_seq) || (tp->snd_wl1 == ti->ti_seq && (SEQ_LT(tp->snd_wl2, ti->ti_ack) || (tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))))) { /* keep track of pure window updates */ if (ti->ti_len == 0 && tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd) tcpstat.tcps_rcvwinupd++; tp->snd_wnd = tiwin; tp->snd_wl1 = ti->ti_seq; tp->snd_wl2 = ti->ti_ack; if (tp->snd_wnd > tp->max_sndwnd) tp->max_sndwnd = tp->snd_wnd; needoutput = 1; } /* * Process segments with URG. */ if ((tiflags & TH_URG) && ti->ti_urp && TCPS_HAVERCVDFIN(tp->t_state) == 0) { /* * This is a kludge, but if we receive and accept * random urgent pointers, we'll crash in * soreceive. It's hard to imagine someone * actually wanting to send this much urgent data. */ if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) { ti->ti_urp = 0; /* XXX */ tiflags &= ~TH_URG; /* XXX */ goto dodata; /* XXX */ } /* * If this segment advances the known urgent pointer, * then mark the data stream. This should not happen * in CLOSE_WAIT, CLOSING, LAST_ACK or TIME_WAIT STATES since * a FIN has been received from the remote side. * In these states we ignore the URG. * * According to RFC961 (Assigned Protocols), * the urgent pointer points to the last octet * of urgent data. We continue, however, * to consider it to indicate the first octet * of data past the urgent section as the original * spec states (in one of two places). */ if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) { tp->rcv_up = ti->ti_seq + ti->ti_urp; so->so_oobmark = so->so_rcv.sb_cc + (tp->rcv_up - tp->rcv_nxt) - 1; if (so->so_oobmark == 0) so->so_state |= SS_RCVATMARK; sohasoutofband(so); tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA); } /* * Remove out of band data so doesn't get presented to user. * This can happen independent of advancing the URG pointer, * but if two URG's are pending at once, some out-of-band * data may creep in... ick. */ if (ti->ti_urp <= (u_long)ti->ti_len #ifdef SO_OOBINLINE && (so->so_options & SO_OOBINLINE) == 0 #endif ) tcp_pulloutofband(so, ti, m); } else /* * If no out of band data is expected, * pull receive urgent pointer along * with the receive window. */ if (SEQ_GT(tp->rcv_nxt, tp->rcv_up)) tp->rcv_up = tp->rcv_nxt; dodata: /* XXX */ /* * Process the segment text, merging it into the TCP sequencing queue, * and arranging for acknowledgment of receipt if necessary. * This process logically involves adjusting tp->rcv_wnd as data * is presented to the user (this happens in tcp_usrreq.c, * case PRU_RCVD). If a FIN has already been received on this * connection then we just ignore the text. */ if ((ti->ti_len || (tiflags&TH_FIN)) && TCPS_HAVERCVDFIN(tp->t_state) == 0) { TCP_REASS(tp, ti, m, so, tiflags); /* * Note the amount of data that peer has sent into * our window, in order to estimate the sender's * buffer size. */ len = so->so_rcv.sb_hiwat - (tp->rcv_adv - tp->rcv_nxt); } else { m_freem(m); tiflags &= ~TH_FIN; } /* * If FIN is received ACK the FIN and let the user know * that the connection is closing. */ if (tiflags & TH_FIN) { if (TCPS_HAVERCVDFIN(tp->t_state) == 0) { socantrcvmore(so); /* * If connection is half-synchronized * (ie NEEDSYN flag on) then delay ACK, * so it may be piggybacked when SYN is sent. * Otherwise, since we received a FIN then no * more input can be expected, send ACK now. */ if (tcp_delack_enabled && (tp->t_flags & TF_NEEDSYN)) tp->t_flags |= TF_DELACK; else tp->t_flags |= TF_ACKNOW; tp->rcv_nxt++; } switch (tp->t_state) { /* * In SYN_RECEIVED and ESTABLISHED STATES * enter the CLOSE_WAIT state. */ case TCPS_SYN_RECEIVED: case TCPS_ESTABLISHED: tp->t_state = TCPS_CLOSE_WAIT; break; /* * If still in FIN_WAIT_1 STATE FIN has not been acked so * enter the CLOSING state. */ case TCPS_FIN_WAIT_1: tp->t_state = TCPS_CLOSING; break; /* * In FIN_WAIT_2 state enter the TIME_WAIT state, * starting the time-wait timer, turning off the other * standard timers. */ case TCPS_FIN_WAIT_2: tp->t_state = TCPS_TIME_WAIT; tcp_canceltimers(tp); /* Shorten TIME_WAIT [RFC-1644, p.28] */ if (tp->cc_recv != 0 && tp->t_duration < TCPTV_MSL) { tp->t_timer[TCPT_2MSL] = tp->t_rxtcur * TCPTV_TWTRUNC; /* For transaction client, force ACK now. */ tp->t_flags |= TF_ACKNOW; } else tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; soisdisconnected(so); break; /* * In TIME_WAIT state restart the 2 MSL time_wait timer. */ case TCPS_TIME_WAIT: tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL; break; } } #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) tcp_trace(TA_INPUT, ostate, tp, &tcp_saveti, 0); #endif /* * Return any desired output. */ if (needoutput || (tp->t_flags & TF_ACKNOW)) (void) tcp_output(tp); return; dropafterack: /* * Generate an ACK dropping incoming segment if it occupies * sequence space, where the ACK reflects our state. */ if (tiflags & TH_RST) goto drop; #ifdef TCPDEBUG if (so->so_options & SO_DEBUG) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif m_freem(m); tp->t_flags |= TF_ACKNOW; (void) tcp_output(tp); return; dropwithreset: /* * Generate a RST, dropping incoming segment. * Make ACK acceptable to originator of segment. * Don't bother to respond if destination was broadcast/multicast. */ if ((tiflags & TH_RST) || m->m_flags & (M_BCAST|M_MCAST) || IN_MULTICAST(ntohl(ti->ti_dst.s_addr))) goto drop; #ifdef TCPDEBUG if (tp == 0 || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif if (tiflags & TH_ACK) tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST); else { if (tiflags & TH_SYN) ti->ti_len++; tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0, TH_RST|TH_ACK); } /* destroy temporarily created socket */ if (dropsocket) (void) soabort(so); return; drop: /* * Drop space held by incoming segment and return. */ #ifdef TCPDEBUG if (tp == 0 || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG)) tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0); #endif m_freem(m); /* destroy temporarily created socket */ if (dropsocket) (void) soabort(so); return; } static void tcp_dooptions(tp, cp, cnt, ti, to) struct tcpcb *tp; u_char *cp; int cnt; struct tcpiphdr *ti; struct tcpopt *to; { u_short mss = 0; int opt, optlen; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[0]; if (opt == TCPOPT_EOL) break; if (opt == TCPOPT_NOP) optlen = 1; else { optlen = cp[1]; if (optlen <= 0) break; } switch (opt) { default: continue; case TCPOPT_MAXSEG: if (optlen != TCPOLEN_MAXSEG) continue; if (!(ti->ti_flags & TH_SYN)) continue; bcopy((char *) cp + 2, (char *) &mss, sizeof(mss)); NTOHS(mss); break; case TCPOPT_WINDOW: if (optlen != TCPOLEN_WINDOW) continue; if (!(ti->ti_flags & TH_SYN)) continue; tp->t_flags |= TF_RCVD_SCALE; tp->requested_s_scale = min(cp[2], TCP_MAX_WINSHIFT); break; case TCPOPT_TIMESTAMP: if (optlen != TCPOLEN_TIMESTAMP) continue; to->to_flag |= TOF_TS; bcopy((char *)cp + 2, (char *)&to->to_tsval, sizeof(to->to_tsval)); NTOHL(to->to_tsval); bcopy((char *)cp + 6, (char *)&to->to_tsecr, sizeof(to->to_tsecr)); NTOHL(to->to_tsecr); /* * A timestamp received in a SYN makes * it ok to send timestamp requests and replies. */ if (ti->ti_flags & TH_SYN) { tp->t_flags |= TF_RCVD_TSTMP; tp->ts_recent = to->to_tsval; tp->ts_recent_age = tcp_now; } break; case TCPOPT_CC: if (optlen != TCPOLEN_CC) continue; to->to_flag |= TOF_CC; bcopy((char *)cp + 2, (char *)&to->to_cc, sizeof(to->to_cc)); NTOHL(to->to_cc); /* * A CC or CC.new option received in a SYN makes * it ok to send CC in subsequent segments. */ if (ti->ti_flags & TH_SYN) tp->t_flags |= TF_RCVD_CC; break; case TCPOPT_CCNEW: if (optlen != TCPOLEN_CC) continue; if (!(ti->ti_flags & TH_SYN)) continue; to->to_flag |= TOF_CCNEW; bcopy((char *)cp + 2, (char *)&to->to_cc, sizeof(to->to_cc)); NTOHL(to->to_cc); /* * A CC or CC.new option received in a SYN makes * it ok to send CC in subsequent segments. */ tp->t_flags |= TF_RCVD_CC; break; case TCPOPT_CCECHO: if (optlen != TCPOLEN_CC) continue; if (!(ti->ti_flags & TH_SYN)) continue; to->to_flag |= TOF_CCECHO; bcopy((char *)cp + 2, (char *)&to->to_ccecho, sizeof(to->to_ccecho)); NTOHL(to->to_ccecho); break; } } if (ti->ti_flags & TH_SYN) tcp_mss(tp, mss); /* sets t_maxseg */ } /* * Pull out of band byte out of a segment so * it doesn't appear in the user's data queue. * It is still reflected in the segment length for * sequencing purposes. */ static void tcp_pulloutofband(so, ti, m) struct socket *so; struct tcpiphdr *ti; register struct mbuf *m; { int cnt = ti->ti_urp - 1; while (cnt >= 0) { if (m->m_len > cnt) { char *cp = mtod(m, caddr_t) + cnt; struct tcpcb *tp = sototcpcb(so); tp->t_iobc = *cp; tp->t_oobflags |= TCPOOB_HAVEDATA; bcopy(cp+1, cp, (unsigned)(m->m_len - cnt - 1)); m->m_len--; return; } cnt -= m->m_len; m = m->m_next; if (m == 0) break; } panic("tcp_pulloutofband"); } /* * Collect new round-trip time estimate * and update averages and current timeout. */ static void tcp_xmit_timer(tp, rtt) register struct tcpcb *tp; short rtt; { register int delta; tcpstat.tcps_rttupdated++; tp->t_rttupdated++; if (tp->t_srtt != 0) { /* * srtt is stored as fixed point with 5 bits after the * binary point (i.e., scaled by 8). The following magic * is equivalent to the smoothing algorithm in rfc793 with * an alpha of .875 (srtt = rtt/8 + srtt*7/8 in fixed * point). Adjust rtt to origin 0. */ delta = ((rtt - 1) << TCP_DELTA_SHIFT) - (tp->t_srtt >> (TCP_RTT_SHIFT - TCP_DELTA_SHIFT)); if ((tp->t_srtt += delta) <= 0) tp->t_srtt = 1; /* * We accumulate a smoothed rtt variance (actually, a * smoothed mean difference), then set the retransmit * timer to smoothed rtt + 4 times the smoothed variance. * rttvar is stored as fixed point with 4 bits after the * binary point (scaled by 16). The following is * equivalent to rfc793 smoothing with an alpha of .75 * (rttvar = rttvar*3/4 + |delta| / 4). This replaces * rfc793's wired-in beta. */ if (delta < 0) delta = -delta; delta -= tp->t_rttvar >> (TCP_RTTVAR_SHIFT - TCP_DELTA_SHIFT); if ((tp->t_rttvar += delta) <= 0) tp->t_rttvar = 1; } else { /* * No rtt measurement yet - use the unsmoothed rtt. * Set the variance to half the rtt (so our first * retransmit happens at 3*rtt). */ tp->t_srtt = rtt << TCP_RTT_SHIFT; tp->t_rttvar = rtt << (TCP_RTTVAR_SHIFT - 1); } tp->t_rtt = 0; tp->t_rxtshift = 0; /* * the retransmit should happen at rtt + 4 * rttvar. * Because of the way we do the smoothing, srtt and rttvar * will each average +1/2 tick of bias. When we compute * the retransmit timer, we want 1/2 tick of rounding and * 1 extra tick because of +-1/2 tick uncertainty in the * firing of the timer. The bias will give us exactly the * 1.5 tick we need. But, because the bias is * statistical, we have to test that we don't drop below * the minimum feasible timer (which is 2 ticks). */ TCPT_RANGESET(tp->t_rxtcur, TCP_REXMTVAL(tp), max(tp->t_rttmin, rtt + 2), TCPTV_REXMTMAX); /* * We received an ack for a packet that wasn't retransmitted; * it is probably safe to discard any error indications we've * received recently. This isn't quite right, but close enough * for now (a route might have failed after we sent a segment, * and the return path might not be symmetrical). */ tp->t_softerror = 0; } /* * Determine a reasonable value for maxseg size. * If the route is known, check route for mtu. * If none, use an mss that can be handled on the outgoing * interface without forcing IP to fragment; if bigger than * an mbuf cluster (MCLBYTES), round down to nearest multiple of MCLBYTES * to utilize large mbufs. If no route is found, route has no mtu, * or the destination isn't local, use a default, hopefully conservative * size (usually 512 or the default IP max size, but no more than the mtu * of the interface), as we can't discover anything about intervening * gateways or networks. We also initialize the congestion/slow start * window to be a single segment if the destination isn't local. * While looking at the routing entry, we also initialize other path-dependent * parameters from pre-set or cached values in the routing entry. * * Also take into account the space needed for options that we * send regularly. Make maxseg shorter by that amount to assure * that we can send maxseg amount of data even when the options * are present. Store the upper limit of the length of options plus * data in maxopd. * * NOTE that this routine is only called when we process an incoming * segment, for outgoing segments only tcp_mssopt is called. * * In case of T/TCP, we call this routine during implicit connection * setup as well (offer = -1), to initialize maxseg from the cached * MSS of our peer. */ void tcp_mss(tp, offer) struct tcpcb *tp; int offer; { register struct rtentry *rt; struct ifnet *ifp; register int rtt, mss; u_long bufsize; struct inpcb *inp; struct socket *so; struct rmxp_tao *taop; int origoffer = offer; inp = tp->t_inpcb; if ((rt = tcp_rtlookup(inp)) == NULL) { tp->t_maxopd = tp->t_maxseg = tcp_mssdflt; return; } ifp = rt->rt_ifp; so = inp->inp_socket; taop = rmx_taop(rt->rt_rmx); /* * Offer == -1 means that we didn't receive SYN yet, * use cached value in that case; */ if (offer == -1) offer = taop->tao_mssopt; /* * Offer == 0 means that there was no MSS on the SYN segment, * in this case we use tcp_mssdflt. */ if (offer == 0) offer = tcp_mssdflt; else /* * Sanity check: make sure that maxopd will be large * enough to allow some data on segments even is the * all the option space is used (40bytes). Otherwise * funny things may happen in tcp_output. */ offer = max(offer, 64); taop->tao_mssopt = offer; /* * While we're here, check if there's an initial rtt * or rttvar. Convert from the route-table units * to scaled multiples of the slow timeout timer. */ if (tp->t_srtt == 0 && (rtt = rt->rt_rmx.rmx_rtt)) { /* * XXX the lock bit for RTT indicates that the value * is also a minimum value; this is subject to time. */ if (rt->rt_rmx.rmx_locks & RTV_RTT) tp->t_rttmin = rtt / (RTM_RTTUNIT / PR_SLOWHZ); tp->t_srtt = rtt / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE)); tcpstat.tcps_usedrtt++; if (rt->rt_rmx.rmx_rttvar) { tp->t_rttvar = rt->rt_rmx.rmx_rttvar / (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE)); tcpstat.tcps_usedrttvar++; } else { /* default variation is +- 1 rtt */ tp->t_rttvar = tp->t_srtt * TCP_RTTVAR_SCALE / TCP_RTT_SCALE; } TCPT_RANGESET(tp->t_rxtcur, ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1, tp->t_rttmin, TCPTV_REXMTMAX); } /* * if there's an mtu associated with the route, use it */ if (rt->rt_rmx.rmx_mtu) mss = rt->rt_rmx.rmx_mtu - sizeof(struct tcpiphdr); else { mss = ifp->if_mtu - sizeof(struct tcpiphdr); if (!in_localaddr(inp->inp_faddr)) mss = min(mss, tcp_mssdflt); } mss = min(mss, offer); /* * maxopd stores the maximum length of data AND options * in a segment; maxseg is the amount of data in a normal * segment. We need to store this value (maxopd) apart * from maxseg, because now every segment carries options * and thus we normally have somewhat less data in segments. */ tp->t_maxopd = mss; /* * In case of T/TCP, origoffer==-1 indicates, that no segments * were received yet. In this case we just guess, otherwise * we do the same as before T/TCP. */ if ((tp->t_flags & (TF_REQ_TSTMP|TF_NOOPT)) == TF_REQ_TSTMP && (origoffer == -1 || (tp->t_flags & TF_RCVD_TSTMP) == TF_RCVD_TSTMP)) mss -= TCPOLEN_TSTAMP_APPA; if ((tp->t_flags & (TF_REQ_CC|TF_NOOPT)) == TF_REQ_CC && (origoffer == -1 || (tp->t_flags & TF_RCVD_CC) == TF_RCVD_CC)) mss -= TCPOLEN_CC_APPA; #if (MCLBYTES & (MCLBYTES - 1)) == 0 if (mss > MCLBYTES) mss &= ~(MCLBYTES-1); #else if (mss > MCLBYTES) mss = mss / MCLBYTES * MCLBYTES; #endif /* * If there's a pipesize, change the socket buffer * to that size. Make the socket buffers an integral * number of mss units; if the mss is larger than * the socket buffer, decrease the mss. */ #ifdef RTV_SPIPE if ((bufsize = rt->rt_rmx.rmx_sendpipe) == 0) #endif bufsize = so->so_snd.sb_hiwat; if (bufsize < mss) mss = bufsize; else { bufsize = roundup(bufsize, mss); if (bufsize > sb_max) bufsize = sb_max; (void)sbreserve(&so->so_snd, bufsize); } tp->t_maxseg = mss; #ifdef RTV_RPIPE if ((bufsize = rt->rt_rmx.rmx_recvpipe) == 0) #endif bufsize = so->so_rcv.sb_hiwat; if (bufsize > mss) { bufsize = roundup(bufsize, mss); if (bufsize > sb_max) bufsize = sb_max; (void)sbreserve(&so->so_rcv, bufsize); } /* * Don't force slow-start on local network. */ if (!in_localaddr(inp->inp_faddr)) tp->snd_cwnd = mss; if (rt->rt_rmx.rmx_ssthresh) { /* * There's some sort of gateway or interface * buffer limit on the path. Use this to set * the slow start threshhold, but set the * threshold to no less than 2*mss. */ tp->snd_ssthresh = max(2 * mss, rt->rt_rmx.rmx_ssthresh); tcpstat.tcps_usedssthresh++; } } /* * Determine the MSS option to send on an outgoing SYN. */ int tcp_mssopt(tp) struct tcpcb *tp; { struct rtentry *rt; rt = tcp_rtlookup(tp->t_inpcb); if (rt == NULL) return tcp_mssdflt; return rt->rt_ifp->if_mtu - sizeof(struct tcpiphdr); }