Author Topic: Writing networking code in assembly  (Read 22643 times)

Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #15 on: September 21, 2017, 06:45:16 PM »
This man page for Linux "socketcall" lists the various options that can go into "call" type (which would be what is placed in ebx for the syscall)

It looks like I would need to use "socket" (ebx= 1) and connect (ebx=3) , are there any others: send and receive maybe? And would ebx be the number of the socket commands in order (ex. would "send" ebx= 9 because it is the 9th one down the list?)

http://man7.org/linux/man-pages/man2/socketcall.2.html


- I think I may be having a moment of clarity here, below your sample code:

Code: [Select]
; socket
    push 0 ; todo: name these!
    push 1
    push 2
    mov ecx, esp
    mov ebx, 1 ; the "socket" command
    mov eax, 102 ; sys_socketcall
    int 80h
    add esp, 12
    test eax, eax
    js exit
    mov [sock_fd], eax
    mov [connect_args], eax

So when mov ecx, esp happens, that is actually putting the three args pushed before it (2 [AF_INET], 1 [SOCK_STREAM], 0 [protocol type]) into the ecx register so that those can be used when invoking the socketcall syscall, that is, when eax= 102 and ebx=1 (1= sys_socket command).

If that's true then I think that helps to clear a lot up about where these arguments are actually coming from. In a sense it seems like ecx can actually hold more than one "thing" at a time, if this makes sense. Then after the socketcall, the stack is "erased" three memory slots so the arguments 0, 1, 2 that were put there are now gone?


- At this point I'm trying to figure out how to convert the hostname from the command line using the dns.o "resolv" to an IP address that I can start to use in these socket sys calls. Nothing is easy  :-\
« Last Edit: September 21, 2017, 07:49:08 PM by turtle13 »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #16 on: September 21, 2017, 08:18:39 PM »
The code you mention is part of a work-in-progress. It currently looks like:
Code: [Select]

; socket
    push INADDR_ANY
    push SOCK_STREAM
    push AF_INET
    mov ecx, esp ; structure's on stack
    mov ebx, SYS_SOCKET ;
    mov eax, __NR_socketcall
    int 80h
    add esp, 12 ; remove structure
    test eax, eax
    js exit
    mov [sock_fd], eax
    mov [connect_args], eax
   
What goes in ecx is not really "several things" but the address of several things - "*args" in C terms.

When we "remove" these items from the stack, they aren't really "gone" but the stack pointer (esp) has been moved above them so they'll be overwritten by any subsequent pushes or calls.

Your link to the man pages knows a lot more about it than I do, so go by what they say, not what I say.

I think that code is finally working. Besides the error you pointed out to me, there were others. I get a 404 when I ask for "/index" from some sites - good(?) content from others. Haven't got a "bad request" or "request timeout" for a while. Change is not always progress, but I think it's coming along...

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #17 on: September 21, 2017, 10:44:53 PM »
Here is what I have so far.. there are lots of gaps so at this point want to get these filled in. Goal is to get this entire thing complete and running by midnight tonight

Code: [Select]
bits 32

global _start
global resolv


; creating the initial socket syscall to initiate a socket:

AF_INET         equ 2           ; sin_family
SOCK_STREAM     equ 1           ; socket type (TCP)
PROTOCOL        equ 0           ; just because

SYS_SOCKET      equ 1
SYS_CONNECT     equ 3

buflen          equ 1000


section .data

socket_args dd AF_INET, SOCK_STREAM, 0



section .text
_start:

push PROTOCOL
push SOCK_STREAM
push AF_INET

mov ecx, esp            ; places socketcall args into ecx for syscall
mov ebx, SYS_SOCKET     ; socket function to invoke (1= socket command)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; test for error (eax= socket file descriptor)
jl exit                 ; jump if negative (error)

mov [sock_fd], eax      ; place the newly created socket file descriptor into sock_fd
mov [connect_args], eax ; ???


; now make a connection with the created socket

push addr_len
push ip_addr
push sock_fd

mov ecx, esp            ; places connect args into ecx for syscall
mov ebx, SYS_CONNECT    ; socket function to invoke (3= socket connect)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; check for errors
jl exit


; now start sending stuff

                 



; now read into the buffer
readwrite:

        mov edx, buflen         ; edx= sys read size_t buflen= max. 1000 bytes
        mov ecx, buffer         ; each character (char *) to be read
        mov ebx, [connect_fd]   ; int: file descriptor to be read from the socket connection
        mov eax, 3              ; read sys call
        int 0x80

        cmp eax, 0              ; returns the number of bytes read
        jl exit                 ; exit on error (if return value -1 or less)


; now write the length of what was read

        mov edx, eax            ; the size_t count of bytes read moved into edx
        mov ecx, buffer         ; the characters to write are stored in "buffer"
        mov ebx, [connect_fd]   ; file descriptor to write to
        mov eax, 4              ; write sys call
        int 0x80

        cmp eax, 0              ; return value= bytes written, if less than 0, error occurred
        jl exit

        ; need something here to determine EOF??

        jmp readwrite           ; continue reading/ writing until EOF



xor eax, eax            ; clear eax if no errors occurred

exit:
        mov ebx, eax            ; put exit code into ebx
        mov eax, 1              ; exit sys call
        int 0x80

section .bss

sock_fd         resd 1          ; socket fd = 32- bits
connect_fd      resd 1
buffer          resb buflen     ; reserve 1000 bytes for 'buffer'


A few questions on your previous server/bind code:

You use:

Code: [Select]
my_sa istruc sockaddr_in
    at sockaddr_in.sin_family, dw AF_INET
    at sockaddr_in.sin_port, dw PORT
    at sockaddr_in.sin_addr, dd INADDR_ANY
    at sockaddr_in.sin_zero, dd 0, 0
iend

My professor provided us with sample code:

Code: [Select]
struc sockaddr_in
   .sin_family: resw 1
   .sin_port: resw 1
   .sin_addr: resd 1
   .sin_pad: resb 8
endstruc

are these the same/ interchangeable? If not what is going on and what is the difference?



What is this for and how does it work:

Code: [Select]
BACKLOG equ 128 ;for listen



For code below:

Code: [Select]
    mov [fd_socket], eax
    ; and fill in bind_args, etc.
    mov [bind_args], eax
    mov [listen_args], eax
    mov [accept_args], eax

are fd_socket, bind_args, listen_args, and accept_args all going to have the same value (whatever is in eax) or does the value in eax change each time something is moved?



You declared in section .data:

Code: [Select]
bind_args   dd 0, my_sa, sockaddr_in_size

and then there is a socket call:

Code: [Select]
    mov ecx, bind_args
    mov ebx, SYS_BIND ; subfunction or "command"
    mov eax, __NR_socketcall
    int 80h

What is the value of sockaddr_in_size and what are values of my_sa:

Code: [Select]
my_sa istruc sockaddr_in
    at sockaddr_in.sin_family, dw AF_INET
    at sockaddr_in.sin_port, dw PORT
    at sockaddr_in.sin_addr, dd INADDR_ANY
    at sockaddr_in.sin_zero, dd 0, 0
iend

I'm not making a "connection" as to how those values get filled up or how those are being stored from the previous socket call.



I notice you do this a lot:

Code: [Select]
    cmp eax, -4096
    ja exit

Is there any significance to -4096 or does this only pertain to the server side of things?



Are "listen" and "accept" required on the client (my) side?



Can you help explain what's going on here:

Code: [Select]
section .bss
my_buf resb BUFLEN

and

Code: [Select]
BUFLEN equ 1000

Why is BUFLEN used sometimes instead of my_buf?

For example:

Code: [Select]
; read(sock, buf, len)
    mov edx, BUFLEN ; arg 3: max count
    mov ecx, my_buf ; arg 2: buffer
    mov ebx, [fd_conn] ; arg 1: fd
    mov eax, __NR_read ; sys_read
    int 80h

All the pieces coming together...

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #18 on: September 22, 2017, 01:54:52 AM »
First some comments on your code-so-far. I don't think "global resolv" is correct. I think you want "extern resolv". "global" tells Nasm to tell the linker, "this function/data is here, if anybody is looking for it". "extern" tells Nasm to tell the linker, "we use this but it isn't here. Please find it for us".

At your comment "now start sending stuff" you want to send the "request".  Then you read what the server sends us into a buffer. Then you appear to write it back to the socket. I think you want to write it to stdout, or to a disk file. Other than that, it looks like you're on the right track.

"struc" is a typedef. "istruc" creates an instance of the structure. There's a small error in my code there. At sin.addr I've got INADDR_ANY. We want a zero there - to be filled in with the address we get from "resolv" or "localhost" in the case of "echo". It is not the "protocol". I don't know how I got away with that. Nasm calculates sockaddr_in_size from the "struc" typedef. It is the size of the structure(!). "my_sa" is the address of my instance of the structure.

"BACKLOG"  is just an argument to "listen". You don't need it (or "bind" or "accept") for a client.

After the "socket" call, eax contains the descriptor, which is the first argument to several other calls. I'm just filling them in while I've got the value handy. The value in eax does not change.

"-4096" is a Linux thing, not limited to socket programming. I often use the "quick and dirty" js error. Strictly speaking,  there are negative numbers which are not an error. We might encounter them in allocating memory or... I dunno perhaps other things. You may notice that I use a negative number with an unsigned condition code. C would complain, but I know what I mean. It's just a more accurate way to identify what's an error. js or jl (than 0) should work  for anything we encounter here.

BUFLEN is "EQU"ated to a constant value, 1000 in this case. "my_buf" is the address of the buffer of that size.

If I've missed any questions, ask again...

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #19 on: September 22, 2017, 02:13:33 AM »
Which file descriptor do I use with write to write to a local file?

I imagine the file first has to be "created".. do I use a sys call for create such that

Code: [Select]
mov eax, 8
mov ebx, pathname
mov ecx, 3 (mode, write)
int 0x80
eax would now equal the file descriptor to write to for subsequent writes

but.. how do we specify a pathname in nasm?

I am going by this linux man page for "open/ create" http://man7.org/linux/man-pages/man2/creat.2.html
« Last Edit: September 22, 2017, 02:19:27 AM by turtle13 »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #20 on: September 22, 2017, 02:20:39 AM »
The descriptor you got from sys_open, I guess.

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #21 on: September 22, 2017, 04:07:37 AM »
I notice you declare sizes for multiple things on the same lines:

Code: [Select]
bind_args   dd 0, my_sa, sockaddr_in_size

Does this mean that bind_args is of size "double word" (32- bits/ 4 bytes) and that each "argument" such as 0, my_sa, and sockaddr_in_size are also 32- bits/ 4 bytes each? So bind_args would end up being a total of 12 bytes?

And what does "nop" do right after your start?
« Last Edit: September 22, 2017, 04:18:10 AM by turtle13 »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #22 on: September 22, 2017, 04:52:30 AM »
Yes, each argument is 32 bits. If they were different sizes, we would want to put them on different lines.

The "nop" is sometimes commented as "parking place for GDB". GDB seems happier if it has a single-byte instruction to replace with the single-byte single step interrupt opcode (0xCC or whatever it is). It also seems happier if you assemble with -F dwarf.  I find GDB "unfriendly" and avoid it if I can. You should learn to use it!

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #23 on: September 22, 2017, 05:23:17 AM »
I'm working at parsing the hostname from the command line, here is what I have going so far:

Code: [Select]
section .bss

sock_fd         resd 1          ; socket fd = 32- bits
connect_fd      resd 1
buffer          resb buflen     ; address of 1000 byte 'buflen' (1,000 characters at a time)
get_request     resb 0x1000     ; 1000 bytes (characters) reserved for the HTTP GET request
cl_name         resb 150        ; 150 bytes (characters) reserved for URL typed at command line
cl_name_len     resd 1          ; 4 bytes (1 double word) reserved for storing length of typed  URL
cl_hostname     resb 50         ; 50 bytes (characters) reserved for the hostname of URL
cl_tail         resb 100        ; 100 bytes (characters) reserved for the end of URL (past hostname)

section .text
_start:


; set up and parse from command line for HTTP GET request

; nop?

pop eax         ; pops esp (top of stack [argc]) into eax
cmp eax, 2      ; were only 2 args typed in command line?
je good_argc    ; if yes, away we go
jmp error_exit        ; buh bye


; start parsing the URL
good_argc:

        pop esi                 ; pop argv[0] (program name) throw it away- don't need it
        pop esi                 ; pop argv[1] (URL) into esi- this is what we need
        cmp [esi], dword "http" ; does URL start with http?
        je x_http               ; if so, parse it out

        cmp [esi], dword "HTTP" ; same thing but user typed all caps
        jne get_hostname             ; if none of the above conditions apply, no need to parse out http://

x_http:

        add esi, 4              ; jump to next character after http
        cmp [esi], byte ":"     ; should be :
        jne error_exit          ; user typed in the url incorrectly

        add esi, 1              ; proceed to character after :
        cmp [esi], word "//"    ; // comes after :
        jne error_exit          ; user typed in url incorrectly

        add esi, 2              ; now we are at the beginning of hostname
       

get_hostname:

        mov cl_hostname, [esi]  ; store character in variable 'cl_hostname'
        cmp [esi], byte "/"     ; check for end of hostname
        je found_hostname
        inc esi                 ; inc to next character to be stored into 'cl_hostname'       
        jmp get_hostname

       
found_hostname:

        ; now start parsing the pathname




exit:
        mov ebx, eax            ; put exit code into ebx
        mov eax, 1              ; exit sys call
        int 0x80

error_exit:
        xor ebx, ebx
        mov ebx, -1             ; produce -1 exit error code
        mov eax, 1              ; exit sys call
        int 0x80

yay or nay?
« Last Edit: September 22, 2017, 05:30:47 AM by turtle13 »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #24 on: September 22, 2017, 07:16:22 AM »
Nasm says nay.
Code: [Select]
section .bss

sock_fd         resd 1          ; socket fd = 32- bits
connect_fd      resd 1
buffer          resb buflen     ; address of 1000 byte 'buflen' (1,000 characters at a time)
get_request     resb 0x1000     ; 1000 bytes (characters) reserved for the HTTP GET request
cl_name         resb 150        ; 150 bytes (characters) reserved for URL typed at command line
cl_name_len     resd 1          ; 4 bytes (1 double word) reserved for storing length of typed  URL
cl_hostname     resb 50         ; 50 bytes (characters) reserved for the hostname of URL
cl_tail         resb 100        ; 100 bytes (characters) reserved for the end of URL (past hostname)

section .text
_start:


; set up and parse from command line for HTTP GET request

; nop?

pop eax         ; pops esp (top of stack [argc]) into eax
cmp eax, 2      ; were only 2 args typed in command line?
je good_argc    ; if yes, away we go
mov eax, -1     ; to produce an error code upon exiting
jmp error_exit        ; buh bye


; start parsing the URL
good_argc:

        pop esi                 ; pop argv[0] (program name) throw it away- don't need it
        pop esi                 ; pop argv[1] (URL) into esi- this is what we need
        cmp [esi], dword "http" ; does URL start with http?
        je x_http               ; if so, parse it out

        cmp [esi], dword "HTTP" ; same thing but user typed all caps
        jne get_hostname             ; if none of the above conditions apply, no need to parse out http://

x_http:

        add esi, 4              ; jump to next character after http
        cmp [esi], byte ":"     ; should be :
        jne error_exit          ; user typed in the url incorrectly

        add esi, 1              ; proceed to character after :
        cmp [esi], word "//"    ; // comes after :
        jne error_exit          ; user typed in url incorrectly

        add esi, 2              ; now we are at the beginning of hostname
       

get_hostname:
BZZZZZT!
        mov cl_hostname, [esi]  ; store character in variable 'cl_hostname'


cl_hostname is the address of the variable. it is an immediate number. You're trying to do "mov 6, 9" (reference to an old Jimi Hendrix tune).

[cl_hostname] would be the contents, which we could put something into. But... we can't do a memory to memory move. The movsb instruction is an exception, and might be an option here. Otherwise we have to use a register (8-bit). al will do...

mov al, [esi]
mov [cl_hostname], al
inc esi? also known as lodsb?

Code: [Select]
        cmp [esi], byte "/"     ; check for end of hostname
        je found_hostname
        inc esi                 ; inc to next character to be stored into 'cl_hostname'       
        jmp get_hostname

       
found_hostname:

        ; now start parsing the pathname


Nasm has other complaints, but that one's a flat out error.

I think I mentioned that I had a heck of a time with this part of the code. I started out thinking I needed two copies of hostname - one zero terminated for resolv and one CR, LF terminated for the request. No, I can put the CR, LF in the "hard coded" part of the request. I was and am parsing the command line by copying parts to two intermediate variables in .bss. It occurs to me that I don't have to move the command line at all. I could just find the start address and length of the hostname and the "tail" if any. Then I could copy them directly into the request. I don't think I'd want to try to write them to the socket directly from there. Just thinking out loud... I'll probably keep the intermediate copy...

My suggestion would be to keep it as simple as you can. I haven't written the "usage" message yet. My thought would be to not tell 'em http at all. I've got http and HTTP covered, but if the pesky user goes Http or https I'm still toast... Since you and I and maybe your prof are probably going to be the only users we can be well behaved, I suppose.

Code on, Turtle13!

Best,
Frank


Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #25 on: September 22, 2017, 08:03:21 AM »
^If I want to use movsb:

Code: [Select]
get_hostname:
        inc cl_hostname_len     ; variable for number of characters in hostname

        ;mov cl_hostname, [esi]  ; store character in variable 'cl_hostname'
        cmp [esi], byte "/"     ; check for end of hostname
        je copy_hostname
        cmp [esi], 0            ; because user can type in just a host with no path, need to return /index.html
        je copy_hostname_only       
        inc esi                 ; inc to next character to be stored into 'cl_hostname'       
        jmp get_hostname


copy_hostname:       
        ; try movsb?
        cld                             ; clear direction flag       
        mov ecx, [cl_hostname_len]      ; the count= number of characters to copy into cl_hostname
        mov edi, cl_hostname
        mov esi, esi
        rep movsb                       ; move the string

?



To resolve hostname- to- IP (assuming that I already parsed the hostname correctly):

Code: [Select]
; resolve DNS hostname to IP address

push cl_hostname
call resolv
add esp, 4
mov [ip_hostname], ebx
...
section .bss

ip_hostname     resd 1          ; for storing the resolved hostname- to- IP address

?
« Last Edit: September 22, 2017, 08:39:06 AM by turtle13 »

Offline turtle13

  • Jr. Member
  • *
  • Posts: 73
Re: Writing networking code in assembly
« Reply #26 on: September 22, 2017, 08:47:34 AM »
Almost 2am here and going to bed but in case you have nothing better to do on a Friday morning when you wake up before I wake up, here is some of my code for socket call stuff:

Code: [Select]
AF_INET         equ 2           ; sin_family
SOCK_STREAM     equ 1           ; socket type (TCP)
PROTOCOL        equ 0           ; just because

SYS_SOCKET      equ 1
SYS_CONNECT     equ 3
SYS_SEND        equ 9
SYS_RECV        equ 10

buflen          equ 1000



; structure for sockaddr_in

struc sockaddr_in
        .sin_family:    resw 1          ; address family AF_INET 16- bits
        .sin_port:      resw 1          ; port # 16- bits
        .sin_addr:      resd 1          ; 32- bit IP address of web server
        .sin_pad:       resb 8          ; 8 bytes of padding
endstruc

section .data

socket_args dd AF_INET, SOCK_STREAM, 0

addr_len dw 1           ; 32- bit IP address


section .text
...
; set up and create a socket file descriptor

push PROTOCOL
push SOCK_STREAM
push AF_INET

mov ecx, esp            ; places socketcall args into ecx for syscall
mov ebx, SYS_SOCKET     ; socket function to invoke (1= socket command)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; test for error (eax= socket file descriptor)
jl exit                 ; jump if negative (error)

mov [sock_fd], eax      ; place the newly created socket file descriptor into sock_fd
mov [connect_args], eax ; this will need the socket fd as an argument also


; now make a connection with the created socket

push addr_len           ; 32- bit IP address
push ip_hostname        ; resolved hostname- to- IP address
push sock_fd            ; identifies the socket

mov ecx, esp            ; places connect args into ecx for syscall
mov ebx, SYS_CONNECT    ; socket function to invoke (3= socket connect)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; check for errors
jl exit

...

xor eax, eax            ; clear eax if no errors occurred

exit:
        mov ebx, eax            ; put exit code into ebx
        mov eax, 1              ; exit sys call
        int 0x80

error_exit:
        xor ebx, ebx
        mov ebx, -1             ; produce -1 exit error code
        mov eax, 1              ; exit sys call
        int 0x80
...
section .bss

sock_fd         resd 1          ; socket fd = 32- bits
connect_fd      resd 1
buffer          resb buflen     ; address of 1000 byte 'buflen' (1,000 characters at a time)
get_request     resb 0x1000     ; 1000 bytes (characters) reserved for the HTTP GET request
cl_name         resb 300        ; 300 bytes (characters) reserved for URL typed at command line
cl_path_len     resd 1          ; 4 bytes (1 double word) reserved for storing length of typed URL's path
cl_filename_len resd 1          ; 4 bytes to store length of the URL's filename
cl_filename     resb 50         ; 50 bytes (characters) reserved for filename to download
cl_hostname     resb 50         ; 50 bytes (characters) reserved for the hostname of URL
cl_hostname_len resd 1          ; 4 bytes to store length of the hostname
cl_tail         resb 100        ; 100 bytes (characters) reserved for the end of URL (past hostname)
pathname        resb 100        ; 100 bytes (characters) for the parsed pathname
ip_hostname     resd 1          ; for storing the resolved hostname- to- IP address



Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #27 on: September 22, 2017, 09:11:02 AM »
Ahhh had a reply typed in and then lost it, Hate it when that happens! [cl_hostname] ! And put esi back to where you want movsb to start.

G'night!
Frank

Oh, and resolv teturns answer or -1 in eax, not ebx!
« Last Edit: September 22, 2017, 09:16:28 AM by Frank Kotler »

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #28 on: September 22, 2017, 09:28:00 AM »
Usual confusion between address and [contents]!

Code: [Select]
AF_INET         equ 2           ; sin_family
SOCK_STREAM     equ 1           ; socket type (TCP)
PROTOCOL        equ 0           ; just because

SYS_SOCKET      equ 1
SYS_CONNECT     equ 3
SYS_SEND        equ 9
SYS_RECV        equ 10

buflen          equ 1000



; structure for sockaddr_in

struc sockaddr_in
        .sin_family:    resw 1          ; address family AF_INET 16- bits
        .sin_port:      resw 1          ; port # 16- bits
        .sin_addr:      resd 1          ; 32- bit IP address of web server
        .sin_pad:       resb 8          ; 8 bytes of padding
endstruc

section .data

socket_args dd AF_INET, SOCK_STREAM, 0

addr_len dw 1           ; 32- bit IP address


section .text
...
; set up and create a socket file descriptor
; here you're pushing constants - that's fine
push PROTOCOL
push SOCK_STREAM
push AF_INET

mov ecx, esp            ; places socketcall args into ecx for syscall
mov ebx, SYS_SOCKET     ; socket function to invoke (1= socket command)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; test for error (eax= socket file descriptor)
jl exit                 ; jump if negative (error)

mov [sock_fd], eax      ; place the newly created socket file descriptor into sock_fd
mov [connect_args], eax ; this will need the socket fd as an argument also


; now make a connection with the created socket


; you're pushing addresses of variables here!
; probably want [contents]
; you'll have to say "dword".
push addr_len           ; 32- bit IP address
push ip_hostname        ; resolved hostname- to- IP address
push sock_fd            ; identifies the socket

mov ecx, esp            ; places connect args into ecx for syscall
mov ebx, SYS_CONNECT    ; socket function to invoke (3= socket connect)
mov eax, 102            ; socketcall syscall
int 0x80

add esp, 12             ; clean up stack

cmp eax, 0              ; check for errors
jl exit

...

xor eax, eax            ; clear eax if no errors occurred

exit:
        mov ebx, eax            ; put exit code into ebx
        mov eax, 1              ; exit sys call
        int 0x80

error_exit:
        xor ebx, ebx
        mov ebx, -1             ; produce -1 exit error code
        mov eax, 1              ; exit sys call
        int 0x80
...
section .bss

sock_fd         resd 1          ; socket fd = 32- bits
connect_fd      resd 1
buffer          resb buflen     ; address of 1000 byte 'buflen' (1,000 characters at a time)
get_request     resb 0x1000     ; 1000 bytes (characters) reserved for the HTTP GET request
cl_name         resb 300        ; 300 bytes (characters) reserved for URL typed at command line
cl_path_len     resd 1          ; 4 bytes (1 double word) reserved for storing length of typed URL's path
cl_filename_len resd 1          ; 4 bytes to store length of the URL's filename
cl_filename     resb 50         ; 50 bytes (characters) reserved for filename to download
cl_hostname     resb 50         ; 50 bytes (characters) reserved for the hostname of URL
cl_hostname_len resd 1          ; 4 bytes to store length of the hostname
cl_tail         resb 100        ; 100 bytes (characters) reserved for the end of URL (past hostname)
pathname        resb 100        ; 100 bytes (characters) for the parsed pathname
ip_hostname     resd 1          ; for storing the resolved hostname- to- IP address

Past my bedtime...

Best,
Frank


Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Writing networking code in assembly
« Reply #29 on: September 22, 2017, 06:55:15 PM »
Well... another day...
Code: [Select]
; now make a connection with the created socket

push addr_len           ; 32- bit IP address
push ip_hostname        ; resolved hostname- to- IP address
push sock_fd            ; identifies the socket
What I've got for connect arguments (and it works) is the descriptor ([contents] !), the address of a sockaddr_in structure... this is not just the IP returned from resolv... and the length of that structure. You've got the "typedef" of the structure, but I don't see an instance of it.

This is what I've got:
Code: [Select]
sa:
    dw AF_INET ; family
    dw htons (80) ; port
.addr    dd 0 ; fill in later
    dd 0, 0 ; zero
sa_size equ $ - sa

; first of these wants to be socket descriptor
; we fill it in later...
    connect_args dd 0, sa, sa_size

As you can see, I don't use the "struc" or "istruc" keywords, but it generates exactly the same code. Note that "family" and "port" are words, the IP address is a dword, and two dwords padding at the end. "port" needs to be in "network byte order. Apparently "family" does not - I don't know why not. The IP address is already in network byte order. I don't put it on the stack, but that shouldn't matter.

If you can try out the socket code with a hard-coded request - separate from parsing the command line - that would be a good thing.

Best,
Frank