The problem and different from IPv4 behavior was found with ipfw fwd to IPv6 address that is configured in jail.
We have some application that works in jail and listens port 13333 for both IPv4 and IPv6. On host system we forward all TCP connections to port 3333 to address and port in the jail. With IPv4 it works as expected. With IPv6 we see successful TCP handshake and then server sends RST just after final ACK.
# tcpdump -ni lagg0 tcp and port 3333 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lagg0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 09:32:33.410088 IP6 fc00::2.63582 > fc00::6.3333: Flags [S], seq 275485310, win 64800, options [mss 1440,nop,wscale 8,nop,nop,sackOK], length 0 09:32:33.410112 IP6 fc00::6.3333 > fc00::2.63582: Flags [S.], seq 1400678893, ack 275485311, win 65535, options [mss 1440,nop,wscale 6,sackOK,eol], length 0 09:32:33.410235 IP6 fc00::2.63582 > fc00::6.3333: Flags [.], ack 1, win 8235, length 0 09:32:33.410328 IP6 fc00::6.3333 > fc00::2.63582: Flags [R], seq 1400678894, win 0, length 0
With enabled net.inet.tcp.log_debug=1 we can see these messages in the log:
Sep 1 12:32:33 btr-test kernel: TCP: [fc00::2]:63582 to [fc00::6]:3333; syncache_socket: in6_pcbconnect failed with error 49 Sep 1 12:32:33 btr-test kernel: TCP: [fc00::2]:63582 to [fc00::6]:3333 tcpflags 0x10<ACK>; tcp_input_with_port: Listen socket: Socket allocation failed due to limits or memory shortage, sending RST
Further debugging shows that errors is returned through in6_pcbladdr() -> in6_selectsrc_socket() -> in6_selectsrc() -> prison_local_ip6().
And it is because our source IPv6 address doesn't belongs to the jail, since fwd_tag was used to find corresponding listen socket.
The patch makes behavior to be similar to IPv4. in6_pcbconnect() can be called from syncache_socket() on accepting established connection, and from UDP&TCP connect(), when we are initiate connection. We can reuse bool argument rehash to determine our case and skip IPv6 SAS.