Reputation: 81
I am trying to write a server-like program where a client connects and you can download any file you want(and have permission to). I made a function that has the ability to make the client send the file (with bash, I don't want to install a client to all of my PCs) using the /dev/TCP/IP/port
device. I can fetch and save the file from the client, but if I want to download a second file from the same port(the port should close after every download) I get an EADDRINUSE error.
The function:
void DownloadFile(int fd, cmd_args cmdargs, arguments args) {
// check filepaths
if(cmdargs.lpath.size() == 0 || (exists(cmdargs.lpath.c_str()) && is_directory(cmdargs.lpath.c_str()))) {
log(/*args.OutputFile*/"file", "Please specify a path to save the downloaded file");
return;
}
if(cmdargs.rpath.size() == 0) {
log("file", "Please enter a path to a file");
return;
}
// open a second server to bind
SockInitializer sock = InitializeSocket(cmdargs.ip, cmdargs.port, true);
// PACKET FORMAT: [f/d][int][\n][data]
// PACKET EXPLANATION: file/directory size_of_file new_line file_data
// open connection
string connect_payload = "exec 3>/dev/tcp/";
connect_payload += cmdargs.ip;
connect_payload += '/';
connect_payload += to_string(cmdargs.port);
//connect_payload += ';\n';
// send file/dir type
string begin_payload = "[ -d \"";
begin_payload += cmdargs.rpath;
begin_payload += "\" ] && echo -n d >&3 || echo -n f >&3;";
// send size number
begin_payload += "echo -n $(stat --printf='%s' ";
begin_payload += cmdargs.rpath;
begin_payload += ") >&3;";
// new line
begin_payload += "echo >&3;";
// send file
begin_payload += "cat ";
begin_payload += cmdargs.rpath;
begin_payload += " >&3\n\n";
// accept connection
SendString(fd, connect_payload);
int cfd = accept(sock.fd, &sock.addr, &sock.addrlen);
SendString(fd, begin_payload);
// get type
string str = ListenSocket(cfd, 1, true);
bool isFile = (str == "f");
// get file size
string ToDownloadSTRING = "";
char buf = '\0';
do {
if(buf != '\0')
ToDownloadSTRING += buf;
buf = ListenSocket(cfd, 1, true)[0];
} while(buf != '\n');
// parse file size
int ToDownload = atoi(ToDownloadSTRING.c_str());
/* download file */
if(isFile) {
int downloaded = 0;
// open file [write binary]
fstream file;
file.open(cmdargs.lpath, ios::out | ios::binary);
// loop to fetch data
while(downloaded < ToDownload) {
string tmp = ListenSocket(cfd, DOWNLOAD_BUF_SIZE);
// write to file
file.write(tmp.c_str(), tmp.size());
// flush to file
file.flush();
// add size to downloaded
downloaded += tmp.size();
}
// close file
file.close();
}
string disconnect_payload = "exec 3>&-";
SendString(fd, disconnect_payload);
// terminate connection
shutdown(cfd, SHUT_RDWR);
close(cfd);
shutdown(sock.fd, SHUT_RDWR);
close(sock.fd);
}
The above is the whole function in case you need the whole thing. The below is the same function but with only the stuff that (I think) you need to understand what happens
void DownloadFile(int fd, cmd_args cmdargs, arguments args) {
//check the filepaths
// open a second socket to bind
SockInitializer sock = InitializeSocket(cmdargs.ip, cmdargs.port, true);
// PACKET FORMAT: [f/d][int][\n][data]
// PACKET EXPLAN: file/directory size_of_file new_line file_data
// Payload to connect here and send the packet with the file
string connect_payload = "exec 3>/dev/tcp/IP/PORT";
string begin_payload = "[ -d \"REMOTE_PATH\" ] && echo -n d >&3 || echo -n f >&3; echo -n $(stat --printf='%s' REMOTE_PATH) >&3; echo >&3; cat REMOTE_PATH >&3\n\n";
// accept connection on the new socket we initialized earlier in the function
SendString(fd, connect_payload);
int cfd = accept(sock.fd, &sock.addr, &sock.addrlen);
SendString(fd, begin_payload);
// is REMOTE_PATH referring to a file or a directory?
string str = ListenSocket(cfd, 1, true);
bool isFile = (str == "f");
// get file size
string ToDownloadSTRING = "";
char buf = '\0';
do {
if(buf != '\0')
ToDownloadSTRING += buf;
buf = ListenSocket(cfd, 1, true)[0];
} while(buf != '\n');
// parse file size to integer
int ToDownload = atoi(ToDownloadSTRING.c_str());
/* download file */
if(isFile) {
int downloaded = 0;
// open file [write binary]
fstream file;
file.open(LOCAL_PATH, ios::out | ios::binary);
// loop to fetch data
while(downloaded < ToDownload) {
// get data
string tmp = ListenSocket(cfd, DOWNLOAD_BUF_SIZE);
// write to file
file.write(tmp.c_str(), tmp.size());
file.flush();
// add size to downloaded
downloaded += tmp.size();
}
// close file
file.close();
}
// make the client close the file descriptor referring to the tcp connection
string disconnect_payload = "exec 3>&-";
SendString(fd, disconnect_payload);
// terminate connection from the server
shutdown(cfd, SHUT_RDWR);
close(cfd);
shutdown(sock.fd, SHUT_RDWR);
close(sock.fd);
}
NOTES
A lot of functions are missing from the question, if you need any of the functions just ask, I just think you can understand from the name what they do.
The client should connect to the server with this bash connect(I know that this is not actually secure): /bin/bash -i >& /dev/tcp/IP/PORT 0>&1
I am using parrotOS
: Linux
The function should be called twice without terminating the program, or disconnecting the client from the main socket(the fd that is passed to the function)
Upvotes: 4
Views: 840
Reputation: 81
As pointed out by @Ted Lyngmo i needed to use the SO_REUSEADDR and SO_REUSEPORT options on my sockets.
If you have the same problem just follow this steps:
//get file descriptor
int fd = socket.socket(/*options*/);
// set the options
int _enable = 1;
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &_enable, sizeof(_enable)) < 0) {
/*Do stuff */
}
if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &_enable, sizeof(_enable)) < 0) {
/* Do stuff */
}
bind(fd, /*blah blah*/);
NOTE SO_REUSEPORT and SO_REUSEADDR options need to be setted from the first socket
do not try to do something like:
int sock = socket.socket(/*blah blah*/);
int bound = bind(sock, /*blah blah*/);
if(bound < 0 && errno == EADDRINUSE) {
int _enable = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &_enable, sizeof(_enable));
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &_enable, sizeof(_enable));
bind(sock, /*blah blah*/);
}
Upvotes: 4