| /* |
| * Copyright (C) 1993-2001, 2003 by Darren Reed. |
| * |
| * See the IPFILTER.LICENCE file for details on licencing. |
| */ |
| /* #pragma ident "@(#)solaris.c 1.12 6/5/96 (C) 1995 Darren Reed"*/ |
| #pragma ident "@(#)$Id$" |
| |
| #include <sys/systm.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/errno.h> |
| #include <sys/uio.h> |
| #include <sys/buf.h> |
| #include <sys/modctl.h> |
| #include <sys/open.h> |
| #include <sys/kmem.h> |
| #include <sys/conf.h> |
| #include <sys/cmn_err.h> |
| #include <sys/stat.h> |
| #include <sys/cred.h> |
| #include <sys/dditypes.h> |
| #include <sys/stream.h> |
| #include <sys/poll.h> |
| #include <sys/autoconf.h> |
| #include <sys/byteorder.h> |
| #include <sys/socket.h> |
| #include <sys/dlpi.h> |
| #include <sys/stropts.h> |
| #include <sys/sockio.h> |
| #include <net/if.h> |
| #if SOLARIS2 >= 6 |
| # include <net/if_types.h> |
| #endif |
| #include <net/af.h> |
| #include <net/route.h> |
| #include <netinet/in.h> |
| #include <netinet/in_systm.h> |
| #include <netinet/if_ether.h> |
| #include <netinet/ip.h> |
| #include <netinet/ip_var.h> |
| #include <netinet/tcp.h> |
| #include <netinet/udp.h> |
| #include <netinet/tcpip.h> |
| #include <netinet/ip_icmp.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include "netinet/ip_compat.h" |
| #include "netinet/ipl.h" |
| #include "netinet/ip_fil.h" |
| #include "netinet/ip_nat.h" |
| #include "netinet/ip_frag.h" |
| #include "netinet/ip_auth.h" |
| #include "netinet/ip_state.h" |
| |
| struct pollhead iplpollhead[IPL_LOGSIZE]; |
| |
| extern struct filterstats frstats[]; |
| extern int fr_running; |
| extern int fr_flags; |
| extern int iplwrite __P((dev_t, struct uio *, cred_t *)); |
| |
| extern ipnat_t *nat_list; |
| |
| static int ipf_getinfo __P((dev_info_t *, ddi_info_cmd_t, |
| void *, void **)); |
| #if SOLARIS2 < 10 |
| static int ipf_identify __P((dev_info_t *)); |
| #endif |
| static int ipf_attach __P((dev_info_t *, ddi_attach_cmd_t)); |
| static int ipf_detach __P((dev_info_t *, ddi_detach_cmd_t)); |
| static int fr_qifsync __P((ip_t *, int, void *, int, void *, mblk_t **)); |
| static int ipf_property_update __P((dev_info_t *)); |
| static int iplpoll __P((dev_t, short, int, short *, struct pollhead **)); |
| static char *ipf_devfiles[] = { IPL_NAME, IPNAT_NAME, IPSTATE_NAME, |
| IPAUTH_NAME, IPSYNC_NAME, IPSCAN_NAME, |
| IPLOOKUP_NAME, NULL }; |
| |
| |
| #if SOLARIS2 >= 7 |
| extern timeout_id_t fr_timer_id; |
| #else |
| extern int fr_timer_id; |
| #endif |
| |
| |
| static struct cb_ops ipf_cb_ops = { |
| iplopen, |
| iplclose, |
| nodev, /* strategy */ |
| nodev, /* print */ |
| nodev, /* dump */ |
| iplread, |
| iplwrite, /* write */ |
| iplioctl, /* ioctl */ |
| nodev, /* devmap */ |
| nodev, /* mmap */ |
| nodev, /* segmap */ |
| iplpoll, /* poll */ |
| ddi_prop_op, |
| NULL, |
| D_MTSAFE, |
| #if SOLARIS2 > 4 |
| CB_REV, |
| nodev, /* aread */ |
| nodev, /* awrite */ |
| #endif |
| }; |
| |
| static struct dev_ops ipf_ops = { |
| DEVO_REV, |
| 0, |
| ipf_getinfo, |
| #if SOLARIS2 >= 10 |
| nulldev, |
| #else |
| ipf_identify, |
| #endif |
| nulldev, |
| ipf_attach, |
| ipf_detach, |
| nodev, /* reset */ |
| &ipf_cb_ops, |
| (struct bus_ops *)0 |
| }; |
| |
| extern struct mod_ops mod_driverops; |
| static struct modldrv iplmod = { |
| &mod_driverops, IPL_VERSION, &ipf_ops }; |
| static struct modlinkage modlink1 = { MODREV_1, &iplmod, NULL }; |
| |
| #if SOLARIS2 >= 6 |
| static size_t hdrsizes[57][2] = { |
| { 0, 0 }, |
| { IFT_OTHER, 0 }, |
| { IFT_1822, 0 }, |
| { IFT_HDH1822, 0 }, |
| { IFT_X25DDN, 0 }, |
| { IFT_X25, 0 }, |
| { IFT_ETHER, 14 }, |
| { IFT_ISO88023, 0 }, |
| { IFT_ISO88024, 0 }, |
| { IFT_ISO88025, 0 }, |
| { IFT_ISO88026, 0 }, |
| { IFT_STARLAN, 0 }, |
| { IFT_P10, 0 }, |
| { IFT_P80, 0 }, |
| { IFT_HY, 0 }, |
| { IFT_FDDI, 24 }, |
| { IFT_LAPB, 0 }, |
| { IFT_SDLC, 0 }, |
| { IFT_T1, 0 }, |
| { IFT_CEPT, 0 }, |
| { IFT_ISDNBASIC, 0 }, |
| { IFT_ISDNPRIMARY, 0 }, |
| { IFT_PTPSERIAL, 0 }, |
| { IFT_PPP, 0 }, |
| { IFT_LOOP, 0 }, |
| { IFT_EON, 0 }, |
| { IFT_XETHER, 0 }, |
| { IFT_NSIP, 0 }, |
| { IFT_SLIP, 0 }, |
| { IFT_ULTRA, 0 }, |
| { IFT_DS3, 0 }, |
| { IFT_SIP, 0 }, |
| { IFT_FRELAY, 0 }, |
| { IFT_RS232, 0 }, |
| { IFT_PARA, 0 }, |
| { IFT_ARCNET, 0 }, |
| { IFT_ARCNETPLUS, 0 }, |
| { IFT_ATM, 0 }, |
| { IFT_MIOX25, 0 }, |
| { IFT_SONET, 0 }, |
| { IFT_X25PLE, 0 }, |
| { IFT_ISO88022LLC, 0 }, |
| { IFT_LOCALTALK, 0 }, |
| { IFT_SMDSDXI, 0 }, |
| { IFT_FRELAYDCE, 0 }, |
| { IFT_V35, 0 }, |
| { IFT_HSSI, 0 }, |
| { IFT_HIPPI, 0 }, |
| { IFT_MODEM, 0 }, |
| { IFT_AAL5, 0 }, |
| { IFT_SONETPATH, 0 }, |
| { IFT_SONETVT, 0 }, |
| { IFT_SMDSICIP, 0 }, |
| { IFT_PROPVIRTUAL, 0 }, |
| { IFT_PROPMUX, 0 }, |
| }; |
| #endif /* SOLARIS2 >= 6 */ |
| |
| static dev_info_t *ipf_dev_info = NULL; |
| |
| #if defined(_INET_IP_STACK_H) |
| static hook_t *ipfhook; |
| static int ipf_hook(hook_event_token_t, hook_data_t, void *); |
| net_handle_t ipfipv4, ipfipv6; |
| #endif |
| |
| |
| int _init() |
| { |
| int ipfinst; |
| |
| ipfinst = mod_install(&modlink1); |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: _init() = %d", ipfinst); |
| #endif |
| return ipfinst; |
| } |
| |
| |
| int _fini(void) |
| { |
| int ipfinst; |
| |
| ipfinst = mod_remove(&modlink1); |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: _fini() = %d", ipfinst); |
| #endif |
| return ipfinst; |
| } |
| |
| |
| int _info(modinfop) |
| struct modinfo *modinfop; |
| { |
| int ipfinst; |
| |
| ipfinst = mod_info(&modlink1, modinfop); |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: _info(%x) = %x", modinfop, ipfinst); |
| #endif |
| return ipfinst; |
| } |
| |
| |
| #if SOLARIS2 < 10 |
| static int ipf_identify(dip) |
| dev_info_t *dip; |
| { |
| # ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: ipf_identify(%x)", dip); |
| # endif |
| if (strcmp(ddi_get_name(dip), "ipf") == 0) |
| return (DDI_IDENTIFIED); |
| return (DDI_NOT_IDENTIFIED); |
| } |
| #endif |
| |
| |
| static int ipf_attach(dip, cmd) |
| dev_info_t *dip; |
| ddi_attach_cmd_t cmd; |
| { |
| char *s; |
| int i; |
| #ifdef IPFDEBUG |
| int instance; |
| |
| cmn_err(CE_NOTE, "IP Filter: ipf_attach(%x,%x)", dip, cmd); |
| #endif |
| |
| #if !defined(_INET_IP_STACK_H) |
| if ((pfilinterface != PFIL_INTERFACE) || (PFIL_INTERFACE < 2000000)) { |
| cmn_err(CE_NOTE, "pfilinterface(%d) != %d\n", pfilinterface, |
| PFIL_INTERFACE); |
| return EINVAL; |
| } |
| #else |
| #endif |
| |
| switch (cmd) |
| { |
| case DDI_ATTACH: |
| if (fr_running != 0) |
| break; |
| #ifdef IPFDEBUG |
| instance = ddi_get_instance(dip); |
| |
| cmn_err(CE_NOTE, "IP Filter: attach ipf instance %d", instance); |
| #endif |
| |
| (void) ipf_property_update(dip); |
| |
| for (i = 0; ((s = ipf_devfiles[i]) != NULL); i++) { |
| s = strrchr(s, '/'); |
| if (s == NULL) |
| continue; |
| s++; |
| if (ddi_create_minor_node(dip, s, S_IFCHR, i, |
| DDI_PSEUDO, 0) == |
| DDI_FAILURE) { |
| ddi_remove_minor_node(dip, NULL); |
| goto attach_failed; |
| } |
| } |
| |
| ipf_dev_info = dip; |
| /* |
| * Initialize mutex's |
| */ |
| RWLOCK_INIT(&ipf_global, "ipf filter load/unload mutex"); |
| RWLOCK_INIT(&ipf_mutex, "ipf filter rwlock"); |
| RWLOCK_INIT(&ipf_frcache, "ipf cache rwlock"); |
| |
| /* |
| * Lock people out while we set things up. |
| */ |
| WRITE_ENTER(&ipf_global); |
| if ((fr_running != 0) || (ipfattach() == -1)) { |
| RWLOCK_EXIT(&ipf_global); |
| goto attach_failed; |
| } |
| |
| #if !defined(_INET_IP_STACK_H) |
| if (pfil_add_hook(fr_check, PFIL_IN|PFIL_OUT, &pfh_inet4)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_inet4) failed", |
| "pfil_add_hook"); |
| # ifdef USE_INET6 |
| if (pfil_add_hook(fr_check, PFIL_IN|PFIL_OUT, &pfh_inet6)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_inet6) failed", |
| "pfil_add_hook"); |
| # endif |
| if (pfil_add_hook(fr_qifsync, PFIL_IN|PFIL_OUT, &pfh_sync)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_sync) failed", |
| "pfil_add_hook"); |
| #else |
| HOOK_INIT(ipfhook, ipf_hook, "ipf_v4", NULL); |
| |
| ipfipv4 = net_protocol_lookup(0, NHF_INET); |
| if (ipfipv4 == NULL) |
| goto attach_failed; |
| if (net_hook_register(ipfipv4, NH_PHYSICAL_IN, ipfhook)) |
| goto attach_failed; |
| if (net_hook_register(ipfipv4, NH_PHYSICAL_OUT, ipfhook)) |
| goto attach_failed; |
| # ifdef USE_INET6 |
| ipfipv6 = net_protocol_lookup(0, NHF_INET6); |
| if (ipfipv6 == NULL) |
| goto attach_failed; |
| if (net_hook_register(ipfipv6, NH_PHYSICAL_IN, ipfhook)) |
| goto attach_failed; |
| if (net_hook_register(ipfipv6, NH_PHYSICAL_OUT, ipfhook)) |
| goto attach_failed; |
| # endif |
| #endif |
| |
| fr_timer_id = timeout(fr_slowtimer, NULL, |
| drv_usectohz(1000000 * IPF_HZ_MULT / |
| IPF_HZ_DIVIDE)); |
| |
| |
| fr_running = 1; |
| |
| RWLOCK_EXIT(&ipf_global); |
| |
| cmn_err(CE_CONT, "!%s, running.\n", ipfilter_version); |
| |
| return DDI_SUCCESS; |
| /* NOTREACHED */ |
| default: |
| break; |
| } |
| |
| attach_failed: |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: failed to attach\n"); |
| #endif |
| /* |
| * Use our own detach routine to toss |
| * away any stuff we allocated above. |
| */ |
| (void) ipf_detach(dip, DDI_DETACH); |
| return DDI_FAILURE; |
| } |
| |
| |
| static int ipf_detach(dip, cmd) |
| dev_info_t *dip; |
| ddi_detach_cmd_t cmd; |
| { |
| int i; |
| |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: ipf_detach(%x,%x)", dip, cmd); |
| #endif |
| switch (cmd) { |
| case DDI_DETACH: |
| if (fr_refcnt != 0) |
| return DDI_FAILURE; |
| |
| if (fr_running == -2 || fr_running == 0) |
| break; |
| /* |
| * Make sure we're the only one's modifying things. With |
| * this lock others should just fall out of the loop. |
| */ |
| WRITE_ENTER(&ipf_global); |
| if (fr_running == -2) { |
| RWLOCK_EXIT(&ipf_global); |
| return DDI_FAILURE; |
| } |
| fr_running = -2; |
| |
| #if !defined(_INET_IP_STACK_H) |
| if (pfil_remove_hook(fr_check, PFIL_IN|PFIL_OUT, &pfh_inet4)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_inet4) failed", |
| "pfil_remove_hook"); |
| # ifdef USE_INET6 |
| if (pfil_remove_hook(fr_check, PFIL_IN|PFIL_OUT, &pfh_inet6)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_inet6) failed", |
| "pfil_add_hook"); |
| # endif |
| if (pfil_remove_hook(fr_qifsync, PFIL_IN|PFIL_OUT, &pfh_sync)) |
| cmn_err(CE_WARN, "IP Filter: %s(pfh_sync) failed", |
| "pfil_remove_hook"); |
| #else |
| if (net_hook_unregister(ipfipv4, NH_PHYSICAL_IN, ipfhook)) |
| cmn_err(CE_WARN, "IP Filter: v4-IN unregister failed"); |
| if (net_hook_unregister(ipfipv4, NH_PHYSICAL_OUT, ipfhook)) |
| cmn_err(CE_WARN, "IP Filter: v4-OUT unregister failed"); |
| if (net_release(ipfipv4)) |
| cmn_err(CE_WARN, "IP Filter: v4 net_release failed"); |
| # ifdef USE_INET6 |
| if (net_hook_unregister(ipfipv6, NH_PHYSICAL_IN, ipfhook)) |
| cmn_err(CE_WARN, "IP Filter: v6-IN unregister failed"); |
| if (net_hook_unregister(ipfipv6, NH_PHYSICAL_OUT, ipfhook)) |
| cmn_err(CE_WARN, "IP Filter: v6-OUT unregister failed"); |
| if (net_release(ipfipv6)) |
| cmn_err(CE_WARN, "IP Filter: v6 net_release failed"); |
| # endif |
| #endif |
| |
| RWLOCK_EXIT(&ipf_global); |
| |
| if (fr_timer_id != 0) { |
| (void) untimeout(fr_timer_id); |
| fr_timer_id = 0; |
| } |
| |
| /* |
| * Undo what we did in ipf_attach, freeing resources |
| * and removing things we installed. The system |
| * framework guarantees we are not active with this devinfo |
| * node in any other entry points at this time. |
| */ |
| ddi_prop_remove_all(dip); |
| i = ddi_get_instance(dip); |
| ddi_remove_minor_node(dip, NULL); |
| if (i > 0) { |
| cmn_err(CE_CONT, "IP Filter: still attached (%d)\n", i); |
| return DDI_FAILURE; |
| } |
| |
| WRITE_ENTER(&ipf_global); |
| if (!ipfdetach()) { |
| RWLOCK_EXIT(&ipf_global); |
| RW_DESTROY(&ipf_mutex); |
| RW_DESTROY(&ipf_frcache); |
| RW_DESTROY(&ipf_global); |
| cmn_err(CE_CONT, "!%s detached.\n", ipfilter_version); |
| return (DDI_SUCCESS); |
| } |
| RWLOCK_EXIT(&ipf_global); |
| break; |
| default: |
| break; |
| } |
| cmn_err(CE_NOTE, "IP Filter: failed to detach\n"); |
| return DDI_FAILURE; |
| } |
| |
| |
| /*ARGSUSED*/ |
| static int ipf_getinfo(dip, infocmd, arg, result) |
| dev_info_t *dip; |
| ddi_info_cmd_t infocmd; |
| void *arg, **result; |
| { |
| int error; |
| |
| if (fr_running <= 0) |
| return DDI_FAILURE; |
| error = DDI_FAILURE; |
| #ifdef IPFDEBUG |
| cmn_err(CE_NOTE, "IP Filter: ipf_getinfo(%x,%x,%x)", dip, infocmd, arg); |
| #endif |
| switch (infocmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| *result = ipf_dev_info; |
| error = DDI_SUCCESS; |
| break; |
| case DDI_INFO_DEVT2INSTANCE: |
| *result = (void *)0; |
| error = DDI_SUCCESS; |
| break; |
| default: |
| break; |
| } |
| return (error); |
| } |
| |
| |
| /* |
| * look for bad consistancies between the list of interfaces the filter knows |
| * about and those which are currently configured. |
| */ |
| /*ARGSUSED*/ |
| static int fr_qifsync(ip, hlen, il, out, qif, mp) |
| ip_t *ip; |
| int hlen; |
| void *il; |
| int out; |
| void *qif; |
| mblk_t **mp; |
| { |
| |
| frsync(qif); |
| /* |
| * Resync. any NAT `connections' using this interface and its IP #. |
| */ |
| fr_natsync(qif); |
| fr_statesync(qif); |
| return 0; |
| } |
| |
| |
| /* |
| * look for bad consistancies between the list of interfaces the filter knows |
| * about and those which are currently configured. |
| */ |
| int ipfsync() |
| { |
| frsync(NULL); |
| return 0; |
| } |
| |
| |
| /* |
| * Fetch configuration file values that have been entered into the ipf.conf |
| * driver file. |
| */ |
| static int ipf_property_update(dip) |
| dev_info_t *dip; |
| { |
| ipftuneable_t *ipft; |
| u_long i64; |
| char *name; |
| u_int one; |
| int *i32p; |
| int err; |
| |
| #ifdef DDI_NO_AUTODETACH |
| if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, |
| DDI_NO_AUTODETACH, 1) != DDI_PROP_SUCCESS) { |
| cmn_err(CE_WARN, "!updating DDI_NO_AUTODETACH failed"); |
| return DDI_FAILURE; |
| } |
| #else |
| if (ddi_prop_update_int(DDI_DEV_T_NONE, dip, |
| "ddi-no-autodetach", 1) != DDI_PROP_SUCCESS) { |
| cmn_err(CE_WARN, "!updating ddi-no-autodetach failed"); |
| return DDI_FAILURE; |
| } |
| #endif |
| |
| err = DDI_SUCCESS; |
| ipft = ipf_tuneables; |
| for (ipft = ipf_tuneables; (name = (char *)ipft->ipft_name) != NULL; |
| ipft++) { |
| one = 1; |
| i32p = NULL; |
| err = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, |
| 0, name, &i32p, &one); |
| if (err == DDI_PROP_NOT_FOUND) |
| continue; |
| if (*i32p >= ipft->ipft_min && *i32p <= ipft->ipft_max) { |
| if (ipft->ipft_sz == sizeof(int)) { |
| *ipft->ipft_pint = *i32p; |
| } else { |
| i64 = *(u_int *)i32p; |
| *(u_long *)ipft->ipft_pint = i64; |
| } |
| } |
| } |
| |
| return err; |
| } |
| |
| |
| static int iplpoll(dev, events, anyyet, reventsp, phpp) |
| dev_t dev; |
| short events; |
| int anyyet; |
| short *reventsp; |
| struct pollhead **phpp; |
| { |
| u_int xmin = getminor(dev); |
| int revents = 0; |
| |
| if (xmin < 0 || xmin > IPL_LOGMAX) |
| return ENXIO; |
| |
| switch (xmin) |
| { |
| case IPL_LOGIPF : |
| case IPL_LOGNAT : |
| case IPL_LOGSTATE : |
| #ifdef IPFILTER_LOG |
| if ((events & (POLLIN | POLLRDNORM)) && ipflog_canread(xmin)) |
| revents |= events & (POLLIN | POLLRDNORM); |
| #endif |
| break; |
| case IPL_LOGAUTH : |
| if ((events & (POLLIN | POLLRDNORM)) && fr_auth_waiting()) |
| revents |= events & (POLLIN | POLLRDNORM); |
| break; |
| case IPL_LOGSYNC : |
| #ifdef IPFILTER_SYNC |
| if ((events & (POLLIN | POLLRDNORM)) && ipfsync_canread()) |
| revents |= events & (POLLIN | POLLRDNORM); |
| if ((events & (POLLOUT | POLLWRNORM)) && ipfsync_canwrite()) |
| revents |= events & (POLLOUT | POLLOUTNORM); |
| #endif |
| break; |
| case IPL_LOGSCAN : |
| case IPL_LOGLOOKUP : |
| default : |
| break; |
| } |
| |
| if (revents) { |
| *reventsp = revents; |
| } else { |
| *reventsp = 0; |
| if (!anyyet) |
| *phpp = &iplpollhead[xmin]; |
| } |
| return 0; |
| } |
| |
| |
| #if defined(_INET_IP_STACK_H) |
| static int |
| ipf_hook(hook_event_token_t event, hook_data_t data, void *stp) |
| { |
| hook_pkt_event_t *hpe; |
| qpktinfo_t qpi; |
| int out, hlen; |
| ip_t *ip; |
| |
| hpe = (hook_pkt_event_t *)data; |
| qpi.qpi_data = hpe->hpe_hdr; |
| qpi.qpi_mp = hpe->hpe_mp; |
| qpi.qpi_m = hpe->hpe_mb; |
| |
| if (hpe->hpe_ifp > 0) { |
| qpi.qpi_real = hpe->hpe_ifp; |
| out = 0; |
| } else { |
| qpi.qpi_real = hpe->hpe_ofp; |
| out = 1; |
| } |
| qpi.qpi_flags = 0; |
| qpi.qpi_num = hpe->hpe_ifp; |
| qpi.qpi_off = 0; |
| |
| ip = hpe->hpe_hdr; |
| if (ip->ip_v == 4) { |
| hlen = ip->ip_hl << 2; |
| ip->ip_off = ntohs(ip->ip_off); |
| ip->ip_len = ntohs(ip->ip_len); |
| } else { |
| hlen = sizeof(ip6_t); |
| } |
| |
| if (fr_check(hpe->hpe_hdr, hlen, qpi.qpi_real, out, &qpi, qpi.qpi_mp)) |
| return -1; |
| ip = hpe->hpe_hdr; |
| if (ip->ip_v == 4) { |
| ip->ip_off = htons(ip->ip_off); |
| ip->ip_len = htons(ip->ip_len); |
| } |
| return 0; |
| } |
| #endif |