I want to create a small getaddrinfo proxy dynamic library for Linux, which can be LD_PRELOADed in order to debug a getaddrinfo call from an application code and modify the response (for example, intercept a query and make the response based on data in Redis).
But it can not be compiled by
error: conflicting types for 'getaddrinfo'
Can someone give me any hint?
type
Socklen* {.
importc: "socklen_t",
header: "<sys/socket.h>",
.} = cuint
Sa_Family* {.
importc: "sa_family_t",
header: "<sys/socket.h>",
.} = cint
SockAddr* {.
importc: "struct sockaddr",
header: "<sys/socket.h>",
pure,
final,
.} = object
sa_family*: Sa_Family
sa_data*: array [0..255, char]
AddrInfo* {.
importc: "struct addrinfo",
pure,
final,
header: "<netdb.h>",
.} = object
ai_flags*: cint
ai_family*: cint
ai_socktype*: cint
ai_protocol*: cint
ai_addrlen*: Socklen
ai_addr*: ptr SockAddr
ai_canonname*: cstring
ai_next*: ptr AddrInfo
var
RTLD_NEXT* {.importc, header: "<dlfcn.h>".}: pointer
proc dlsym* (
handle: pointer,
symbol: cstring,
): pointer {.
importc,
header: "<dlfcn.h>",
.}
proc preGetAddrInfo (
node: var cstring,
service: var cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
) {.inline.} =
discard
proc postGetAddrinfo (
rtncode: cint,
res: var ptr Addrinfo,
) {.inline.} =
discard
proc getAddrInfo* (
node: cstring,
service: cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
): cint {.
exportc: "getaddrinfo",
cdecl,
.} =
var
rnode = node
rservice = service
# Do something (log or modify the request).
preGetAddrInfo(rnode, rservice, hints, res)
let
cgetaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo".cstring)
# How to call cgetaddrinfo?
result = ???(rnode, rservice, hints, res)
# Log or modify the res.
postGetAddrInfo(result, res)
try something along these lines maybe:
type tgetaddrinfo = proc(a, b, c, d: cint) {.cdecl.}
let cgetaddrinfo = cast[tgetaddrinfo](dlsym(RTLD_NEXT, "getaddrinfo".cstring))
cgetaddrinfo(...)
Thanks!
I modified my code as follows, but same compile error happens.
And another issue, I may have to define a symbol _GNU_SOURCE, though, I don't know how to define it in nim.
type
Socklen* {.
importc: "socklen_t",
header: "<sys/socket.h>",
.} = cuint
Sa_Family* {.
importc: "sa_family_t",
header: "<sys/socket.h>",
.} = cint
SockAddr* {.
importc: "struct sockaddr",
header: "<sys/socket.h>",
pure,
final,
.} = object
sa_family*: Sa_Family
sa_data*: array [0..255, char]
AddrInfo* {.
importc: "struct addrinfo",
pure,
final,
header: "<netdb.h>",
.} = object
ai_flags*: cint
ai_family*: cint
ai_socktype*: cint
ai_protocol*: cint
ai_addrlen*: Socklen
ai_addr*: ptr SockAddr
ai_canonname*: cstring
ai_next*: ptr AddrInfo
GetAddrInfo = proc (
a, b: cstring,
c: ptr AddrInfo,
d: var ptr AddrInfo,
): cint {.cdecl.}
var
RTLD_NEXT* {.importc, header: "<dlfcn.h>".}: pointer
proc dlsym* (
handle: pointer,
symbol: cstring,
): pointer {.
importc,
header: "<dlfcn.h>",
.}
proc preGetAddrInfo (
node: var cstring,
service: var cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
) {.inline.} =
discard
proc postGetAddrinfo (
rtncode: cint,
res: var ptr Addrinfo,
) {.inline.} =
discard
proc getAddrInfo* (
node: cstring,
service: cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
): cint {.
exportc: "getaddrinfo",
cdecl,
.} =
var
rnode = node
rservice = service
# Do something (log or modify the request).
preGetAddrInfo(rnode, rservice, hints, res)
let
cgetaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo".cstring)
result = cast[ptr GetAddrInfo](cgetaddrinfo)[](rnode, rservice, hints, res)
# Log or modify the res.
postGetAddrInfo(result, res)
try putting this at very top of your file
{.emit: "#define _GNU_SOURCE 1".}
@rku: Thanks! emit seems to be used for #define XXX. But same compile error happens.
Currently, my compile commmand is:
nim c --app:lib --passC:-ldl mygai.nim
That may be wrong. I have to read the nim docs more...
@Jehan: Thanks! I also noticed the {.nodecl.}. With it, compilation finishes successfully but getaddrinfo() seems to be disappeared.
My current code is:
{.emit: "#define _GNU_SOURCE".}
type
Socklen* {.
importc: "socklen_t",
header: "<sys/socket.h>",
.} = cuint
Sa_Family* {.
importc: "sa_family_t",
header: "<sys/socket.h>",
.} = cint
SockAddr* {.
importc: "struct sockaddr",
header: "<sys/socket.h>",
pure,
final,
.} = object
sa_family*: Sa_Family
sa_data*: array [0..255, char]
AddrInfo* {.
importc: "struct addrinfo",
header: "<netdb.h>",
pure,
final,
.} = object
ai_flags*: cint
ai_family*: cint
ai_socktype*: cint
ai_protocol*: cint
ai_addrlen*: Socklen
ai_addr*: ptr SockAddr
ai_canonname*: cstring
ai_next*: ptr AddrInfo
GetAddrInfo = proc (
a, b: cstring,
c: ptr AddrInfo,
d: var ptr AddrInfo,
): cint
var
RTLD_NEXT* {.importc, header: "<dlfcn.h>".}: pointer
proc dlsym* (
handle: pointer,
symbol: cstring,
): pointer {.
importc,
header: "<dlfcn.h>",
.}
proc preGetAddrInfo (
node: var cstring,
service: var cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
) {.inline.} =
var
f: File
if open(f, "/tmp/mygai.log", mode=fmAppend):
try:
f.write("preGetAddrInfo\n")
finally:
f.close()
proc postGetAddrinfo (
rtncode: cint,
res: var ptr Addrinfo,
) {.inline.} =
var
f: File
if open(f, "/tmp/mygai.log", mode=fmAppend):
try:
f.write("postGetAddrInfo\n")
finally:
f.close()
proc getaddrinfo* (
node: cstring,
service: cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
): cint {.
exportc: "getaddrinfo",
nodecl,
.} =
var
rnode = node
rservice = service
# Do something (log or modify the request).
preGetAddrInfo(rnode, rservice, hints, res)
let
cgetaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo".cstring)
result = cast[ptr GetAddrInfo](cgetaddrinfo)[](rnode, rservice, hints, res)
# Log or modify the res.
postGetAddrInfo(result, res)
Ugh, it looks like nodecl also suppresses code generation. In this case, you may have to work around it with {.emit.}, e.g.:
proc getAddrInfo* (
node: cstring,
service: cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
): cint {.exportc: "my_getaddrinfo".} =
var
rnode = node
rservice = service
# Do something (log or modify the request).
preGetAddrInfo(rnode, rservice, hints, res)
let
cgetaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo".cstring)
# How to call cgetaddrinfo?
result = getAddrInfo(node, rservice, hints, res)
# Log or modify the res.
postGetAddrInfo(result, res)
{.emit:"""
int getaddrinfo(const char *hostname, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
return my_getaddrinfo(hostname, service, hints, res);
}
""".}
(Untested code.)
@Jehan: Thanks!
WIth the emit, the compilation finishes successfully. But it does not work as a LD_PRELOAD module.
I'm sorry. With the emit line, the module can be successfully LD_PRELOADED. Thanks a lot.
But at the following line, an error SIGSEGV: Illegal storage access. (Attempt to read from nil?) occurs.
result = cast[ptr GetAddrInfo](cgetaddrinfo)[](rnode, rservice, hints, res)
I guess the cast is wrong but I have no idea now.
It's probably because the ptr is superfluous. A proc type with {.cdecl.} calling convention is already the equivalent of a function pointer in C. I.e. something like (again untested):
result = (cast[GetAddrInfo](cgetaddrinfo))(rnode, rservice, hints, res)
@Jehan: Thanks! I got the meaning of {.cdecl.}.
At last, the following code works well. Thank you all!
{.emit: "#define _GNU_SOURCE".}
import strutils
type
Socklen* {.
importc: "socklen_t",
header: "<sys/socket.h>",
.} = cuint
Sa_Family* {.
importc: "sa_family_t",
header: "<sys/socket.h>",
.} = cint
SockAddr* {.
importc: "struct sockaddr",
header: "<sys/socket.h>",
pure,
final,
.} = object
sa_family*: Sa_Family
sa_data*: array[0 .. 255, char]
AddrInfo* {.
importc: "struct addrinfo",
header: "<netdb.h>",
pure,
final,
.} = object
ai_flags*: cint
ai_family*: cint
ai_socktype*: cint
ai_protocol*: cint
ai_addrlen*: Socklen
ai_addr*: ptr SockAddr
ai_canonname*: cstring
ai_next*: ptr AddrInfo
GetAddrInfo = proc (
name, service: cstring,
hints: ptr AddrInfo,
res: var ptr AddrInfo,
): cint {.cdecl.}
var
RTLD_NEXT* {.
importc,
header: "<dlfcn.h>"
.}: pointer
proc dlsym* (
handle: pointer,
symbol: cstring,
): pointer {.
importc,
header: "<dlfcn.h>",
.}
proc preGetAddrInfo (
name: var cstring,
service: var cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
) {.inline.} =
var
f: File
if open(f, "/tmp/mygai.log", mode=fmAppend):
try:
f.write("preGetAddrInfo\n")
finally:
f.close()
proc postGetAddrInfo (
result: var cint,
res: var ptr Addrinfo,
) {.inline.} =
var
f: File
if open(f, "/tmp/mygai.log", mode=fmAppend):
try:
f.write("postGetAddrInfo\n")
finally:
f.close()
proc getAddrInfo (
name: cstring,
service: cstring,
hints: ptr AddrInfo,
res: var ptr Addrinfo,
): cint {.
exportc: "my_getaddrinfo",
cdecl,
dynlib,
.} =
var
rname = name
rservice = service
# Do something (log or modify the request).
preGetAddrInfo(rname, rservice, hints, res)
let
cgetaddrinfo = dlsym(RTLD_NEXT, "getaddrinfo")
result = (cast[GetAddrInfo](cgetaddrinfo))(rname, rservice, hints, res)
# Log or modify the res.
postGetAddrInfo(result, res)
{.emit:"""
int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
return my_getaddrinfo(hostname, service, hints, res);
}
""".}
Compile:
nim c --app:lib gai.nim
--passC:-ldl is not required.
Test:
LD_PRELOAD=./libgai.so python -c 'import pprint,socket; pprint.pprint(socket.getaddrinfo("yahoo.com", 80))'