the-tcpdump-group / libpcap

the LIBpcap interface to various kernel packet capture mechanism
https://www.tcpdump.org/
Other
2.65k stars 842 forks source link

some suggested structuring for struct pcap for libpcap 2.0 #984

Open mcr opened 3 years ago

mcr commented 3 years ago

Guy Harris writes, in issue #982:

I presume there's no guarantee that, for an arbitrary plugin, there will be a FILE * corresponding to the input or output stream - or that, even if there is, it will contain the full state of the stream, given that the compression/decompression code might require its own state.

In addition, I'm not sure how control gets handed to the plugin when the sf-pcap.c code attempts to read from or write to the file - or how this handles reading from pcapng files at all (we don't yet support writing pcapng files, so that's not an issue, yet).

Thus, on the read side, we might want to:

add a flag to a struct pcap to indicate whether it's for a live capture or a savefile; put some members of struct pcap into a union, with one element of the union being a structure for live captures and another one being a structure for savefiles; put the rfile member into the union for savefiles, and change it to be a void ; have pcap_file() return NULL for a live capture and return the rfile member, cast to FILE for a savefile; deprecate pcap_file(), as, in the general case, all we can guarantee is that it'll return NULL for a live capture and something non-NULL, but not guaranteed to be usable with standard I/O routines, for a savefile; add pcap_is_savefile(), which returns 0 for live captures and 1 for savefiles (meaning "return the "this is a savefile" flag mentioned above; have read plugins have a read routine as well as an open-for-reading routine, and write plugins have a write routine as well as an open-for-writing routine, with the read and write routines taking fread() and fwrite()-style arguments, but with the void passed, rather than a FILE , as the handle argument, and with the stdio read and write routines being tiny wrappers around fread() and fwrite(); have a pointer to the read routine, and a pointer to the handle returned by the open-for-reading routine, be members of the savefile structure mentioned above; have the sf-pcap.c and sf-pcapng.c code call the read routine, handing it the pointer to the handle, whenever they nee to read data from the savefile.

mcr commented 3 years ago

1) continue to build a libpcap.so.0.8 with a compatible ABI (could be separate file though!) I know that this applies to Debian derived systems. I don't know if it applies to Fedora/RPM based systems.

mcr commented 3 years ago

2) build a new libpcap.so.2.0 ABI. This would remove much of the cruft, and we would have the opportunity to construct better abstractions, such as that pcap_dumper_t is not a FILE *. (Probably have a new name)

mcr commented 3 years ago

3) drop autoconf/automake/configure, and just use CMake for libpcap. (as much as I dislike cmake, and find the documentation really hard to understand)

guyharris commented 3 years ago
  1. continue to build a libpcap.so.0.8 with a compatible ABI

It's both ABI and API; if we're going to change the API, and I presume that's what you're talking about, that would make a difference even on systems that don't care about the ABI.

(could be separate file though!) I know that this applies to Debian derived systems.

For Ubuntu, at least, Ubuntu 20.04 has, on my 64-bit VM, /lib/x86_64-linux-gnu/libpcap.so.1.9.1, with /lib/x86_64-linux-gnu/libpcap.so.0.8 and /lib/x86_64-linux-gnu/libpcap.so as symbolic links to it, and with tcpdump linked with libpcap.so.0.8. In Ubuntu 12.10, the libraries are in /usr/lib/x86_64-linux-gnu, and the library's file name is libpcap.so.1.1.1.

That's probably similar to, and perhaps identical to, Debian and other Debian derivatives.

I don't know if it applies to Fedora/RPM based systems.

For Fedora, Fedora 31 has, on my 64-bit VM, /lib64/libpcap.so.1.9.1, with /lib64/libpcap.so.1 a symlink to it, and has tcpdump linked with libpcap.so.1. In Fedora 20, the only difference is that it's libpcap.so.1.5.3.

That's presumably true of RHEL (and any other "Red Hat" predecessors), as well as CentOS and other related distributions.

So both of them have the convention is "the shared library's file name has the libpcap version number, but there's a libpcap.so.{something} symlink to it, and programs are linked with libpcap.so.{something}, where {something} is a number that doesn't change with updates to libpcap". The {something} is

I have only one OpenSuSE VM, running Leap 15.2; it appears to follow the Fedora convention of .so.1.

Section 3 "Shared Libraries" of the Program Library HOWTO speaks, in subsection 3.1.1 "Shared Library Names", of shared libraries having an "soname" and a "real name":

Every shared library has a special name called the soname''. The soname has the prefixlib'', the name of the library, the phrase .so'', followed by a period and a version number that is incremented whenever the interface changes (as a special exception, the lowest-level C libraries don't start withlib''). A fully-qualified soname includes as a prefix the directory it's in; on a working system a fully-qualified soname is simply a symbolic link to the shared library's ``real name''.

Every shared library also has a ``real name'', which is the filename containing the actual library code. The real name adds to the soname a period, a minor number, another period, and the release number. The last period and release number are optional. The minor number and release number support configuration control by letting you know exactly what version(s) of the library are installed. Note that these numbers might not be the same as the numbers used to describe the library in documentation, although that does make things easier.

In addition, there's the name that the compiler uses when requesting a library, (I'll call it the ``linker name''), which is simply the soname without any version number.

The "linker name" would just be "libpcap", as in "take the argument to -l and prepend "lib" to get the linker name".

I don't know where the "soname"/"real name" convention came from. The whole shared library mechanism goes back to SunOS 4.0, where there was no such notion - for one thing, there were a lot fewer free-software libraries, so most of the libraries shipped with SunOS came from Sun, and they didn't have package version numbers, just shared library version numbers. SVR4 adopted that mechanism, applying it to ELF files rather than the a.out files from SunOS 4 and before; that's what most if not all of the free-software UN*Xes, as well as Solaris, use.

So the "soname"/"real name" convention may have become a generic Linuxism, originating amongst one or more distributions.

FreeBSD 12 just has /lib/libpcap.so.0, which is the shared library file and which has no symbolic links to it. DragonFly BSD 5.8 has /usr/lib/libpcap.so.3, with /usr/lib/libpcap.so as a symbolic link to it. NetBSD 9.0 has /lib/libpcap.so.7.0, with /lib/libpcap.so.7 and /lib/libpcap.so as symbolic links to it. OpenBSD 6.6 has /usr/lib/libpcap.so.8.4, with no symlinks to it, and with tcpdump possibly linked directly to that, so it wouldn't even work with libpcap.so.8.5. (OpenBSD's ldd is... different, so I'm not sure how to interpret its output. They also maintain their own versions of libpcap and tcpdump, so what we do might not matter that much anyway.)

If we go to a recent version of Poppa SunOS 5, in Solaris 11, it appears to follow the Linux convention, with libpcap 1.5.1. Whether they, or Linux, introduced the convention is indeterminate, but if third-party free-software libraries as part of core Solaris is a recent thing, it might well have been invented by the Linux folk and adopted by the Solaris folk, even though the underlying shared library mechanism went the other way.

macOS has its own conventions. A shared library file has, I think, a LC_ID_DYLIB in it that contains a "compatibility version" and a "current version" for the library; I'm not sure there is "a" convention for the library name - libpcap is /usr/lib/libpcap.A.dylib, to which /usr/lib/libpcap.dylib is a symbolic link. Some other shared libraries have only a /usr/lib/libwhatever.dylib file, and some others (possibly from open-source projects) have /usr/lib/libwhatever.{number}.dylib, where {number} looks like a version number, and with a /usr/lib/libwhatever.dylib symlink to it. I'm not sure the name, other than "libwhatever" and ".dylib", is significant; the version number may be decorative, and the "compatibility version" and "current version" may be all that matters.

Here endeth the lesson (in "more than you ever wanted to know about UN*X history and UN*X shared libraries").

Executive summary: for any given platform, future versions of libpcap that are binary compatible with older versions should have the relevant part of the relevant version number remain the same as those older versions, and the version number is ultimately provided by the platform supplier. That version number differs from platform to platform.

And, yes, we should continue to offer that.

guyharris commented 3 years ago

build a new libpcap.so.2.0 ABI. This would remove much of the cruft, and we would have the opportunity to construct better abstractions, such as that pcap_dumper_t is not a FILE *. (Probably have a new name)

The original SunOS 4.x shared library mechanism gave the library a major and minor version number; the minor version number was to be incremented if a backwards-compatible but not forwards-compatible change was made to the ABI (such as adding a new routine to the ABI, or adding new flag bits to a flag argument, or adding new enumerated values to an enumerated-type argument), and the major version number was to be incremented if a non-backwards-compatible (and thus non-forwards-compatible) change was made (such as eliminating a routine from the ABI, renaming a routine in the ABI, or changing the function signature of a routine from the ABI).

A program could link, at run time, with a library with a major version number = the one with which it was built and a minor version number >= the one with which it was built. It could also link with one that had a major version number = the one with which it was built and a minor version number < the one with which it was built, but a warning message would be printed, because the program might use a routine not in the library, pass in a flag bit not supported by the library, pass in an enum value not supported by the library, etc., which probably wouldn't work, but if the program didn't do so, it should work.

A program could not link, at run time, with a library with a major version number != the one with which it was built.

I don't know if anybody ever did that. There's no reason I can think of not to rename that library; it doesn't reduce compatibility (a program linked with libfoo.3 won't work any better on a system with only libfoo.4 than it would work on a system without libfoo but with libbar.3.

Renaming the library would make it clearer that this is a different library; it may attempt to achieve the same goals as the other library, but it's not compatible, and you're going to have to change your code to use it.

I don't know what platforms would pick up libpcapng, or libnewpcap, or whatever. The main Linux distributions and the *BSDs might; I can't speak for macOS or Solaris or AIX, and I don't know what Npcap would do.

One possible advantage of adding APIs to the existing binary-compatible library would be if that made it more likely to be adopted by upstream suppliers. macOS, for example, might pick it up, especially if we provided APIs sufficient to fully support pcapng and various Appleisms (such as PKTAP), as it'd mean they could devote less energy to supporting their (undocumented except in the source code and uninstalled man pages in the source tree) pcap extensions in their own version of libpcap.

However, it means dragging around cruft for a while. Apple might, eventually, kill off deprecated APIs/ABIs, but I'm not sure whether they'd do so in this case.

guyharris commented 3 years ago

drop autoconf/automake/configure, and just use CMake for libpcap. (as much as I dislike cmake, and find the documentation really hard to understand)

Wireshark eventually did that, but it took a while.

mcr commented 3 years ago

The original SunOS 4.x shared library mechanism gave the library a major and minor version number; the minor version number was to be incremented if a backwards-compatible but not forwards-compatible change was made to the ABI (such as adding a new routine to the ABI, or adding new flag bits to a flag argument, or adding new enumerated values to an enumerated-type argument), and the major version number was to be incremented if a non-backwards-compatible (and thus non-forwards-compatible) change was made (such as eliminating a routine from the ABI, renaming a routine in the ABI, or changing the function signature of a routine from the ABI).

Yes, this is what I "grew up" with, and which I understand. We haven't broken the ABI since 0.8, so anything linked against the libpcap 0.8.0 header files will continue to run with today's library. That's why the symlink.

It is my intention to eliminate a bunch of functions in a "2.0" API/ABI. How we get there is open for discussion. We could, for instance, start by removing them from the API (header files), but not from the library, retaining the ABI. Or, we could have a libpcap 0.8-compat .so, that includes the additional ABI calls, and then links against libpcap 2.0. Or, we could compile everything twice.

guyharris commented 3 years ago

The original SunOS 4.x shared library mechanism gave the library a major and minor version number; the minor version number was to be incremented if a backwards-compatible but not forwards-compatible change was made to the ABI (such as adding a new routine to the ABI, or adding new flag bits to a flag argument, or adding new enumerated values to an enumerated-type argument), and the major version number was to be incremented if a non-backwards-compatible (and thus non-forwards-compatible) change was made (such as eliminating a routine from the ABI, renaming a routine in the ABI, or changing the function signature of a routine from the ABI).

Yes, this is what I "grew up" with, and which I understand. We haven't broken the ABI since 0.8,

The last time we broke it was, I think, in 0.5, when some DLT_ values were changed to try to make them the same on all platforms. In 0.6, we reverted that, as some compiled code might, for example, be checking for DLT_RAW by checking for its value on the platform for which it's compiled, and will get surprised by it being 100 rather than 12 (on everything but OpenBSD) or 14 (on OpenBSD).

I don't know why Debian picked 0.8; perhaps we were up to 0.8 at the point at which they said "Oh, does this have a stable ABI? Let's just have the soname use 0.8, then, and keep it there".

Other distributions may have different policies. Fedora 16 had /usr/lib/libpcap.so.0.9.8, with /usr/lib/libpcap.so.0.9 and /usr/lib/libpcap.so as symlinks to it, with tcpdump linked to libpcap.so.0.9, so I guess, prior to our 1.0 release, they used 0.x, for the current value of x, and then switched to just 1 when we came out with 1.0.

It is my intention to eliminate a bunch of functions in a "2.0" API/ABI. How we get there is open for discussion.

If we're going to do that, I might vote for just changing the name of the library. I don't know whether Sun ever changed the major version number of any library, other than perhaps between SunOS 4 (the last 4BSD-plus-some-System-V version) and SunOS 5 (the SVR4 version). That may be an indication that they realized that would be disruptive.

One library that has changed the major version number is libnl; this causes problems with libraries linked with some version of libnl, if a program using that library is linked with a different version of libnl.

I'd go with libpcapng or something such as that (given that one reason for significant API, and thus ABI, changes would be full support of pcapng).

But I'd prefer to do it in a fashion that makes it easy for OSes that want to support older code to do so.

We could, for instance, start by removing them from the API (header files), but not from the library, retaining the ABI.

That works for binary compatibility, but not for source compatibility. (Apple, annoyingly, did that for OpenSSL, which removes some decryption capabilities from tcpdump if you build it from source on macOS; Apple themselves cheat, by making the headers available to their build procedure for tcpdump.)

Or, we could have a libpcap 0.8-compat .so, that includes the additional ABI calls, and then links against libpcap 2.0.

I'd be inclined to do that, as it preserves both API and ABI, although I'd call the old library libpcap and the new one libpcapng or something such as that.

Note, though, that, for backwards compatibility, pcap_open_live()'s implementation uses a non-API hack, so if we were to have pcap_open_live() in libpcap (along, presumably, with pcap_create() and pcap_activate()), with the routines used by those routines in libpcapng, we'd somehow have to allow that to work.

mcr commented 3 years ago

Guy Harris notifications@github.com wrote: mcr> It is my intention to eliminate a bunch of functions in a "2.0" mcr> API/ABI. How we get there is open for discussion.

> If we're going to do that, I might vote for just changing the name of
> the library.  I don't know whether Sun ever changed the major version
> number of any library, other than perhaps between SunOS 4 (the last
> 4BSD-plus-some-System-V version) and SunOS 5 (the SVR4 version).  That
> may be an indication that they realized that would be disruptive.

we could do that. I think that 90% of the API changes can be accomplished by turning certain functions into #defines in the header, along with some tunable #deprecation warning. Rebuilding code ought to work. The other 10% is, I think, the applications that expect pcap_dumper_t to be a FILE, and maybe they are just broken. Or maybe we can provide a shim that finds the pcap_new_dumper_t from a short map of FILE->.

> One library that *has* changed the major version number is libnl; this
> causes problems with libraries linked with some version of libnl, if a
> program using that library is linked with a *different* version of
> libnl.

Agreed.

> I'd go with libpcapng or something such as that (given that one reason
> for significant API, and thus ABI, changes would be full support of
> pcapng).

I hate "ng" and I wish we didn't embed that... because "NEXT" is always coming.

> That works for binary compatibility, but not for source
> compatibility. (Apple, annoyingly, did that for OpenSSL, which removes
> some decryption capabilities from tcpdump if you build it from source
> on macOS; Apple themselves cheat, by making the headers available to
> their build procedure for tcpdump.)

ha.

>> Or, we could have a libpcap 0.8-compat .so, that includes the
>> additional ABI calls, and then links against libpcap 2.0.

> I'd be inclined to do that, as it preserves both API and ABI, although
> I'd call the old library libpcap and the new one libpcapng or something
> such as that.

okay, except that we disagree on the "ng" part, I'm okay with that.

> Note, though, that, for backwards compatibility, `pcap_open_live()`'s
> implementation uses a non-API hack, so if we were to have
> `pcap_open_live()` in libpcap (along, presumably, with `pcap_create()`
> and `pcap_activate()`), with the routines used by those routines in
> libpcapng, we'd somehow have to allow that to work.

I'm not sure what you mean here. Are you saying that pcap_open_live() is a #define?

-- ] Never tell me the odds! | ipv6 mesh networks [ ] Michael Richardson, Sandelman Software Works | IoT architect [ ] mcr@sandelman.ca http://www.sandelman.ca/ | ruby on rails [

guyharris commented 3 years ago

The other 10% is, I think, the applications that expect pcap_dumper_t to be a FILE*, and maybe they are just broken.

The main reason why they might do that would be if they need to, for example, do an fflush() on the file being written, and they were written before we added pcap_dump_flush(), so the only choice was to cheat.

The current pcap file writing API has some problems over and above a pcap_dumper_t * being an alias for a FILE *, which, in addition to making "write plugins" require that the standard I/O routines support plugins, also means we can't use a larger buffer size.

For example, pcap_dump() was designed so that it could be a callback for pcap_loop() and pcap_dispatch(), so it has no return value; this means it can't report errors.

The inability to directly specify the link-layer type and snapshot length, requiring pcap_open_dead() to be used, is a nuisance as well.

So I'd like to have a new pcap file writing API, fixing those issues, in addition to a pcapng file writing API, whether we use the same API for both or have two separate APIs.

I'd also want a different capture/read API, to handle reading pcapng files with full support for pcapng features, and to supply additional information during live captures (packet direction, FCS presence information, information about the interface(s) on which packets are arriving, etc.).

I'd probably continue to use a callback (because, at least with Linux memory-mapped capture, slots in the capture buffer eventually need to be released back to the kernel, and that requires a way for the code using the API to indicate that it's done with the packet buffer, and returning from the callback is a fairly convenient way to do that). However, I'd have the callback return a value that could be used to report errors and stop the loop.

guyharris commented 3 years ago

I hate "ng" and I wish we didn't embed that... because "NEXT" is always coming.

I'm not committed to "ng", just as long as we change the name.

guyharris commented 3 years ago

Are you saying that pcap_open_live() is a #define?

No, it's a wrapper around pcap_create() and pcap_activate(), but it does p->oldstyle = 1; before calling pcap_activate() because, as the comment says:

    /*        
     * Mark this as opened with pcap_open_live(), so that, for
     * example, we show the full list of DLT_ values, rather
     * than just the ones that are compatible with capturing
     * when not in monitor mode.  That allows existing applications
     * to work the way they used to work, but allows new applications
     * that know about the new open API to, for example, find out the
     * DLT_ values that they can select without changing whether
     * the adapter is in monitor mode or not.
     */                     
fxlb commented 3 years ago

drop autoconf/automake/configure, and just use CMake for libpcap. (as much as I dislike cmake, and find the documentation really hard to understand)

Wireshark eventually did that, but it took a while.

If I remember well, some people on the mailing list want to keep autoconf/configure...

rfrancoise commented 3 years ago

I don't know why Debian picked 0.8; perhaps we were up to 0.8 at the point at which they said "Oh, does this have a stable ABI? Let's just have the soname use 0.8, then, and keep it there".

That's basically it, yes. Once upon a time, I collected some notes about this topic here:

https://people.debian.org/~rfrancoise/libpcap-faq.html

Regarding the ABI name ("soname") for libpcap 2.0, from my perspective we don't need to keep providing the old ABI and go through hoops to generate an old-ABI binary alongside the new-ABI build outputs. Changing ABI is a routine event in Debian library packaging and we'll rebuild all the dependencies to match, it's not a big deal.

guyharris commented 3 years ago

Changing the ABI is a bit bigger issue for Solaris and macOS, where binary compatibility is expected by third-party developers.

fxlb commented 3 years ago

Should we keep rpcapd code in the libpcap repository?

guyharris commented 3 years ago

Should we keep rpcapd code in the libpcap repository?

Not doing so would require putting the common rpcap protocol headers and code somewhere, whether:

That would also require that the "socket utilities" be put somewhere else as well. I have plans to add support for additional flavors of "capture over the network", some of which (capturing traffic from some servers that run on wireless APs, capturing mobile telephony network packets sent out by GSMTAP servers) will also use the socket utilities, so perhaps a case could be made for exporting them from libpcap (especially if we support plugins for capture mechanisms).