Okay, I've worked this out. I added "kernel-module-xt-tcpudp" to the RRECOMMENDS_${PN} variable in iptables.inc. I don't know if that was the proper place for it, but it works. Previous, the variable was only:

     RRECOMMENDS_${PN} = "\
       kernel-module-ip-tables \
       kernel-module-iptable-nat \
       kernel-module-iptable-filter \
       kernel-module-ipt-masquerade \

Which is only the list of tables, right? (And why is masquerade not following the naming convention?) So maybe this wasn't the place for the protocol module, but I couldn't find anywhere else in any other recipe where protocols were specified. I can't fathom wanting to add iptables to a kernel and NOT also add the ability to read header info on TCP and UDP packets. It's disturbing that this is the default behavior. I just hope that I can now write all my rules without having to track down and add another module.

Anyway, now all the modules now get properly loaded if I have a startup script perform iptables-restore, so I'm satisfied that the fix is neat enough.