sysfs entries

Entries in the /sys folder are represented by “struct kobject“.

A kobject is … a kernel object. So it can be anything. Regarding the /sys system, kobject can be more or less thinked as a “folder” of /sys. To obtain the “kobject” entry for /sys of an md device, one can do &disk_to_dev(mddev->gendisk)->kobj. This object will handle the “folder” /sys/block/md/mdX/ where X is the number of the md device.

To initialize your own kobject and add it as a child of the kobject of the mddev, one can use kobject_init_and_add(yourobject, &yourobect_type,
&disk_to_dev(mddev->gendisk)->kobj, “%s”, “foldername”); In practice this will create a folder named “foldername” in /sys/block/md/mdX/.

yourobject” should be a pointer to your kobject. It must be persistent, allocated with kmalloc or something like that and not in your function stack, even if you don’t plan to modify it. As kobject are more or less anything, you have to describe your kobject by passing to kobject_init_and_add a struct kobj_type

static struct kobj_type myobject_ktype = {
.release = release_function,
.sysfs_ops = &myobject_sysfs_ops,
}; 

release is the function which will be called when it’s time to destroy your kobject, while sysfs_ops is a struct sys_ops. We’ll come back to them later.

The “files” in your sys folder are represented by struct attribute. You have two choices here. Either the files in your folder do not change, and you add all the attribute as the “default list”of attributesof your kobject before the kobject initialisation  , or you start with an empty kobject (or with some default files in the list but not all) and you add some files after kobject initialization using sysfs_create_file(yourobject, attr); where attr is a pointer to the attribute that you want to add. We’ll consider that all files are known in advance and we’ll put everything in the “default” list.

The “default” list is more a default array and is referenced via  yourobject_ktype.default_attr . It is a pointer to the array of pointer to attributes. Yes, re-read that sentence twice 😉 It means you’ll give an array of pointer to attributes and not an array of attributes.

struct attribute **all_attrs;
all_attrs =  kzalloc(sizeof(struct attribute *) * number_of_files);
attrs =  kzalloc(sizeof(struct attribute) * number_of_files);
[fill attrs] for (i = 0; i < number_of_files;i++)
all_attrs[i] = &attrs[i];
yourobject_ktype.default_attrs =all_attrs;

If we look at struct attributes it contains :

struct attribute {
const char *name;
mode_t mode;
};

You should find by yourself how to initialize the attrs. Note that name is a char pointer, and in noway can store character themselves. So you need to allocate somewhere all the names of your attributes, and obviously not on your function stack…

Now, let’s go back to the myobject_sysfs_ops which describes how to read and write to these attributes

The main two functions in the sys_ops are show an store

static const struct sysfs_ops myobject_sysfs_ops = {
.show = myobject_attr_show,
.store = myobject_attr_store,
};

These two functions will be called when any kobject attr is read or written in your kobject entry. Let’s see the show function :

static ssize_t
myobject_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
{}

kobj is the pointer to your kobject, the attr is the attribute the user is trying to read and the page is a pointer to a space where you should print the content which was trying to be read.

The function container_of() is very usefull and often used in this case. Let’s say your kobject is stored in another structure, that we will call “struct conf” in our example. To recover the conf storing the kobject, one can do :

struct conf* conf = container_of(kobj, struct conf, kobj);

And you can use the attribute name to find what to write in the page.

This should be sufficient for most usage, using some tricks to respond according the attribute name.

If the treatment need to be specific according to the attribute/file, or if you absolutely need to store some data with each attribute, you have to allocate another bigger structure which contains the struct attr, this explains by the way why you give pointer to attributes and not an array of attributes to the ktype, because attributes could be scattered inside an array of a bigger structure. As an exemple, here the per-file structure of the md driver :

struct md_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct mddev *, char *);
ssize_t (*store)(struct mddev *, const char *, size_t);
};

The default_attributes cointains pointers to all the md_sysfs_entry->attr. And the show function of the kobject use cointainer_of() to find the md_sysfs_entry containing the attr. Then it calls the specific show and store functions of the md_sysfs_entry instead of doing something similar for all attributes.

MDADM git repository

If by any chance *someone* would have to edit mdadm for the assignment number 2, here is the git repository :

git://neil.brown.name/mdadm

The function Create in Create.c take cares of creating a new array, the mapping_t pers[] structure in maps.c take care of mapping “RAID level names” to level numbers. Those numbers are defined in mdadm.h. Of course those numbers should match what’s defined in the kernel in linux/raid/md_u.h…

To compile mdadm, just type “make”.

Introduce delay between printk (kernel messages) at boot

If you’re developing into the linux kernel but your system is crashing at boot, the boot_delay parameter may be usefull. If you added printk messages to see what happened just before, you may use the boot_delay=XXX options to add XXX milliseconds between each printk, and therefore allow you to see each messages if the messages pass through quickly.

To add this parameter, type “e” while the good entry is highlighted in the grub menu bootloader, and add boot_delay=XXX at the end of the line starting with “linux=”.

This is the “quick” solution. Other posts in this blog allows you to find other solution to make the console go through a serial link in a VM for instance.

Get a map (dictionnary) of arguments->value from an URI in Android with any API Level requirements

If someone wants to use the URI class to parse an URL in Android, and get the given parameters the normal way to do it is :

String url = "http://www.example.com/?argument=value&argument2=value2&...";
Uri uri = Uri.parse(url);

//To get the value of known parameters
String argument = uri.getQueryParameter("argument");
String argument2 = uri.getQueryParameter("argument2");
...

//To look at all parameters if you don't know what you're waiting for
for (String key : uri.getQueryParameterNames()) {
   String value = uri.getQueryParameter(key);
   //Do something with value  key, like using a switch/case
}


The problem with the first method is that :

  • You need to know the arguments, and you cannot check if there is “unknow” arguments, like a malicious client trying to pass “include=X”
  • The query part of the URI is parsed each time you call setQueryParameter from the beginning !

The problem with the second method  is that :

  • When you call getQueryParameterNames() the whole query is parsed once
  • The query part of the URI is parsed each time you call setQueryParameter from the beginning !
  • getQueryParameterNames() is only available starting with Android 3.0 (API Level 11)

So I wrote the code below, which gives you a map of argument->value in one reading. You can then call any map-related function like containsKey(key) to know if an argument is in the URL, entrySet() to iterate through all argument/value, keySet() to get all the arguments, and values() to get all values. This only parse the URL once and is much more convenient. This code is in distributed in any GPL variant, take the one you prefer.

	/**
	 * Return a map of argument->value from a query in a URI
	 * @param uri The URI
	 */
	private Map<String,String> getQueryParameter(Uri uri) {
	    if (uri.isOpaque()) {
	    	return Collections.emptyMap();
	    }

	    String query = uri.getEncodedQuery();
	    if (query == null) {
	        return Collections.emptyMap();
	    }

	    Map<String,String> parameters = new LinkedHashMap<String,String>();
	    int start = 0;
	    do {
	        int next = query.indexOf('&', start);
	        int end = (next == -1) ? query.length() : next;

	        int separator = query.indexOf('=', start);
	        if (separator > end || separator == -1) {
	            separator = end;
	        }

	        String name = query.substring(start, separator);
	        String value;
	        if (separator < end)
	        	value = query.substring(separator + 1, end);
	        else
	        	value = "";
	        
	        parameters.put(Uri.decode(name),Uri.decode(value));

	        // Move start to end of name.
	        start = end + 1;
	    } while (start < query.length());

	    return Collections.unmodifiableMap(parameters);
	}

Understanding sleeping mechanism in Linux Kernel

The students of INFO-0940 were asked to remove a task from it’s current scheduler class when calling a new syscall and put it back when we call again that syscall. The question is : what to do if the task is currently sleeping? Cause the timer will expire at one moment and maybe put back the state to TASK_RUNNING and run the task which should haven’t been able to run again. So either it expires after the processus is back in its previous scheduler, and that’s okay as it will reset it in a runnable state. Or it expires while it’s still removed, and that’s a problem as it will put it back in its previous state (or not, depending on how the syscall is implemented).
Warning : this post is just the result of a quick look. More to give you some ideas and basis for understanding the sleeping mechanism.

 

So we should try to understand how sleep works.

 

Let’s look at the nanosleep syscall (which is called when you user sleep() or usleep() functions in C).

SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp,
		struct timespec __user *, rmtp)
{
	struct timespec tu;

	if (copy_from_user(&tu, rqtp, sizeof(tu)))
		return -EFAULT;

	if (!timespec_valid(&tu))
		return -EINVAL;

	return hrtimer_nanosleep(&tu, rmtp, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
}

It copies the userspace data, check that the time is valid and call hrtimer_nanosleep.

long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp,
		       const enum hrtimer_mode mode, const clockid_t clockid)
{
	struct restart_block *restart;
	struct hrtimer_sleeper t;
	int ret = 0;
	unsigned long slack;

	slack = current->timer_slack_ns;
	if (rt_task(current))
		slack = 0;

	hrtimer_init_on_stack(&t.timer, clockid, mode);
	hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack);
	if (do_nanosleep(&t, mode))
		goto out;

	/* Absolute timers do not update the rmtp value and restart: */
	if (mode == HRTIMER_MODE_ABS) {
		ret = -ERESTARTNOHAND;
		goto out;
	}

	if (rmtp) {
		ret = update_rmtp(&t.timer, rmtp);
		if (ret <= 0)
			goto out;
	}

	restart = &current_thread_info()->restart_block;
	restart->fn = hrtimer_nanosleep_restart;
	restart->nanosleep.clockid = t.timer.base->clockid;
	restart->nanosleep.rmtp = rmtp;
	restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);

	ret = -ERESTART_RESTARTBLOCK;
out:
	destroy_hrtimer_on_stack(&t.timer);
	return ret;
}

It initialize a timer and call do_nanosleep giving it the timer. After do_nanosleep it will destroy/free the timer structure.

static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
{
	hrtimer_init_sleeper(t, current);

	do {
		set_current_state(TASK_INTERRUPTIBLE);
		hrtimer_start_expires(&t->timer, mode);
		if (!hrtimer_active(&t->timer))
			t->task = NULL;

		if (likely(t->task))
			schedule();

		hrtimer_cancel(&t->timer);
		mode = HRTIMER_MODE_ABS;

	} while (t->task && !signal_pending(current));

	__set_current_state(TASK_RUNNING);

	return t->task == NULL;
}

This is the interesting part.

 

The first thing done is calling hrtimer_init_sleeper(); which will set parameters of the timer to call the function hrtimer_wakeup when the timer expires, and specify the task to wakeup at that time. hrtimer_wakeup will simply set the task to NULL and call wake_up_process() on that task. We’ll come back to wake_up_process(task) later.

 

We see that after that the state is changed to TASK_INTERRUPTIBLE. Then it starts the timer strictly speaking, and go in schedule().

 

Remember that schedule(), before scheduling a new task, will test the state of the current (which is now “previous”) task, and if the state is not TASK_RUNNING, it will remove that task from its runqueue (and that is the case as it is in the TASK_INTERRUPTIBLE state).

 

Schedule() goes on and start scheduling another task, if there is not, it will run the “idle” task.

 

At some point, the timer will expire. The timer rely on an hardware timer which will cause an interrupt, leaving any current task to process the interrupt handler (this is not the schedule() handler and so on !). The interrupt handler for the hardware timer is  hrtimer_interrupt() which will run hrtimer_wakeup() for all expired timer. As said before, outr timer for the sleeping mechanism will set the timer task as NULL, and call wake_up_process(). But that functions just put back the process in the runqueue and set its state to TASK_RUNNING, not actually scheduling it. The interrupt handler will finish and the CPU will go back to it’s currently running process (maybe the idle process, running the cpu_idle() function). 

 

That currently running process will eventually finish its processing or be preempted (the normal scheduling mechanism), and the process which was sleeping will re-run again when it will be picked again by pick_next_task().

 

But where will this process restart? After the sleep() or usleep() call? No ! Where it was… in the nanosleep syscall. What does the syscall do after its call to schedule()? Put the state back to TASK_RUNNING and effectively running. That seems obvious as we said before that the syscall is taking care of destroying the timer structure, so it should not restart after the syscall.

 

Note that I skipped the part where do_nanosleep() is looping and so on… I tried usleep(1) and sleep(1), and the loop never happend. I suspect it’s for very long timers, the kernel would’nt launch a timer of 1 hour long… But it’s just a guess.

 

Can a task have multiple states?

A little correction about what I said in class… I said that technically, a task could have multiple states because it’s a bitmask but not in practice. It seems that was before that, and searching for TASK_KILLABLE in the linux kernel code (and not sched.c only, that was my mistake), you’ll find for example in timer.c :

 

[code]__set_current_state(TASK_KILLABLE);[/code]

 

And TASK_KILLABLE is a combinaison of two states… So it’s not just used for convenient “multiple test at once”.

Update about the assignment #3

As asked in class, I forgot to precise two things :

The scheduler class has to be placed between sched_stop and sched_rt. If multiple tasks have greenlights available, you’ll pick the task with the most greenlights left. If their number of greenlights is equal, you’re free to choose any task.

 

Note that the printed version has the wrong submission date 😉

Standalone version of chrt

This is a modification of the program chrt (used to change the policy of a process) which has no external dependencies. It can be compiled using “gcc -o chrt chrt.c”.

 

[code]/*
 * chrt.c - chrt
 * Command-line utility for manipulating a task's real-time attributes 
 *
 * Robert Love <rml@tech9.net>
 * 27-Apr-2002: initial version
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, v2, as
 * published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Copyright (C) 2004 Robert Love
 */

#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h>
#include <err.h>

#define PACKAGE_STRING "modified for greentask"
#define FALSE 0
#define TRUE 1

/* the SCHED_BATCH is supported since Linux 2.6.16
 *  -- temporary workaround for people with old glibc headers
 */
#if defined (__linux__) && !defined(SCHED_BATCH)
# define SCHED_BATCH 3
#endif

/* the SCHED_IDLE is supported since Linux 2.6.23
 * commit id 0e6aca43e08a62a48d6770e9a159dbec167bf4c6
 * -- temporary workaround for people with old glibc headers
 */
#if defined (__linux__) && !defined(SCHED_IDLE)
# define SCHED_IDLE 5
#endif

#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK)
#define SCHED_RESET_ON_FORK 0x40000000
#endif

#define SCHED_GREEN 6

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

char* _(char* s) {
	return s;
}

long strtol_or_err(const char *str, const char *errmesg)
{
	long num;
	char *end = NULL;

	if (str != NULL && *str != '\0') {
		errno = 0;
		num = strtol(str, &end, 10);
		if (errno == 0 && str != end && end != NULL && *end == '\0')
			return num;
	}
	error(EXIT_FAILURE, errno, "%s: '%s'", errmesg, str);
	return 0;
}

static void __attribute__((__noreturn__)) show_usage(int rc)
{
	FILE *out = rc == EXIT_SUCCESS ? stdout : stderr;

	fprintf(out, _(
	"\nchrt - manipulate real-time attributes of a process\n"
	"\nSet policy:\n"
	"  chrt [options] <policy> <priority> {<pid> | <command> [<arg> ...]}\n"
	"\nGet policy:\n"
	"  chrt [options] {<pid> | <command> [<arg> ...]}\n"));

	fprintf(out, _(
	"\nScheduling policies:\n"
	"  -b | --batch         set policy to SCHED_BATCH\n"
	"  -f | --fifo          set policy to SCHED_FIFO\n"
	"  -i | --idle          set policy to SCHED_IDLE\n"
	"  -o | --other         set policy to SCHED_OTHER\n"
	"  -g | --green         set policy to SCHED_GREEN\n"
	"  -r | --rr            set policy to SCHED_RR (default)\n"));

#ifdef SCHED_RESET_ON_FORK
	fprintf(out, _(
	"\nScheduling flags:\n"
	"  -R | --reset-on-fork set SCHED_RESET_ON_FORK for FIFO or RR\n"));
#endif
	fprintf(out, _(
	"\nOptions:\n"
	"  -h | --help          display this help\n"
	"  -m | --max           show min and max valid priorities\n"
	"  -p | --pid           operate on existing given pid\n"
	"  -v | --verbose       display status information\n"
	"  -V | --version       output version information\n\n"));

	exit(rc);
}

static void show_rt_info(pid_t pid, int isnew)
{
	struct sched_param sp;
	int policy;

	/* don't display "pid 0" as that is confusing */
	if (!pid)
		pid = getpid();

	policy = sched_getscheduler(pid);
	if (policy == -1)
		err(EXIT_FAILURE, _("failed to get pid %d's policy"), pid);

	if (isnew)
		printf(_("pid %d's new scheduling policy: "), pid);
	else
		printf(_("pid %d's current scheduling policy: "), pid);

	switch (policy) {
	case SCHED_OTHER:
		printf("SCHED_OTHER\n");
		break;
	case SCHED_FIFO:
		printf("SCHED_FIFO\n");
		break;
#ifdef SCHED_RESET_ON_FORK
	case SCHED_FIFO|SCHED_RESET_ON_FORK:
		printf("SCHED_FIFO|SCHED_RESET_ON_FORK\n");
		break;
#endif
#ifdef SCHED_IDLE
	case SCHED_IDLE:
		printf("SCHED_IDLE\n");
		break;
#endif
#ifdef SCHED_IDLE
	case SCHED_GREEN:
		printf("SCHED_GREEN\n");
		break;
#endif
	case SCHED_RR:
		printf("SCHED_RR\n");
		break;
#ifdef SCHED_RESET_ON_FORK
	case SCHED_RR|SCHED_RESET_ON_FORK:
		printf("SCHED_RR|SCHED_RESET_ON_FORK\n");
		break;
#endif
#ifdef SCHED_BATCH
	case SCHED_BATCH:
		printf("SCHED_BATCH\n");
		break;
#endif
	default:
		printf(_("unknown\n"));
	}

	if (sched_getparam(pid, &sp))
		err(EXIT_FAILURE, _("failed to get pid %d's attributes"), pid);

	if (isnew)
		printf(_("pid %d's new scheduling priority: %d\n"),
		       pid, sp.sched_priority);
	else
		printf(_("pid %d's current scheduling priority: %d\n"),
		       pid, sp.sched_priority);
}

static void show_min_max(void)
{
	int i;
	int policies[] = { SCHED_OTHER, SCHED_FIFO, SCHED_RR,
#ifdef SCHED_BATCH
			   SCHED_BATCH,
#endif
#ifdef SCHED_IDLE
			   SCHED_IDLE,
#endif
#ifdef SCHED_GREEN
			   SCHED_GREEN,
#endif
			 };
	const char *names[] = { "OTHER", "FIFO", "RR",
#ifdef SCHED_BATCH
				"BATCH",
#endif
#ifdef SCHED_IDLE
				"IDLE",
#endif
#ifdef SCHED_GREEN
				"GREEN",
#endif
			      };

	for (i = 0; i < ARRAY_SIZE(policies); i++) {
		int max = sched_get_priority_max(policies[i]);
		int min = sched_get_priority_min(policies[i]);

		if (max >= 0 && min >= 0)
			printf(_("SCHED_%s min/max priority\t: %d/%d\n"),
					names[i], min, max);
		else
			printf(_("SCHED_%s not supported?\n"), names[i]);
	}
}

int main(int argc, char *argv[])
{
	int i, policy = SCHED_RR, priority = 0, verbose = 0, policy_flag = 0;
	struct sched_param sp;
	pid_t pid = -1;

	static const struct option longopts[] = {
		{ "batch",	0, NULL, 'b' },
		{ "fifo",	0, NULL, 'f' },
		{ "idle",	0, NULL, 'i' },
		{ "pid",	0, NULL, 'p' },
		{ "help",	0, NULL, 'h' },
		{ "max",        0, NULL, 'm' },
		{ "other",	0, NULL, 'o' },
		{ "rr",		0, NULL, 'r' },
		{ "green",		0, NULL, 'g' },
		{ "reset-on-fork", 0, NULL, 'R' },
		{ "verbose",	0, NULL, 'v' },
		{ "version",	0, NULL, 'V' },
		{ NULL,		0, NULL, 0 }
	};

	while((i = getopt_long(argc, argv, "+bfgiphmoRrvV", longopts, NULL)) != -1)
	{
		int ret = EXIT_FAILURE;

		switch (i) {
		case 'b':
#ifdef SCHED_BATCH
			policy = SCHED_BATCH;
#endif
			break;
		case 'f':
			policy = SCHED_FIFO;
			break;
#ifdef SCHED_RESET_ON_FORK
		case 'R':
			policy_flag |= SCHED_RESET_ON_FORK;
			break;
#endif
		case 'i':
#ifdef SCHED_IDLE
			policy = SCHED_IDLE;
#endif
			break;
		case 'g':
#ifdef SCHED_GREEN
			policy = SCHED_GREEN;
#endif
			break;
		case 'm':
			show_min_max();
			return 0;
		case 'o':
			policy = SCHED_OTHER;
			break;
		case 'p':
			errno = 0;
			pid = strtol_or_err(argv[argc - 1], _("failed to parse pid"));
			break;
		case 'r':
			policy = SCHED_RR;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'V':
			printf("chrt (%s)\n", PACKAGE_STRING);
			return 0;
		case 'h':
			ret = EXIT_SUCCESS;
		default:
			show_usage(ret);
		}
	}

	if (((pid > -1) && argc - optind < 1) || ((pid == -1) && argc - optind < 2))
		show_usage(EXIT_FAILURE);

	if ((pid > -1) && (verbose || argc - optind == 1)) {
		show_rt_info(pid, FALSE);
		if (argc - optind == 1)
			return EXIT_SUCCESS;
	}

	errno = 0;
	priority = strtol_or_err(argv[optind], _("failed to parse priority"));

#ifdef SCHED_RESET_ON_FORK
	/* sanity check */
	if ((policy_flag & SCHED_RESET_ON_FORK) &&
	    !(policy == SCHED_FIFO || policy == SCHED_RR))
		errx(EXIT_FAILURE, _("SCHED_RESET_ON_FORK flag is suppoted for "
				"SCHED_FIFO and SCHED_RR policies only"));
#endif

	policy |= policy_flag;

	if (pid == -1)
		pid = 0;
	sp.sched_priority = priority;
	if (sched_setscheduler(pid, policy, &sp) == -1)
		err(EXIT_FAILURE, _("failed to set pid %d's policy"), pid);

	if (verbose)
		show_rt_info(pid, TRUE);

	if (!pid) {
		argv += optind + 1;
		execvp(argv[0], argv);
		printf("FINIIISHED\n");
		perror("execvp");
		err(EXIT_FAILURE, _("failed to execute %s"), argv[0]);
	}

	return EXIT_SUCCESS;
}[/code]

Manual network configuration under ubuntu

This procedure is only with cable, not for wifi

First check that your interface is up with the command “sudo ifconfig” :

lo
If like in this screenshot you do not see an interface named “ethXXX“, you have to start the interface manually.
 
To found which of eth0, eth1, … your network card is, you can type “dmesg | grep eth

dmesg
We see here that the card “Intel Pro/1000” takes the “eth0” interface name. But it’s later renamed to “eth1“.
 
So our interface here is eth1, to bring it up, simply run “sudo ifconfig eth1 up”.
eth1up

You may not have an IP Adress automatically like in this screenshot. If it’s the case, simply type “sudo dhclient eth1” to get one with DHCP.
 
If it doesn’t work, try to directly ping an IP address like google’s dns server 8.8.8.8 with the command “ping 8.8.8.8“. If it works, you probably have a nameserver problem. Simply add the line “nameserver 8.8.8.8” in /etc/resolv.conf