Axel Simon wrote:
Does anyone know why these are in the IO monad? Aren't they pure functions converting between dotted-decimal strings and a 32-bit network byte ordered binary value?
I guess the answer is no for both: The first one can fail
That doesn't mean that it should be in the IO monad; using Maybe would suffice.
Agreed. Perhaps I wasn't clear enough in my original question. There appears to be no intrinsic reason why they should be in the IO monad.
Hence ntoa needs to be an IO action so that the value is read immediately before the next ntoa call is executed.
That shouldn't be an issue so long as the buffer contents are converted to a Haskell String before the function is called again within the same thread.
However, I wouldn't rely upon all implementations of inet_ntoa() being thread-safe.
What you could do is to apply unsafePerformIO to
[snip]
Or you could just re-implement the functions in Haskell.
Apart from the re-entrancy issues with inet_ntoa(), many implementations of inet_addr() have misfeatures, e.g. allowing octets to be expressed in octal or hex, or allowing numbers outside of the 0-255 range (in which case, the top bits overflow into the next octet).
Perhaps better would be: inet_addr' :: (Octet,Octet,Octet,Octet) -> HostAddress inet_ntoa' :: HostAddress -> (Octet,Octet,Octet,Octet) I see Peter Simons has already written something: http://cryp.to/hsdns/docs/Network.IP.Address.html#v%3Aha2tpl Dominic.
Dominic Steinitz writes:
inet_addr' :: (Octet,Octet,Octet,Octet) -> HostAddress inet_ntoa' :: HostAddress -> (Octet,Octet,Octet,Octet)
I see Peter Simons has already written something:
http://cryp.to/hsdns/docs/Network.IP.Address.html#v%3Aha2tpl
As usual, I didn't write as much as I would have liked, and I really don't know whether to place a ':-)' or a ':-(' after that statement. Anyway, in my humble opinion aiming for better IPv4 address support would be aiming too low in this day and age. HostAddress really ought to be an abstract data type which represents a "network address" in general. We have quite a few of them already: there is HostAddress, SockAddr, and PortID; and each of them may represent the exact same thing or an entirely different thing, depending on how it was initialized. Consequently, we have all kinds of variants in the API too: connectTo vs. connect, Network.accept vs. Network.Socket.accept, listen vs. listenOn, etc. Considering that as of today _none_ of these variations has the slightest idea what IPv6 is, it might be worth trying to unify that. I would be curious to know how other programming languages have solved this problem. C's solution is the one we all know and love, and C++ added pretty much nothing to that in the last 10 years or so. How about others? Peter
Peter Simons <simons@cryp.to> writes:
I would be curious to know how other programming languages have solved this problem. C's solution is the one we all know and love, and C++ added pretty much nothing to that in the last 10 years or so. How about others?
I once looked at .NET and was not impressed: too many variations of types which hold various kinds of addresses, hard-coded choices which are left more polymorphic in the BSD sockets API, and mono performs packing and unpacking of addresses on operations. Java also was not very interesting, I don't remember the details. For my language Kogut I have designed and implemented the following as a wrapper around BSD sockets (warning: not tested on real-life applications). In a few places I rely on dynamic typing and optional keyword parameters, so it would have to be changed a bit for Haskell. * Address families and protocol families of type FAMILY: InetFamily, Inet6Family (optional), and UnixFamily. * Socket types of type SOCKET_TYPE: AnyType, DgramType, RawType (optional), SeqPacketType, and StreamType. * Protocols are represented by strings or numbers. Function GetProtocolInfo resolves a protocol to a record of type PROTOCOL_INFO with fields code, name, and aliases. * Sockets of type SOCKET. It may be the same as RAW_FILE (currently it is always the same). Function Socket creates an unconnected socket. It has keyword parameters family (default: DefaultFamily, which is Inet6Family or InetFamily by default), type (default: StreamType), and protocol (default: depends on the family and type). * Function SocketPair creates a pair of sockets connected to each other. It has the same arguments as Socket. * Addresses of type ADDRESS with fields family and data. Data is exposed as a byte array whose format depends on the family and on the system. Data is also exposed as a set of fields which depend on the address family: - InetFamily: port (0..65535), addr (a list of 4 bytes); some constants for the addr field: InetAddrAny, InetAddrBroadcast and InetAddrLoopback - Inet6Family: port (0..65535), flowInfo (0..2**31-1), addr (a list of 16 bytes), scopeId (0..2**31-1); some constants for the addr field: Inet6AddrAny, Inet6AddrLoopback; some predicates on the addr field: IsInet6AddrUnspecified, IsInet6AddrLoopback, IsInet6AddrMulticast, IsInet6AddrLinkLocal, IsInet6AddrSiteLocal, IsInet6AddrV4Mapped, IsInet6AddrV4Compat, IsInet6AddrMcNodeLocal, IsInet6AddrMcLinkLocal, IsInet6AddrMcSiteLocal, IsInet6AddrMcOrgLocal, IsInet6AddrMcGlobal - UnixFamily: path (string) There is a constructor Address with parameter family and named parameters: either data or family-dependent fields. The UnspecAddress constant can be used with Connect for clearing the default destination of datagram sockets. Even though data is a mutable byte array, addresses are immutable; a new array is created each time the data field is accessed. Addresses are compared and hashed by value. * Function GetAddrInfo resolves a host (string) and port (string or number) to a list of records of type ADDR_INFO with fields family, type, protocol, address, and canonName. Either the host or the port may be missing, but not both. Other keyword parameters of GetAddrInfo are family (default: find out automatically), type (default: StreamType), protocol (default: depends on the family and type), and flags: passive, canonName, numericHost, numericServ, v4Mapped, all, addrConfig (default: v4Mapped and addrConfig are True, others are False). * Function GetFirstAddrInfo returns the first element of the list returned by GetAddrInfo. * Function GetNameInfo translates an address to a pair of strings: host and port. It takes keyword parameters after the address: noFQDN, numericHost, nameReqd, numericServ, numericScope, and dgram. * Functions Connect and Bind take a socket and keyword parameters: either address, or arguments for GetFirstAddrInfo. * Function Listen takes a socket and a keyword parameter backlog (default: DefaultBacklog, which is 5 by default). * Function Accept takes a socket and returns a pair: a socket for the connection and the peer address. * Function Shutdown takes a socket and a symbol #read, #write, or #both. * Functions GetSocketName and GetPeerName take a socket and return an address. * Function SocketOption takes a socket, protocol level (SocketLevel, IPProtoIP, IPProtoIPv6, IPProtoICMP, IPProtoRAW, IPProtoTCP, IPProtoUDP), option name (#acceptConn, #broadcast, #debug, #dontRoute, #error, #keepAlive, #linger, #oobInline, #rcvBuf, #rcvLoWat, #rcvTimeO, #reuseAddr, #sndBuf, #sndLoWat, #sndTimeO, #type, #noDelay), and either gets the socket option, or takes the new value as the last argument and sets the option. The type of the value depends on the option name. * Function Receive takes a socket, byte array to fill, maximum size, and keyword parameters: peek, oob, and waitAll; it returns a boolean indicating the end of transmission. Function ReceiveFrom takes the same parameters and returns the sender address or Null if the address is unavailable. * Function Send takes a socket, byte array to send, maximum size, and keyword parameters: eor and oob. Function SendTo additionally takes the recipient address after the socket. * Function SocketStreams exposes a socket as a pair of streams: input stream and output stream of bytes. Closing both streams closes the socket. * Functions SocketTextStreams and SocketBinaryStreams expose a socket as a pair of {Text,Binary}{Reader,Writer}. They ensure that the writer is flushed before asking for input. They take keyword parameters for readers and writers. * Function ClientSocket is a convenience wrapper for Socket and Connect. Function ServerSocket is a convenience wrapper for Socket, Bind, and Listen. They take combined keyword parameters for the functions they use. The family of the address (if the address is given) or the result of resolving the name (if the host or port are given) influences the family, type and protocol of the socket. * Functions {ClientSocket,Accept}{,Text,Binary}Streams are convenience wrappers for {ClientSocket,Accept} and Socket{,Text,Binary}Streams. They take combined keyword parameters for the functions they use. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
Peter Simons <simons@cryp.to> writes:
Considering that as of today _none_ of these variations has the slightest idea what IPv6 is, it might be worth trying to unify that.
Einar Karttunen's network-alt supports IPv6, datagram, and more: http://www.cs.helsinki.fi/u/ekarttun/network-alt/ -- It seems I've been living two lives. One life is a self-employed web developer In the other life, I'm shapr, functional programmer. | www.ScannedInAvian.com One of these lives has futures (and subcontinuations!)| --Shae Matijs Erisson
Shae Matijs Erisson writes:
Einar Karttunen's network-alt supports IPv6, datagram, and more: http://www.cs.helsinki.fi/u/ekarttun/network-alt/
Duh, I didn't even know this library existed! Thanks for the pointer. Judging from a quick glance, the code seems to marshal the POSIX API: type SockAddrLen = Int data SockAddrT type SockAddr = ForeignPtr SockAddrT data SocketAddress = SA !SockAddr !SockAddrLen I'm not sure whether that's a useful representation. It works, of course, but it appears that an address is essentially opaque (unless you want to do more FFI things). It doesn't really unify different types of network addresses either. Note, for example, that you can't pass such an address to the 'connectTCP' function. Peter
Peter Simons <simons@cryp.to> writes:
Judging from a quick glance, the code seems to marshal the POSIX API:
type SockAddrLen = Int data SockAddrT type SockAddr = ForeignPtr SockAddrT data SocketAddress = SA !SockAddr !SockAddrLen
I'm not sure whether that's a useful representation. It works, of course, but it appears that an address is essentially opaque (unless you want to do more FFI things). It doesn't really unify different types of network addresses either. Note, for example, that you can't pass such an address to the 'connectTCP' function.
That is one of the few working representations. The user of the library is not aware how the socket addresses are represented internally, so it is not a problem. Lifting the information to Haskell level seems quite pointless, as it is usually just fed back to the C functions. Also IPv6 addresses sometimes need scopes - lifting this would make things even more messy. The current way is to ignore adress families as much as possible while still supporting multiple ones. E.g. the following works with both IPv4 and IPv6 in network-alt: googleMainPage = do h <- connectTCP "www.google.com" "http" hPutStr h "GET / HTTP/1.0\r\n\r\n" hFlush h hGetContents h >>= print hClose h - Einar Karttunen
Einar Karttunen writes:
Lifting [network address information] to Haskell level seems quite pointless, as it is usually just fed back to the C functions.
Well, I certainly _do_ need it.
The current way is to ignore adress families as much as possible while still supporting multiple ones. E.g. the following works with both IPv4 and IPv6 in network-alt:
googleMainPage = do h <- connectTCP "www.google.com" "http"
That's true. However, it doesn't work with anything _but_ IPv4 and IPv6. I think it is unsatisfactory that you need a different function to connect to a TCP target than to connect to a Unix stream socket. The longer I think about this whole thing, the more I am convinced that using URIs is the answer. Peter
At 14:05 12/05/05 +0200, Peter Simons wrote:
The longer I think about this whole thing, the more I am convinced that using URIs is the answer.
FWIW, the revised URI parsing code [2][3] in the latest libraries includes support for IPv6 literals, as specified by RFC 3986 [1]. #g -- [1] ftp://ftp.rfc-editor.org/in-notes/rfc3986.txt [2] http://www.haskell.org/ghc/docs/latest/html/libraries/network/Network.URI.ht... [3] http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/libraries/network/Network/... ------------ Graham Klyne For email: http://www.ninebynine.org/#Contact
Graham Klyne writes:
The longer I think about this whole thing, the more I am convinced that using URIs is the answer.
FWIW, the revised URI parsing code [2][3] in the latest libraries includes support for IPv6 literals, as specified by RFC 3986 [1].
Thanks for the pointer, Graham. I knew that the URI code had been written a while ago, but never realized how extensive the changes were! Great job. Now the only problem is that the module doesn't expose the functions we would need; such as Network.URI.host, for instance. Would it be possible to factor those parser out into a Text.ParserCombinators.Parsec.Rfc3986 module? Maybe we could even merge those parsers with the ones I have here: http://cryp.to/hsemail/docs/index.html RFC grammars are often very similar after all. Peter P.S.: In the definition host = ipLiteral <|> try ipv4address <|> regName it looks as if the 'try' modifier should be given for the first alternative; not for the second. I may be wrong though.
At 12:03 15/05/05 +0200, Peter Simons wrote:
Graham Klyne writes:
The longer I think about this whole thing, the more I am convinced that using URIs is the answer.
FWIW, the revised URI parsing code [2][3] in the latest libraries includes support for IPv6 literals, as specified by RFC 3986 [1].
Thanks for the pointer, Graham. I knew that the URI code had been written a while ago, but never realized how extensive the changes were! Great job.
Thanks! (It's essentially a complete rewrite.)
Now the only problem is that the module doesn't expose the functions we would need; such as Network.URI.host, for instance. Would it be possible to factor those parser out into a
Text.ParserCombinators.Parsec.Rfc3986
This seems a reasonable idea.
module? Maybe we could even merge those parsers with the ones I have here:
http://cryp.to/hsemail/docs/index.html
RFC grammars are often very similar after all.
I think it could be useful to have a collection of RFC parsers along these lines. I'm not entirely sure what you mean my "merge" -- I think the RFC distinctions should be maintained. One might also consider that my unit test code (see ../tests directory) contains some framework functions that might be used to create a test case library. One thought: in some cases, my URI parser code depends on the URI data types that I declare (for the return values), so it might not separate as cleanly as one might like -- I think it would be confusing if the data type declarations were separated from the URI module. I suppose the parser combinators might return tuples that are assembled by the URI code. Also, if separating the combinators, one might want to make the monadic parser type more general. Hmmm.... I did something like this for another bit of code somewhere, but I forget where. I think I made the parser polymorphic in the state value, which was not referenced. This way, the parsers can be referenced by other, more specific combinators that do use the state value. Currently, the state type is ().
P.S.: In the definition
host = ipLiteral <|> try ipv4address <|> regName
it looks as if the 'try' modifier should be given for the first alternative; not for the second. I may be wrong though.
My initial (lame) answer is that it passes all the available test cases. More seriously, if you think there's something that breaks the current code then a test case should be created. Looking at the production (copy below), the first case doesn't need a 'try' because if the initial character is a '[' then no other parse is possible. But for the ipv4literal production backtracking may be needed; consider: 111.222.333.mydomain.org #g -- ***** Selected host productions: [[ host :: URIParser String host = ipLiteral <|> try ipv4address <|> regName ipLiteral :: URIParser String ipLiteral = do { char '[' ; ua <- ( ipv6address <|> ipvFuture ) ; char ']' ; return $ "[" ++ ua ++ "]" } <?> "IP address literal" : ipv4address :: URIParser String ipv4address = do { a1 <- decOctet ; char '.' ; a2 <- decOctet ; char '.' ; a3 <- decOctet ; char '.' ; a4 <- decOctet ; return $ a1++"."++a2++"."++a3++"."++a4 } ]] ------------ Graham Klyne For email: http://www.ninebynine.org/#Contact
Peter Simons <simons@cryp.to> writes:
Lifting [network address information] to Haskell level seems quite pointless, as it is usually just fed back to the C functions.
Well, I certainly _do_ need it.
You can certainly get it: getHost mySocketAddress niNumerichost getServ mySocketAddress niNumericserv
That's true. However, it doesn't work with anything _but_ IPv4 and IPv6. I think it is unsatisfactory that you need a different function to connect to a TCP target than to connect to a Unix stream socket.
Having a separate TCP connect function is just for niceness - of course one can use an aproach to go from URIs to sockets having a case statement for each scheme. But what URI should represent e.g. unix datagram sockets? Having an URI connection function would be nice, but having it as the primary alternative would not be very nice. - Einar Karttunen
Einar Karttunen writes:
Well, I certainly _do_ need [a representation of network addresses in Haskell].
You can certainly get it: getHost mySocketAddress niNumerichost getServ mySocketAddress niNumericserv
Um, yes, but 'String' isn't a very good representation for manipulating network addresses, IMHO.
[URIs might be the answer]
But what URI should represent e.g. unix datagram sockets?
I don't think it's worth even trying to hide both stream- and packet-oriented services behind the same API. These are completely different things, treated them differently is fine, IMHO. Peter
Peter Simons <simons@cryp.to> writes:
[URIs might be the answer]
But what URI should represent e.g. unix datagram sockets?
I don't think it's worth even trying to hide both stream- and packet-oriented services behind the same API. These are completely different things, treated them differently is fine, IMHO.
But they don't differ in addressing. In BSD sockets the difference between streams and packets lies in "socket type", while addresses are split into "address families" which bijectively correspond to "protocol families". -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
On Thu, 12 May 2005, Marcin 'Qrczak' Kowalczyk wrote:
But they don't differ in addressing. In BSD sockets the difference between streams and packets lies in "socket type", while addresses are split into "address families" which bijectively correspond to "protocol families".
I believe there are some obscure protocol families that have more than one address family, which is why the two concepts exist in the API. However there's an enormous amount of code that muddles them up, but this doesn't matter for the Internet protocols. Tony. -- f.a.n.finch <dot@dotat.at> http://dotat.at/ BISCAY: WEST 5 OR 6 BECOMING VARIABLE 3 OR 4. SHOWERS AT FIRST. MODERATE OR GOOD.
Tony Finch <dot@dotat.at> writes:
But they don't differ in addressing. In BSD sockets the difference between streams and packets lies in "socket type", while addresses are split into "address families" which bijectively correspond to "protocol families".
I believe there are some obscure protocol families that have more than one address family, which is why the two concepts exist in the API.
Single Unix Specification v3 specifies only AF_* constants, used for both. Linux man socket says: NOTE The manifest constants used under BSD 4.* for protocol families are PF_UNIX, PF_INET, etc., while AF_UNIX etc. are used for address fami- lies. However, already the BSD man page promises: "The protocol family generally is the same as the address family", and subsequent standards use AF_* everywhere. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
Einar Karttunen wrote:
But what URI should represent e.g. unix datagram sockets? Having an URI connection function would be nice, but having it as the primary alternative would not be very nice.
Could URI schemes like those in the Java Generic Connection Framework (see: http://developers.sun.com/techtopics/mobility/midp/articles/genericframework... ) help unify things? - Ravi Nanavati
Ravi Nanavati writes:
http://developers.sun.com/techtopics/mobility/midp/articles/genericframework...
It does look quite interesting. We'd probably need only a fraction of all those options, but the schemes socket://www.j2medeveloper.com:80 datagram://www.j2medeveloper.com:7001 file:///myResourceFile.res are pretty much exactly what we need. Thanks for the pointer. Peter
participants (8)
-
Dominic Steinitz -
Einar Karttunen -
Graham Klyne -
Marcin 'Qrczak' Kowalczyk -
Peter Simons -
Ravi Nanavati -
Shae Matijs Erisson -
Tony Finch